From 85f74774509872c9f016f04b8f69ec2575f22392 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 Jun 2020 11:35:42 +0300 Subject: [PATCH 01/13] Cleaning up traks --- app/build.gradle | 10 +++++----- .../java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt | 3 +++ .../java/org/koitharu/kotatsu/core/db/TracksDao.kt | 4 +++- .../kotatsu/domain/tracking/TrackingRepository.kt | 7 +++++++ .../org/koitharu/kotatsu/ui/tracker/TrackWorker.kt | 1 + build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d01996f3c..26ee37c35 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -61,12 +61,12 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6' - implementation 'androidx.core:core-ktx:1.4.0-alpha01' + implementation 'androidx.core:core-ktx:1.5.0-alpha01' implementation 'androidx.appcompat:appcompat:1.3.0-alpha01' - implementation 'androidx.activity:activity-ktx:1.2.0-alpha05' - implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha05' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha03' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6' + implementation 'androidx.activity:activity-ktx:1.2.0-alpha06' + implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha06' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha04' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01' implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03' implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01' diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt index 460205052..547fed576 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt @@ -19,4 +19,7 @@ interface TrackLogsDao { @Query("DELETE FROM track_logs WHERE manga_id = :mangaId") suspend fun removeAll(mangaId: Long) + + @Query("DELETE FROM track_logs WHERE manga_id NOT IN (SELECT manga_id FROM tracks)") + suspend fun cleanup() } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/TracksDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/TracksDao.kt index ae72b2c11..e3f0ea701 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/TracksDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/TracksDao.kt @@ -25,11 +25,13 @@ abstract class TracksDao { @Query("DELETE FROM tracks WHERE manga_id = :mangaId") abstract suspend fun delete(mangaId: Long) + @Query("DELETE FROM tracks WHERE manga_id NOT IN (SELECT manga_id FROM history UNION SELECT manga_id FROM favourites)") + abstract suspend fun cleanup() + @Transaction open suspend fun upsert(entity: TrackEntity) { if (update(entity) == 0) { insert(entity) } } - } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt b/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt index 28dedb15d..6b1a3bdde 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt @@ -44,6 +44,13 @@ class TrackingRepository : KoinComponent { } } + suspend fun cleanup() { + db.withTransaction { + db.tracksDao.cleanup() + db.trackLogsDao.cleanup() + } + } + suspend fun storeTrackResult( mangaId: Long, knownChaptersCount: Int, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackWorker.kt b/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackWorker.kt index dd154a95e..68173f705 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackWorker.kt @@ -118,6 +118,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) : } success++ } + repo.cleanup() if (success == 0) { Result.retry() } else { diff --git a/build.gradle b/build.gradle index 08e69a267..3219af198 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0-alpha10' + classpath 'com.android.tools.build:gradle:4.2.0-alpha02' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8a314251c..f8233caa7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat May 30 09:45:56 EEST 2020 +#Sat Jun 20 11:05:53 EEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-milestone-1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip From 7f9cfdbf7afa0e45dc31ef6be8f7640cd8fd2b53 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 Jun 2020 12:10:42 +0300 Subject: [PATCH 02/13] Show app update in dialog --- .../kotatsu/core/github/AppVersion.kt | 3 +- .../kotatsu/core/github/GithubRepository.kt | 3 +- .../koitharu/kotatsu/ui/list/MainActivity.kt | 9 +- .../kotatsu/ui/settings/AppUpdateChecker.kt | 128 +++++++++++++ .../kotatsu/ui/settings/AppUpdateService.kt | 178 ------------------ .../ui/settings/MainSettingsFragment.kt | 2 +- app/src/main/res/values-ru/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 8 files changed, 140 insertions(+), 187 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateChecker.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateService.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/core/github/AppVersion.kt b/app/src/main/java/org/koitharu/kotatsu/core/github/AppVersion.kt index 52c61c7c1..c5961a543 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/github/AppVersion.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/github/AppVersion.kt @@ -9,5 +9,6 @@ data class AppVersion( val name: String, val url: String, val apkSize: Long, - val apkUrl: String + val apkUrl: String, + val description: String ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/github/GithubRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/github/GithubRepository.kt index 4cd5c445b..b6a6d000c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/github/GithubRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/github/GithubRepository.kt @@ -22,7 +22,8 @@ class GithubRepository : KoinComponent { url = json.getString("html_url"), name = json.getString("name").removePrefix("v"), apkSize = asset.getLong("size"), - apkUrl = asset.getString("browser_download_url") + apkUrl = asset.getString("browser_download_url"), + description = json.getString("body") ) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt index efed16cc6..399ef4142 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt @@ -11,7 +11,6 @@ import android.view.Menu import android.view.MenuItem import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.view.isVisible -import androidx.core.view.postDelayed import androidx.fragment.app.Fragment import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.google.android.material.navigation.NavigationView @@ -26,14 +25,14 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.domain.MangaProviderFactory import org.koitharu.kotatsu.ui.common.BaseActivity import org.koitharu.kotatsu.ui.list.favourites.FavouritesContainerFragment +import org.koitharu.kotatsu.ui.list.feed.FeedFragment import org.koitharu.kotatsu.ui.list.history.HistoryListFragment import org.koitharu.kotatsu.ui.list.local.LocalListFragment import org.koitharu.kotatsu.ui.list.remote.RemoteListFragment -import org.koitharu.kotatsu.ui.list.feed.FeedFragment import org.koitharu.kotatsu.ui.reader.ReaderActivity import org.koitharu.kotatsu.ui.reader.ReaderState import org.koitharu.kotatsu.ui.search.SearchHelper -import org.koitharu.kotatsu.ui.settings.AppUpdateService +import org.koitharu.kotatsu.ui.settings.AppUpdateChecker import org.koitharu.kotatsu.ui.settings.SettingsActivity import org.koitharu.kotatsu.ui.tracker.TrackWorker import org.koitharu.kotatsu.utils.ext.getDisplayMessage @@ -70,10 +69,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } ?: run { openDefaultSection() } - drawer.postDelayed(2000) { - AppUpdateService.startIfRequired(applicationContext) - } TrackWorker.setup(applicationContext) + AppUpdateChecker(this).invoke() } override fun onDestroy() { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateChecker.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateChecker.kt new file mode 100644 index 000000000..52fd992c2 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateChecker.kt @@ -0,0 +1,128 @@ +package org.koitharu.kotatsu.ui.settings + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import androidx.activity.ComponentActivity +import androidx.lifecycle.lifecycleScope +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.core.KoinComponent +import org.koin.core.inject +import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.github.AppVersion +import org.koitharu.kotatsu.core.github.GithubRepository +import org.koitharu.kotatsu.core.github.VersionId +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.utils.FileSizeUtils +import org.koitharu.kotatsu.utils.ext.byte2HexFormatted +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.security.cert.CertificateEncodingException +import java.security.cert.CertificateException +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.util.concurrent.TimeUnit + +class AppUpdateChecker(private val activity: ComponentActivity) : KoinComponent { + + private val settings by inject() + + operator fun invoke() { + if (isUpdateSupported(activity) && settings.appUpdateAuto && settings.appUpdate + PERIOD < System.currentTimeMillis()) { + launch() + } + } + + private fun launch() = activity.lifecycleScope.launch { + try { + val repo = GithubRepository() + val version = withContext(Dispatchers.IO) { + repo.getLatestVersion() + } + val newVersionId = VersionId.parse(version.name) + val currentVersionId = VersionId.parse(BuildConfig.VERSION_NAME) + if (newVersionId > currentVersionId) { + showUpdateDialog(version) + } + settings.appUpdate = System.currentTimeMillis() + } catch (_: CancellationException) { + } catch (e: Throwable) { + if (BuildConfig.DEBUG) { + e.printStackTrace() + } + } + } + + private fun showUpdateDialog(version: AppVersion) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.app_update_available) + .setMessage(buildString { + append(activity.getString(R.string.new_version_s, version.name)) + appendln() + append(activity.getString(R.string.size_s, FileSizeUtils.formatBytes(activity, version.apkSize))) + appendln() + appendln() + append(version.description) + }) + .setPositiveButton(R.string.download) { _, _ -> + activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(version.apkUrl))) + } + .setNegativeButton(R.string.close, null) + .create() + .show() + } + + companion object { + + private const val CERT_SHA1 = "2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE" + private val PERIOD = TimeUnit.HOURS.toMillis(6) + + fun isUpdateSupported(context: Context): Boolean { + return getCertificateSHA1Fingerprint(context) == CERT_SHA1 + } + + @Suppress("DEPRECATION") + @SuppressLint("PackageManagerGetSignatures") + private fun getCertificateSHA1Fingerprint(context: Context): String? { + val packageInfo = try { + context.packageManager.getPackageInfo( + context.packageName, + PackageManager.GET_SIGNATURES + ) + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + return null + } + val signatures = packageInfo?.signatures + val cert: ByteArray = signatures?.firstOrNull()?.toByteArray() ?: return null + val input: InputStream = ByteArrayInputStream(cert) + val c = try { + val cf = CertificateFactory.getInstance("X509") + cf.generateCertificate(input) as X509Certificate + } catch (e: CertificateException) { + e.printStackTrace() + return null + } + return try { + val md: MessageDigest = MessageDigest.getInstance("SHA1") + val publicKey: ByteArray = md.digest(c.encoded) + publicKey.byte2HexFormatted() + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + null + } catch (e: CertificateEncodingException) { + e.printStackTrace() + null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateService.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateService.kt deleted file mode 100644 index 94d0d97fb..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateService.kt +++ /dev/null @@ -1,178 +0,0 @@ -package org.koitharu.kotatsu.ui.settings - -import android.annotation.SuppressLint -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.BitmapFactory -import android.net.Uri -import android.os.Build -import androidx.core.app.NotificationCompat -import androidx.core.content.ContextCompat -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.github.AppVersion -import org.koitharu.kotatsu.core.github.GithubRepository -import org.koitharu.kotatsu.core.github.VersionId -import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.common.BaseService -import org.koitharu.kotatsu.utils.FileSizeUtils -import org.koitharu.kotatsu.utils.ext.byte2HexFormatted -import java.io.ByteArrayInputStream -import java.io.InputStream -import java.security.MessageDigest -import java.security.NoSuchAlgorithmException -import java.security.cert.CertificateEncodingException -import java.security.cert.CertificateException -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate -import java.util.concurrent.TimeUnit - - -class AppUpdateService : BaseService() { - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - launch(Dispatchers.IO) { - try { - val repo = GithubRepository() - val version = repo.getLatestVersion() - val newVersionId = VersionId.parse(version.name) - val currentVersionId = VersionId.parse(BuildConfig.VERSION_NAME) - if (newVersionId > currentVersionId) { - withContext(Dispatchers.Main) { - showUpdateNotification(version) - } - } - AppSettings(this@AppUpdateService).appUpdate = System.currentTimeMillis() - } catch (_: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - } finally { - withContext(Dispatchers.Main) { - stopSelf(startId) - } - } - } - return START_NOT_STICKY - } - - private fun showUpdateNotification(newVersion: AppVersion) { - val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - && manager.getNotificationChannel(CHANNEL_ID) == null - ) { - val channel = NotificationChannel( - CHANNEL_ID, - getString(R.string.application_update), - NotificationManager.IMPORTANCE_DEFAULT - ) - manager.createNotificationChannel(channel) - } - val builder = NotificationCompat.Builder(this, CHANNEL_ID) - builder.setContentTitle(getString(R.string.app_update_available)) - builder.setContentText(buildString { - append(newVersion.name) - append(" (") - append(FileSizeUtils.formatBytes(this@AppUpdateService, newVersion.apkSize)) - append(')') - }) - builder.setContentIntent( - PendingIntent.getActivity( - this, - NOTIFICATION_ID, - Intent(Intent.ACTION_VIEW, Uri.parse(newVersion.url)), - PendingIntent.FLAG_CANCEL_CURRENT - ) - ) - builder.addAction( - R.drawable.ic_download, getString(R.string.download), - PendingIntent.getActivity( - this, - NOTIFICATION_ID + 1, - Intent(Intent.ACTION_VIEW, Uri.parse(newVersion.apkUrl)), - PendingIntent.FLAG_CANCEL_CURRENT - ) - ) - builder.setSmallIcon(R.drawable.ic_stat_update) - builder.setAutoCancel(true) - builder.color = ContextCompat.getColor(this, R.color.blue_primary_dark) - builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)) - manager.notify(NOTIFICATION_ID, builder.build()) - } - - companion object { - - private const val CERT_SHA1 = "2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE" - private const val NOTIFICATION_ID = 202 - private const val CHANNEL_ID = "update" - private val PERIOD = TimeUnit.HOURS.toMillis(6) - - fun isUpdateSupported(context: Context): Boolean { - return getCertificateSHA1Fingerprint(context) == CERT_SHA1 - } - - fun startIfRequired(context: Context) { - if (!isUpdateSupported(context)) { - return - } - val settings = AppSettings(context) - if (settings.appUpdateAuto) { - val lastUpdate = settings.appUpdate - if (lastUpdate + PERIOD < System.currentTimeMillis()) { - start(context) - } - } - } - - private fun start(context: Context) { - try { - context.startService(Intent(context, AppUpdateService::class.java)) - } catch (_: IllegalStateException) { - } - } - - @Suppress("DEPRECATION") - @SuppressLint("PackageManagerGetSignatures") - private fun getCertificateSHA1Fingerprint(context: Context): String? { - val packageInfo = try { - context.packageManager.getPackageInfo( - context.packageName, - PackageManager.GET_SIGNATURES - ) - } catch (e: PackageManager.NameNotFoundException) { - e.printStackTrace() - return null - } - val signatures = packageInfo?.signatures - val cert: ByteArray = signatures?.firstOrNull()?.toByteArray() ?: return null - val input: InputStream = ByteArrayInputStream(cert) - val c = try { - val cf = CertificateFactory.getInstance("X509") - cf.generateCertificate(input) as X509Certificate - } catch (e: CertificateException) { - e.printStackTrace() - return null - } - return try { - val md: MessageDigest = MessageDigest.getInstance("SHA1") - val publicKey: ByteArray = md.digest(c.encoded) - publicKey.byte2HexFormatted() - } catch (e: NoSuchAlgorithmException) { - e.printStackTrace() - null - } catch (e: CertificateEncodingException) { - e.printStackTrace() - null - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt index a7cd0b7b2..a22f15647 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt @@ -42,7 +42,7 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), findPreference(R.string.key_reader_switchers)?.summaryProvider = MultiSummaryProvider(R.string.gestures_only) findPreference(R.string.key_app_update_auto)?.run { - isVisible = AppUpdateService.isUpdateSupported(context) + isVisible = AppUpdateChecker.isUpdateSupported(context) } findPreference(R.string.key_local_storage)?.run { summary = settings.getStorageDir(context)?.getStorageName(context) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c6a32d4a7..aa40ca4e2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -137,4 +137,6 @@ Здесь будут отображаться обновления манги, которую Вы читаете Результаты поиска Похожие + Новая версия: %s + Размер: %s \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 83e6bd10c..dfb998cf5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -138,4 +138,6 @@ Here you will see the new chapters of the manga you are reading Search results Related + New version: %s + Size: %s \ No newline at end of file From 79058440a1aff6c2c88bfafab4091e2344417fe8 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 25 Jun 2020 19:40:50 +0300 Subject: [PATCH 03/13] Fix grid span count #11 --- app/build.gradle | 8 ++++---- .../org/koitharu/kotatsu/ui/list/MangaListFragment.kt | 11 +++++------ .../org/koitharu/kotatsu/ui/list/MangaListSheet.kt | 11 +++++------ 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 26ee37c35..a5a1c8035 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,13 +65,13 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.3.0-alpha01' implementation 'androidx.activity:activity-ktx:1.2.0-alpha06' implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha06' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha04' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha05' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7' - implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01' - implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha04' implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01' implementation 'androidx.preference:preference-ktx:1.1.1' - implementation 'androidx.work:work-runtime-ktx:2.4.0-beta01' + implementation 'androidx.work:work-runtime-ktx:2.4.0-rc01' implementation 'com.google.android.material:material:1.3.0-alpha01' implementation 'androidx.room:room-runtime:2.2.5' diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt index 2dcaae645..d12a28d9f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt @@ -41,9 +41,9 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), SectionItemDecoration.Callback, SwipeRefreshLayout.OnRefreshListener { private val settings by inject() - private val adapterConfig = MergeAdapter.Config.Builder() + private val adapterConfig = ConcatAdapter.Config.Builder() .setIsolateViewTypes(true) - .setStableIdMode(MergeAdapter.Config.StableIdMode.SHARED_STABLE_IDS) + .setStableIdMode(ConcatAdapter.Config.StableIdMode.SHARED_STABLE_IDS) .build() private var adapter: MangaListAdapter? = null @@ -245,18 +245,17 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), adapter?.listMode = mode recyclerView.layoutManager = when (mode) { ListMode.GRID -> { - val spanCount = UiUtils.resolveGridSpanCount(ctx) - GridLayoutManager(ctx, spanCount).apply { + GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx)).apply { spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int) = if (position < getItemsCount()) - 1 else spanCount + 1 else this@apply.spanCount } } } else -> LinearLayoutManager(ctx) } recyclerView.recycledViewPool.clear() - recyclerView.adapter = MergeAdapter(adapterConfig, adapter, progressAdapter) + recyclerView.adapter = ConcatAdapter(adapterConfig, adapter, progressAdapter) recyclerView.addItemDecoration( when (mode) { ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListSheet.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListSheet.kt index ddddee7db..eddfdf2c4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListSheet.kt @@ -36,9 +36,9 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list), SharedPreferences.OnSharedPreferenceChangeListener, Toolbar.OnMenuItemClickListener { private val settings by inject() - private val adapterConfig = MergeAdapter.Config.Builder() + private val adapterConfig = ConcatAdapter.Config.Builder() .setIsolateViewTypes(true) - .setStableIdMode(MergeAdapter.Config.StableIdMode.SHARED_STABLE_IDS) + .setStableIdMode(ConcatAdapter.Config.StableIdMode.SHARED_STABLE_IDS) .build() private var adapter: MangaListAdapter? = null @@ -181,17 +181,16 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list), adapter?.listMode = mode recyclerView.layoutManager = when (mode) { ListMode.GRID -> { - val spanCount = UiUtils.resolveGridSpanCount(ctx) - GridLayoutManager(ctx, spanCount).apply { + GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx)).apply { spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int) = if (position < getItemsCount()) - 1 else spanCount + 1 else this@apply.spanCount } } } else -> LinearLayoutManager(ctx) } - recyclerView.adapter = MergeAdapter(adapterConfig, adapter, progressAdapter) + recyclerView.adapter = ConcatAdapter(adapterConfig, adapter, progressAdapter) recyclerView.addItemDecoration( when (mode) { ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL) From a2f09d8763bb1726375f2c4c10f432641468ccdb Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 29 Jun 2020 09:48:17 +0300 Subject: [PATCH 04/13] Restore download after network error --- .idea/dictionaries/admin.xml | 1 + app/build.gradle | 1 - .../core/parser/LocalMangaRepository.kt | 1 - .../kotatsu/domain/MangaProviderFactory.kt | 44 ++++++++++----- .../ui/download/DownloadNotification.kt | 5 ++ .../kotatsu/ui/download/DownloadService.kt | 55 +++++++++++-------- .../ui/list/local/LocalListPresenter.kt | 4 +- .../koitharu/kotatsu/utils/ext/AndroidExt.kt | 22 ++++++++ app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 10 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt diff --git a/.idea/dictionaries/admin.xml b/.idea/dictionaries/admin.xml index 07b4b61db..143df12e7 100644 --- a/.idea/dictionaries/admin.xml +++ b/.idea/dictionaries/admin.xml @@ -3,6 +3,7 @@ chucker desu + failsafe koin kotatsu manga diff --git a/app/build.gradle b/app/build.gradle index a5a1c8035..9606fe6d9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,6 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6' implementation 'androidx.core:core-ktx:1.5.0-alpha01' - implementation 'androidx.appcompat:appcompat:1.3.0-alpha01' implementation 'androidx.activity:activity-ktx:1.2.0-alpha06' implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha06' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha05' diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt index c9eb5190c..725119563 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt @@ -72,7 +72,6 @@ class LocalMangaRepository : MangaRepository, KoinComponent { } } - fun delete(manga: Manga): Boolean { val file = Uri.parse(manga.url).toFile() return file.delete() diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/MangaProviderFactory.kt b/app/src/main/java/org/koitharu/kotatsu/domain/MangaProviderFactory.kt index d2dc795ab..5cc4a3000 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/MangaProviderFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/domain/MangaProviderFactory.kt @@ -13,7 +13,8 @@ import java.util.* object MangaProviderFactory : KoinComponent { private val loaderContext by inject() - private val cache = EnumMap>(MangaSource::class.java) + private val cache = + EnumMap>(MangaSource::class.java) fun getSources(includeHidden: Boolean): List { val settings = get() @@ -33,24 +34,37 @@ object MangaProviderFactory : KoinComponent { } } - fun createLocal(): LocalMangaRepository = - (cache[MangaSource.LOCAL]?.get() as? LocalMangaRepository) - ?: LocalMangaRepository().also { - cache[MangaSource.LOCAL] = WeakReference(it) + fun createLocal(): LocalMangaRepository { + var instance = cache[MangaSource.LOCAL]?.get() + if (instance == null) { + synchronized(cache) { + instance = cache[MangaSource.LOCAL]?.get() + if (instance == null) { + instance = LocalMangaRepository() + cache[MangaSource.LOCAL] = WeakReference(instance) + } } + } + return instance as LocalMangaRepository + } @Throws(Throwable::class) fun create(source: MangaSource): MangaRepository { - cache[source]?.get()?.let { - return it + var instance = cache[source]?.get() + if (instance == null) { + synchronized(cache) { + instance = cache[source]?.get() + if (instance == null) { + instance = try { + source.cls.getDeclaredConstructor(MangaLoaderContext::class.java) + .newInstance(loaderContext) + } catch (e: NoSuchMethodException) { + source.cls.newInstance() + } + cache[source] = WeakReference(instance!!) + } + } } - val instance = try { - source.cls.getDeclaredConstructor(MangaLoaderContext::class.java) - .newInstance(loaderContext) - } catch (e: NoSuchMethodException) { - source.cls.newInstance() - } - cache[source] = WeakReference(instance) - return instance + return instance!! } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt b/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt index 2ec22af54..cc7f37049 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt @@ -92,6 +92,11 @@ class DownloadNotification(private val context: Context) { builder.setCategory(NotificationCompat.CATEGORY_PROGRESS) } + fun setWaitingForNetwork() { + builder.setProgress(0, 0, false) + builder.setContentText(context.getString(R.string.waiting_for_network)) + } + fun setPostProcessing() { builder.setProgress(1, 0, true) builder.setContentText(context.getString(R.string.processing_)) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt index 9d9ccb9e8..033d603b4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import okhttp3.OkHttpClient import okhttp3.Request +import okio.IOException import org.koin.android.ext.android.inject import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R @@ -24,10 +25,7 @@ import org.koitharu.kotatsu.domain.local.MangaZip import org.koitharu.kotatsu.ui.common.BaseService import org.koitharu.kotatsu.ui.common.dialog.CheckBoxAlertDialog import org.koitharu.kotatsu.utils.CacheUtils -import org.koitharu.kotatsu.utils.ext.await -import org.koitharu.kotatsu.utils.ext.retryUntilSuccess -import org.koitharu.kotatsu.utils.ext.safe -import org.koitharu.kotatsu.utils.ext.sub +import org.koitharu.kotatsu.utils.ext.* import java.io.File import java.util.concurrent.TimeUnit import kotlin.collections.set @@ -37,6 +35,7 @@ class DownloadService : BaseService() { private lateinit var notification: DownloadNotification private lateinit var wakeLock: PowerManager.WakeLock + private lateinit var connectivityManager: ConnectivityManager private val okHttp by inject() private val cache by inject() @@ -47,6 +46,7 @@ class DownloadService : BaseService() { override fun onCreate() { super.onCreate() notification = DownloadNotification(this) + connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading") } @@ -88,9 +88,11 @@ class DownloadService : BaseService() { try { val repo = MangaProviderFactory.create(manga.source) val cover = safe { - Coil.execute(GetRequestBuilder(this@DownloadService) - .data(manga.coverUrl) - .build()).drawable + Coil.execute( + GetRequestBuilder(this@DownloadService) + .data(manga.coverUrl) + .build() + ).drawable } withContext(Dispatchers.Main) { notification.setLargeIcon(cover) @@ -112,23 +114,30 @@ class DownloadService : BaseService() { if (chaptersIds == null || chapter.id in chaptersIds) { val pages = repo.getPages(chapter) for ((pageIndex, page) in pages.withIndex()) { - val url = repo.getPageFullUrl(page) - val file = cache[url] ?: downloadPage(url, destination) - output.addPage( - chapter, - file, - pageIndex, - MimeTypeMap.getFileExtensionFromUrl(url) + failsafe@ do { + try { + val url = repo.getPageFullUrl(page) + val file = cache[url] ?: downloadPage(url, destination) + output.addPage( + chapter, + file, + pageIndex, + MimeTypeMap.getFileExtensionFromUrl(url) + ) + } catch (e: IOException) { + notification.setWaitingForNetwork() + notification.update() + connectivityManager.waitForNetwork() + continue@failsafe + } + } while (false) + notification.setProgress( + chapters.size, + pages.size, + chapterIndex, + pageIndex ) - withContext(Dispatchers.Main) { - notification.setProgress( - chapters.size, - pages.size, - chapterIndex, - pageIndex - ) - notification.update() - } + notification.update() } } } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt index 53cbe4123..62a387803 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt @@ -13,7 +13,6 @@ import org.koin.core.get import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.parser.LocalMangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.domain.MangaProviderFactory @@ -33,7 +32,8 @@ class LocalListPresenter : BasePresenter>() { private lateinit var repository: LocalMangaRepository override fun onFirstViewAttach() { - repository = MangaProviderFactory.create(MangaSource.LOCAL) as LocalMangaRepository + repository = MangaProviderFactory.createLocal() + super.onFirstViewAttach() } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt new file mode 100644 index 000000000..b9cc851c4 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt @@ -0,0 +1,22 @@ +package org.koitharu.kotatsu.utils.ext + +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkRequest +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +suspend fun ConnectivityManager.waitForNetwork(): Network { + val request = NetworkRequest.Builder().build() + return suspendCancellableCoroutine { cont -> + val callback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + cont.resume(network) + } + } + registerNetworkCallback(request, callback) + cont.invokeOnCancellation { + unregisterNetworkCallback(callback) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index aa40ca4e2..1306bd976 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -139,4 +139,5 @@ Похожие Новая версия: %s Размер: %s + Ожидание подключения… \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dfb998cf5..215a2316c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,4 +140,5 @@ Related New version: %s Size: %s + Waiting for network… \ No newline at end of file From 84ef2af82fd53b5783dff53dc80ed04bbe586167 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 29 Jun 2020 13:06:40 +0300 Subject: [PATCH 05/13] Fix MangaDetailsPresenter sharing --- app/build.gradle | 2 +- .../kotatsu/ui/browser/BrowserActivity.kt | 4 +-- .../kotatsu/ui/common/BaseActivity.kt | 11 -------- .../ui/common/SharedPresenterHolder.kt | 25 +++++++++++++++++++ .../kotatsu/ui/details/ChaptersFragment.kt | 4 ++- .../ui/details/MangaDetailsActivity.kt | 10 +++++--- .../ui/details/MangaDetailsFragment.kt | 4 ++- .../ui/details/MangaDetailsPresenter.kt | 23 +++++++---------- .../ui/details/RelatedMangaFragment.kt | 4 ++- .../ui/list/local/LocalListPresenter.kt | 8 +++--- .../kotatsu/ui/reader/ReaderActivity.kt | 2 +- .../kotatsu/ui/search/SearchActivity.kt | 2 +- .../ui/search/global/GlobalSearchActivity.kt | 2 +- .../ui/widget/shelf/ShelfConfigActivity.kt | 2 +- 14 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/common/SharedPresenterHolder.kt diff --git a/app/build.gradle b/app/build.gradle index 9606fe6d9..6d1d97bc2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,7 @@ android { minSdkVersion 21 targetSdkVersion 29 versionCode gitCommits - versionName '0.5-b1' + versionName '0.5-b2' kapt { arguments { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserActivity.kt index a0b406121..a616b6172 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserActivity.kt @@ -29,7 +29,7 @@ class BrowserActivity : BaseActivity(), BrowserCallback { webView.webViewClient = BrowserClient(this) val url = intent?.dataString if (url.isNullOrEmpty()) { - finish() + finishAfterTransition() } else { webView.loadUrl(url) } @@ -43,7 +43,7 @@ class BrowserActivity : BaseActivity(), BrowserCallback { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { android.R.id.home -> { webView.stopLoading() - finish() + finishAfterTransition() true } R.id.action_browser -> { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/common/BaseActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/common/BaseActivity.kt index eb0230f18..f6cb18b76 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/common/BaseActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/common/BaseActivity.kt @@ -1,12 +1,10 @@ package org.koitharu.kotatsu.ui.common -import android.view.KeyEvent import android.view.MenuItem import android.view.View import androidx.appcompat.widget.Toolbar import moxy.MvpAppCompatActivity import org.koin.core.KoinComponent -import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R abstract class BaseActivity : MvpAppCompatActivity(), KoinComponent { @@ -29,13 +27,4 @@ abstract class BaseActivity : MvpAppCompatActivity(), KoinComponent { onBackPressed() true } else super.onOptionsItemSelected(item) - - override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { - //TODO remove. Just for testing - if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { - recreate() - return true - } - return super.onKeyDown(keyCode, event) - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/common/SharedPresenterHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/common/SharedPresenterHolder.kt new file mode 100644 index 000000000..05b676d5d --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/common/SharedPresenterHolder.kt @@ -0,0 +1,25 @@ +package org.koitharu.kotatsu.ui.common + +import android.util.ArrayMap +import moxy.MvpPresenter +import java.lang.ref.WeakReference + +abstract class SharedPresenterHolder> { + + private val cache = ArrayMap>(3) + + fun getInstance(key: Int): T { + var instance = cache[key]?.get() + if (instance == null) { + instance = onCreatePresenter(key) + cache[key] = WeakReference(instance) + } + return instance + } + + fun clear(key: Int) { + cache.remove(key) + } + + protected abstract fun onCreatePresenter(key: Int): T +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersFragment.kt index d07c7fe3d..7c96faa0c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersFragment.kt @@ -25,7 +25,9 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV OnRecyclerItemClickListener, ActionMode.Callback { @Suppress("unused") - private val presenter by moxyPresenter(factory = MangaDetailsPresenter.Companion::getInstance) + private val presenter by moxyPresenter { + MangaDetailsPresenter.getInstance(activity.hashCode()) + } private var manga: Manga? = null diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt index b8bb92fcc..d0c0ddd71 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt @@ -37,7 +37,9 @@ import org.koitharu.kotatsu.utils.ext.getThemeColor class MangaDetailsActivity : BaseActivity(), MangaDetailsView, TabLayoutMediator.TabConfigurationStrategy { - private val presenter by moxyPresenter(factory = MangaDetailsPresenter.Companion::getInstance) + private val presenter by moxyPresenter { + MangaDetailsPresenter.getInstance(hashCode()) + } private var manga: Manga? = null @@ -52,7 +54,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, presenter.loadDetails(it, true) } ?: intent?.getLongExtra(EXTRA_MANGA_ID, 0)?.takeUnless { it == 0L }?.let { presenter.findMangaById(it) - } ?: finish() + } ?: finishAfterTransition() } } @@ -73,13 +75,13 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, this, getString(R.string._s_deleted_from_local_storage, manga.title), Toast.LENGTH_SHORT ).show() - finish() + finishAfterTransition() } override fun onError(e: Throwable) { if (manga == null) { Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show() - finish() + finishAfterTransition() } else { Snackbar.make(pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show() } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt index 68846901b..bf5a02181 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt @@ -29,7 +29,9 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai View.OnLongClickListener { @Suppress("unused") - private val presenter by moxyPresenter(factory = MangaDetailsPresenter.Companion::getInstance) + private val presenter by moxyPresenter { + MangaDetailsPresenter.getInstance(activity.hashCode()) + } private var manga: Manga? = null private var history: MangaHistory? = null diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt index fa7a1291c..9c234551e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.ui.details +import android.util.Log import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* @@ -21,13 +22,13 @@ import org.koitharu.kotatsu.domain.history.HistoryRepository import org.koitharu.kotatsu.domain.history.OnHistoryChangeListener import org.koitharu.kotatsu.domain.tracking.TrackingRepository import org.koitharu.kotatsu.ui.common.BasePresenter +import org.koitharu.kotatsu.ui.common.SharedPresenterHolder import org.koitharu.kotatsu.utils.ext.safe import java.io.IOException @InjectViewState -class MangaDetailsPresenter private constructor() : BasePresenter(), - OnHistoryChangeListener, - OnFavouritesChangeListener { +class MangaDetailsPresenter private constructor(private val key: Int) : + BasePresenter(), OnHistoryChangeListener, OnFavouritesChangeListener { private lateinit var historyRepository: HistoryRepository private lateinit var favouritesRepository: FavouritesRepository @@ -55,7 +56,7 @@ class MangaDetailsPresenter private constructor() : BasePresenter() { - private var instance: MangaDetailsPresenter? = null - - fun getInstance(): MangaDetailsPresenter = instance ?: synchronized(this) { - MangaDetailsPresenter().also { - instance = it - } - } + override fun onCreatePresenter(key: Int) = MangaDetailsPresenter(key) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/RelatedMangaFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/RelatedMangaFragment.kt index d74f7b6f1..1a5fddd9b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/RelatedMangaFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/RelatedMangaFragment.kt @@ -10,7 +10,9 @@ import org.koitharu.kotatsu.ui.list.MangaListFragment class RelatedMangaFragment : MangaListFragment(), MangaDetailsView { - private val presenter by moxyPresenter(factory = MangaDetailsPresenter.Companion::getInstance) + private val presenter by moxyPresenter { + MangaDetailsPresenter.getInstance(activity.hashCode()) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt index 62a387803..0d755809c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt @@ -38,11 +38,11 @@ class LocalListPresenter : BasePresenter>() { } fun loadList(offset: Int) { - if (offset != 0) { - viewState.onListAppended(emptyList()) - return - } presenterScope.launch { + if (offset != 0) { + viewState.onListAppended(emptyList()) + return@launch + } viewState.onLoadingStateChanged(true) try { val list = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt index eb7629112..2d2662ab3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt @@ -75,7 +75,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh ?: intent.getParcelableExtra(EXTRA_STATE) ?: let { Toast.makeText(this, R.string.error_occurred, Toast.LENGTH_SHORT).show() - finish() + finishAfterTransition() return } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt index 0fe84d044..d39465d54 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt @@ -19,7 +19,7 @@ class SearchActivity : BaseActivity(), SearchView.OnQueryTextListener { super.onCreate(savedInstanceState) setContentView(R.layout.activity_search) source = intent.getParcelableExtra(EXTRA_SOURCE) ?: run { - finish() + finishAfterTransition() return } val query = intent.getStringExtra(EXTRA_QUERY) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchActivity.kt index a39d9bd5b..726d4300b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchActivity.kt @@ -14,7 +14,7 @@ class GlobalSearchActivity : BaseActivity() { val query = intent.getStringExtra(EXTRA_QUERY) if (query == null) { - finish() + finishAfterTransition() return } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfConfigActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfConfigActivity.kt index a8cbb1eea..308ea678e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfConfigActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfConfigActivity.kt @@ -48,7 +48,7 @@ class ShelfConfigActivity : BaseActivity(), FavouriteCategoriesView, AppWidgetManager.INVALID_APPWIDGET_ID ) ?: AppWidgetManager.INVALID_APPWIDGET_ID if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { - finish() + finishAfterTransition() return } config = AppWidgetConfig.getInstance(this, appWidgetId) From b27bc86141fa5b47e7b3966b324e2a539fe8ba2b Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 29 Jun 2020 13:28:25 +0300 Subject: [PATCH 06/13] Fix search history preference --- .../kotatsu/ui/details/MangaDetailsPresenter.kt | 1 - .../kotatsu/ui/settings/HistorySettingsFragment.kt | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt index 9c234551e..a380926e7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.ui.details -import android.util.Log import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt index c1f436edc..92ba5f549 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt @@ -42,7 +42,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach } override fun onPreferenceTreeClick(preference: Preference): Boolean { - return when(preference.key) { + return when (preference.key) { getString(R.string.key_pages_cache_clear) -> { clearCache(preference, Cache.PAGES) true @@ -53,8 +53,13 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach } getString(R.string.key_search_history_clear) -> { MangaSuggestionsProvider.clearHistory(preference.context) - preference.context.resources.getQuantityString(R.plurals.items, 0, 0) - Snackbar.make(view ?: return true, R.string.search_history_cleared, Snackbar.LENGTH_SHORT).show() + preference.summary = preference.context.resources + .getQuantityString(R.plurals.items, 0, 0) + Snackbar.make( + view ?: return true, + R.string.search_history_cleared, + Snackbar.LENGTH_SHORT + ).show() true } else -> super.onPreferenceTreeClick(preference) From a0aa33a4997cb5df1d8b5257bc86d7e32310b3ed Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 29 Jun 2020 13:43:01 +0300 Subject: [PATCH 07/13] Option to clear updates feed --- .../koitharu/kotatsu/core/db/TrackLogsDao.kt | 3 +++ .../domain/tracking/TrackingRepository.kt | 4 ++++ .../ui/settings/HistorySettingsFragment.kt | 24 +++++++++++++++++++ app/src/main/res/values-ru/strings.xml | 2 ++ app/src/main/res/values/constants.xml | 1 + app/src/main/res/values/strings.xml | 2 ++ app/src/main/res/xml/pref_history.xml | 6 +++++ 7 files changed, 42 insertions(+) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt index 547fed576..dc4ce6e01 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/TrackLogsDao.kt @@ -22,4 +22,7 @@ interface TrackLogsDao { @Query("DELETE FROM track_logs WHERE manga_id NOT IN (SELECT manga_id FROM tracks)") suspend fun cleanup() + + @Query("SELECT COUNT(*) FROM track_logs") + suspend fun count(): Int } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt b/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt index 6b1a3bdde..60a46ebfd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt @@ -44,6 +44,10 @@ class TrackingRepository : KoinComponent { } } + suspend fun count() = db.trackLogsDao.count() + + suspend fun clearLogs() = db.trackLogsDao.clear() + suspend fun cleanup() { db.withTransaction { db.tracksDao.cleanup() diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt index 92ba5f549..435b73b23 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.local.Cache +import org.koitharu.kotatsu.domain.tracking.TrackingRepository import org.koitharu.kotatsu.ui.common.BasePreferenceFragment import org.koitharu.kotatsu.ui.search.MangaSuggestionsProvider import org.koitharu.kotatsu.utils.CacheUtils @@ -17,6 +18,10 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cache) { + private val trackerRepo by lazy { + TrackingRepository() + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_history) findPreference(R.string.key_pages_cache_clear)?.let { pref -> @@ -39,6 +44,12 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach val items = MangaSuggestionsProvider.getItemsCount(p.context) p.summary = p.context.resources.getQuantityString(R.plurals.items, items, items) } + findPreference(R.string.key_updates_feed_clear)?.let { p -> + lifecycleScope.launchWhenResumed { + val items = trackerRepo.count() + p.summary = p.context.resources.getQuantityString(R.plurals.items, items, items) + } + } } override fun onPreferenceTreeClick(preference: Preference): Boolean { @@ -62,6 +73,19 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach ).show() true } + getString(R.string.key_updates_feed_clear) -> { + lifecycleScope.launch { + trackerRepo.clearLogs() + preference.summary = preference.context.resources + .getQuantityString(R.plurals.items, 0, 0) + Snackbar.make( + view ?: return@launch, + R.string.updates_feed_cleared, + Snackbar.LENGTH_SHORT + ).show() + } + true + } else -> super.onPreferenceTreeClick(preference) } } diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1306bd976..57db050c6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -140,4 +140,6 @@ Новая версия: %s Размер: %s Ожидание подключения… + Очистить ленту обновлений + Лента обновлений очищена \ No newline at end of file diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml index 92c2d4f00..694073c15 100644 --- a/app/src/main/res/values/constants.xml +++ b/app/src/main/res/values/constants.xml @@ -9,6 +9,7 @@ pages_cache_clear thumbs_cache_clear search_history_clear + updates_feed_clear grid_size remote_sources local_storage diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 215a2316c..a67b32984 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,4 +141,6 @@ New version: %s Size: %s Waiting for network… + Clear updates feed + Updates feed cleared \ No newline at end of file diff --git a/app/src/main/res/xml/pref_history.xml b/app/src/main/res/xml/pref_history.xml index f26371f0c..c05b209c9 100644 --- a/app/src/main/res/xml/pref_history.xml +++ b/app/src/main/res/xml/pref_history.xml @@ -9,6 +9,12 @@ android:title="@string/clear_search_history" app:iconSpaceReserved="false" /> + + From c3ab197aa09354f080cae8509e1f015698f9005e Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 1 Jul 2020 19:17:08 +0300 Subject: [PATCH 08/13] Fix some StrictMode warnings --- .../java/org/koitharu/kotatsu/KotatsuApp.kt | 19 ++++++++++++--- .../core/parser/LocalMangaRepository.kt | 5 ++-- .../ui/details/MangaDetailsFragment.kt | 23 +++++++++++++------ .../koitharu/kotatsu/ui/list/MainActivity.kt | 5 +++- .../ui/search/MangaSuggestionsProvider.kt | 2 +- .../kotatsu/ui/search/SearchActivity.kt | 5 ++++ .../kotatsu/ui/search/SearchHelper.kt | 9 +++++--- .../koitharu/kotatsu/utils/ext/ParseExt.kt | 15 ++++++------ build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 10 files changed, 61 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index 749956ac7..76397ba83 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -1,10 +1,9 @@ package org.koitharu.kotatsu import android.app.Application +import android.os.StrictMode import androidx.appcompat.app.AppCompatDelegate import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.sqlite.db.SupportSQLiteDatabase import coil.Coil import coil.ComponentRegistry import coil.ImageLoaderBuilder @@ -24,6 +23,7 @@ import org.koitharu.kotatsu.core.local.PagesCache import org.koitharu.kotatsu.core.local.cookies.PersistentCookieJar import org.koitharu.kotatsu.core.local.cookies.cache.SetCookieCache import org.koitharu.kotatsu.core.local.cookies.persistence.SharedPrefsCookiePersistor +import org.koitharu.kotatsu.core.parser.LocalMangaRepository import org.koitharu.kotatsu.core.parser.UserAgentInterceptor import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.domain.MangaLoaderContext @@ -46,6 +46,19 @@ class KotatsuApp : Application() { override fun onCreate() { super.onCreate() + if (BuildConfig.DEBUG) { + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + .build()) + StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder() + .detectAll() + .setClassInstanceLimit(LocalMangaRepository::class.java, 1) + .setClassInstanceLimit(PagesCache::class.java, 1) + .setClassInstanceLimit(MangaLoaderContext::class.java, 1) + .penaltyLog() + .build()) + } initKoin() initCoil() Thread.setDefaultUncaughtExceptionHandler(AppCrashHandler(applicationContext)) @@ -75,7 +88,7 @@ class KotatsuApp : Application() { single { MangaLoaderContext() } - factory { + single { AppSettings(applicationContext) } single { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt index 725119563..bd0a54cd7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt @@ -78,8 +78,7 @@ class LocalMangaRepository : MangaRepository, KoinComponent { } @SuppressLint("DefaultLocale") - fun getFromFile(file: File): Manga { - val zip = ZipFile(file) + fun getFromFile(file: File): Manga = ZipFile(file).use { zip -> val fileUri = file.toUri().toString() val entry = zip.getEntry(MangaZip.INDEX_ENTRY) val index = entry?.let(zip::readText)?.let(::MangaIndex) @@ -105,7 +104,7 @@ class LocalMangaRepository : MangaRepository, KoinComponent { } } val uriBuilder = file.toUri().buildUpon() - return Manga( + Manga( id = file.absolutePath.longHashCode(), title = title, url = fileUri, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt index bf5a02181..fb679580d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt @@ -5,9 +5,13 @@ import android.view.View import androidx.core.net.toUri import androidx.core.text.parseAsHtml import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import coil.api.load import com.google.android.material.chip.Chip import kotlinx.android.synthetic.main.fragment_details.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import moxy.ktx.moxyPresenter import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.FavouriteCategory @@ -73,13 +77,18 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai ) } manga.url.toUri().toFileOrNull()?.let { f -> - chips_tags.addChips(listOf(f)) { - create( - text = FileSizeUtils.formatBytes(context, it.length()), - iconRes = R.drawable.ic_chip_storage, - tag = it, - onClickListener = this@MangaDetailsFragment - ) + lifecycleScope.launch { + val size = withContext(Dispatchers.IO) { + f.length() + } + chips_tags.addChips(listOf(f)) { + create( + text = FileSizeUtils.formatBytes(context, size), + iconRes = R.drawable.ic_chip_storage, + tag = it, + onClickListener = this@MangaDetailsFragment + ) + } } } imageView_favourite.setOnClickListener(this) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt index 399ef4142..06442c4df 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt @@ -37,6 +37,7 @@ import org.koitharu.kotatsu.ui.settings.SettingsActivity import org.koitharu.kotatsu.ui.tracker.TrackWorker import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.resolveDp +import java.io.Closeable class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener, SharedPreferences.OnSharedPreferenceChangeListener, MainView { @@ -45,6 +46,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList private val settings by inject() private lateinit var drawerToggle: ActionBarDrawerToggle + private var closeable: Closeable? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -74,6 +76,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } override fun onDestroy() { + closeable?.close() settings.unsubscribe(this) super.onDestroy() } @@ -92,7 +95,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.opt_main, menu) menu.findItem(R.id.action_search)?.let { menuItem -> - SearchHelper.setupSearchView(menuItem) + closeable = SearchHelper.setupSearchView(menuItem) } return super.onCreateOptionsMenu(menu) } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSuggestionsProvider.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSuggestionsProvider.kt index ffa8f0445..0a2cf5cff 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSuggestionsProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSuggestionsProvider.kt @@ -79,7 +79,7 @@ class MangaSuggestionsProvider : SearchRecentSuggestionsProvider() { } @JvmStatic - fun getItemsCount(context: Context) = getCursor(context)?.count ?: 0 + fun getItemsCount(context: Context) = getCursor(context)?.use { it.count } ?: 0 @JvmStatic private fun getCursor(context: Context): Cursor? { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt index d39465d54..2a60f89e8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt @@ -37,6 +37,11 @@ class SearchActivity : BaseActivity(), SearchView.OnQueryTextListener { } } + override fun onDestroy() { + searchView.suggestionsAdapter?.changeCursor(null) //close cursor + super.onDestroy() + } + override fun onQueryTextSubmit(query: String?): Boolean { return if (!query.isNullOrBlank()) { title = query diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchHelper.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchHelper.kt index 231804de8..7ebb87dd8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchHelper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchHelper.kt @@ -8,17 +8,20 @@ import androidx.appcompat.widget.SearchView import org.koitharu.kotatsu.R import org.koitharu.kotatsu.ui.search.global.GlobalSearchActivity import org.koitharu.kotatsu.utils.ext.safe +import java.io.Closeable object SearchHelper { @JvmStatic - fun setupSearchView(menuItem: MenuItem) { - val view = menuItem.actionView as? SearchView ?: return + fun setupSearchView(menuItem: MenuItem): Closeable? { + val view = menuItem.actionView as? SearchView ?: return null val context = view.context + val adapter = MangaSuggestionsProvider.getSuggestionAdapter(context) view.queryHint = context.getString(R.string.search_manga) - view.suggestionsAdapter = MangaSuggestionsProvider.getSuggestionAdapter(context) + view.suggestionsAdapter = adapter view.setOnQueryTextListener(QueryListener(context)) view.setOnSuggestionListener(SuggestionListener(view)) + return adapter?.cursor } private class QueryListener(private val context: Context) : diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ParseExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ParseExt.kt index 944286f59..464785d24 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ParseExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ParseExt.kt @@ -10,13 +10,14 @@ import org.jsoup.select.Elements fun Response.parseHtml(): Document { try { - val stream = body?.byteStream() ?: throw NullPointerException("Response body is null") - val charset = body!!.contentType()?.charset()?.name() - return Jsoup.parse( - stream, - charset, - request.url.toString() - ) + (body?.byteStream() ?: throw NullPointerException("Response body is null")).use { stream -> + val charset = body!!.contentType()?.charset()?.name() + return Jsoup.parse( + stream, + charset, + request.url.toString() + ) + } } finally { closeQuietly() } diff --git a/build.gradle b/build.gradle index 3219af198..d948ecf3e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0-alpha02' + classpath 'com.android.tools.build:gradle:4.2.0-alpha03' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f8233caa7..47bac7af6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Jun 20 11:05:53 EEST 2020 +#Wed Jul 01 18:26:34 EEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip From 367a97a95bbc68389c288868a4cd6c2e2dff8ca4 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 1 Jul 2020 19:37:15 +0300 Subject: [PATCH 09/13] Fix page error processing --- .../java/org/koitharu/kotatsu/ui/reader/PageLoader.kt | 5 ++++- .../java/org/koitharu/kotatsu/utils/ext/CommonExt.kt | 10 +++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt index 663ab050e..b22312f54 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt @@ -30,7 +30,7 @@ class PageLoader : KoinComponent, CoroutineScope, DisposableHandle { return it } } - val task = tasks[url]?.takeUnless { it.isCancelled } + val task = tasks[url]?.takeUnless { it.isCancelled || (force && it.isCompleted) } return (task ?: loadAsync(url).also { tasks[url] = it }).await() } @@ -55,6 +55,9 @@ class PageLoader : KoinComponent, CoroutineScope, DisposableHandle { checkNotNull(body) { "Null response" } + check(response.isSuccessful) { + "Invalid response: ${response.code} ${response.message}" + } cache.put(url) { out -> body.byteStream().copyTo(out) } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt index 0c6e2b28d..9e8ae8a2a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException -import java.io.IOException +import java.net.SocketTimeoutException inline fun T.safe(action: T.() -> R?) = try { this.action() @@ -38,12 +38,8 @@ fun Throwable.getDisplayMessage(resources: Resources) = when (this) { is UnsupportedOperationException -> resources.getString(R.string.operation_not_supported) is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported) is EmptyHistoryException -> resources.getString(R.string.history_is_empty) - is IOException -> resources.getString(R.string.network_error) - else -> if (BuildConfig.DEBUG) { - message ?: resources.getString(R.string.error_occurred) - } else { - resources.getString(R.string.error_occurred) - } + is SocketTimeoutException -> resources.getString(R.string.network_error) + else -> message ?: resources.getString(R.string.error_occurred) } inline fun measured(tag: String, block: () -> T): T { From 9762a466ce9d6a7662932cf94fc6a385fdf9ac0f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 5 Jul 2020 16:45:34 +0300 Subject: [PATCH 10/13] Fix Mangalib pages loading --- .../kotatsu/core/parser/LocalMangaRepository.kt | 3 ++- .../kotatsu/domain/favourites/FavouritesRepository.kt | 4 ++-- .../kotatsu/domain/history/HistoryRepository.kt | 3 ++- .../java/org/koitharu/kotatsu/ui/reader/PageLoader.kt | 10 ++++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt index bd0a54cd7..0f944e655 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.parser import android.annotation.SuppressLint import android.content.Context import android.net.Uri +import androidx.collection.ArraySet import androidx.core.net.toFile import androidx.core.net.toUri import org.koin.core.KoinComponent @@ -97,7 +98,7 @@ class LocalMangaRepository : MangaRepository, KoinComponent { } // fallback val title = file.nameWithoutExtension.replace("_", " ").capitalize() - val chapters = HashSet() + val chapters = ArraySet() for (x in zip.entries()) { if (!x.isDirectory) { chapters += x.name.substringBeforeLast(File.separatorChar, "") diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/favourites/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/domain/favourites/FavouritesRepository.kt index fcbba0119..70dbf46c9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/favourites/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/domain/favourites/FavouritesRepository.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.domain.favourites +import androidx.collection.ArraySet import androidx.room.withTransaction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -12,7 +13,6 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.Manga -import java.util.* class FavouritesRepository : KoinComponent { @@ -98,7 +98,7 @@ class FavouritesRepository : KoinComponent { companion object { - private val listeners = HashSet() + private val listeners = ArraySet() fun subscribe(listener: OnFavouritesChangeListener) { listeners += listener diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/history/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/domain/history/HistoryRepository.kt index c22db90fb..b64cdbd81 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/history/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/domain/history/HistoryRepository.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.domain.history +import androidx.collection.ArraySet import androidx.room.withTransaction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -71,7 +72,7 @@ class HistoryRepository : KoinComponent { companion object { - private val listeners = HashSet() + private val listeners = ArraySet() fun subscribe(listener: OnHistoryChangeListener) { listeners += listener diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt index b22312f54..de38b44f9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.ui.reader import android.net.Uri +import android.util.ArrayMap import kotlinx.coroutines.* import okhttp3.OkHttpClient import okhttp3.Request @@ -16,7 +17,7 @@ import kotlin.coroutines.CoroutineContext class PageLoader : KoinComponent, CoroutineScope, DisposableHandle { private val job = SupervisorJob() - private val tasks = HashMap>() + private val tasks = ArrayMap>() private val okHttp by inject() private val cache by inject() @@ -48,16 +49,17 @@ class PageLoader : KoinComponent, CoroutineScope, DisposableHandle { val request = Request.Builder() .url(url) .get() + .header("Accept", "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8") .cacheControl(CacheUtils.CONTROL_DISABLED) .build() okHttp.newCall(request).await().use { response -> val body = response.body - checkNotNull(body) { - "Null response" - } check(response.isSuccessful) { "Invalid response: ${response.code} ${response.message}" } + checkNotNull(body) { + "Null response" + } cache.put(url) { out -> body.byteStream().copyTo(out) } From 28618e394e5da8c252bd47cc454fbe5bc35980ba Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 5 Jul 2020 16:59:20 +0300 Subject: [PATCH 11/13] Fix *chan search --- .../koitharu/kotatsu/core/parser/site/ChanRepository.kt | 7 ++++++- .../java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt index f4e8d3c21..4a37b0b73 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt @@ -23,7 +23,12 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe ): List { val domain = conf.getDomain(defaultDomain) val url = when { - query != null -> "https://$domain/?do=search&subaction=search&story=${query.urlEncoded()}" + query != null -> { + if (offset != 0) { + return emptyList() + } + "https://$domain/?do=search&subaction=search&story=${query.urlEncoded()}" + } tag != null -> "https://$domain/tags/${tag.key}&n=${getSortKey2(sortOrder)}?offset=$offset" else -> "https://$domain/${getSortKey(sortOrder)}?offset=$offset" } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt index d12a28d9f..c68abd82c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt @@ -126,6 +126,7 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), } final override fun onRefresh() { + swipeRefreshLayout.isRefreshing = true onRequestMoreItems(0) } @@ -188,10 +189,11 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), override fun onLoadingStateChanged(isLoading: Boolean) { val hasItems = recyclerView.hasItems progressBar.isVisible = isLoading && !hasItems - swipeRefreshLayout.isRefreshing = isLoading && hasItems swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled && !progressBar.isVisible if (isLoading) { layout_holder.isVisible = false + } else { + swipeRefreshLayout.isRefreshing = false } } From bf4548036673f75c3c61b18d93150221cdbf02ca Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 5 Jul 2020 17:01:43 +0300 Subject: [PATCH 12/13] Update henchan default domain --- .../org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt index 2f2d4787a..f29282b67 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt @@ -12,7 +12,7 @@ import org.koitharu.kotatsu.utils.ext.withDomain class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) { - override val defaultDomain = "h-chan.me" + override val defaultDomain = "henchan.pro" override val source = MangaSource.HENCHAN override suspend fun getDetails(manga: Manga): Manga { From 66ca51cc73809181b159a99696d985b519557508 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 5 Jul 2020 17:04:24 +0300 Subject: [PATCH 13/13] Fix MangaTown endless search --- app/build.gradle | 2 +- .../koitharu/kotatsu/core/parser/site/ChanRepository.kt | 2 +- .../kotatsu/core/parser/site/MangaTownRepository.kt | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6d1d97bc2..f15df6bdb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,7 @@ android { minSdkVersion 21 targetSdkVersion 29 versionCode gitCommits - versionName '0.5-b2' + versionName '0.5-rc1' kapt { arguments { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt index 4a37b0b73..14c7cd15d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt @@ -23,7 +23,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe ): List { val domain = conf.getDomain(defaultDomain) val url = when { - query != null -> { + !query.isNullOrEmpty() -> { if (offset != 0) { return emptyList() } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt index c9f1e0c2e..5493939a2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt @@ -37,7 +37,12 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposi } val page = (offset / 30) + 1 val url = when { - !query.isNullOrEmpty() -> "$scheme://$domain/search?name=${query.urlEncoded()}" + !query.isNullOrEmpty() -> { + if (offset != 0) { + return emptyList() + } + "$scheme://$domain/search?name=${query.urlEncoded()}" + } tag != null -> "$scheme://$domain/directory/${tag.key}/$page.htm$sortKey" else -> "$scheme://$domain/directory/$page.htm$sortKey" }