From 6881c224538b6cdac4f3470a97d45916569289cf Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 27 May 2023 17:38:32 +0300 Subject: [PATCH 01/21] Update parsers --- app/build.gradle | 12 ++++++------ .../org/koitharu/kotatsu/core/parser/DummyParser.kt | 2 +- .../koitharu/kotatsu/settings/SourceSettingsExt.kt | 10 ++++++++-- .../org/koitharu/kotatsu/core/parser/DummyParser.kt | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 24a9609d1..11d1f3d2e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdkVersion 21 targetSdkVersion 33 - versionCode 546 - versionName '5.1.2' + versionCode 547 + versionName '5.1.3' generatedDensities = [] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -78,7 +78,7 @@ afterEvaluate { } dependencies { //noinspection GradleDependency - implementation('com.github.KotatsuApp:kotatsu-parsers:ebcc6391d6') { + implementation('com.github.KotatsuApp:kotatsu-parsers:fa7ea5b16a') { exclude group: 'org.json', module: 'json' } @@ -87,7 +87,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.core:core-ktx:1.10.1' - implementation 'androidx.activity:activity-ktx:1.7.1' + implementation 'androidx.activity:activity-ktx:1.7.2' implementation 'androidx.fragment:fragment-ktx:1.5.7' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' @@ -96,7 +96,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.3.0' - implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' + implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02' implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05' implementation 'com.google.android.material:material:1.9.0' @@ -136,7 +136,7 @@ dependencies { implementation 'ch.acra:acra-http:5.9.7' implementation 'ch.acra:acra-dialog:5.9.7' - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11' testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20230227' diff --git a/app/src/debug/java/org/koitharu/kotatsu/core/parser/DummyParser.kt b/app/src/debug/java/org/koitharu/kotatsu/core/parser/DummyParser.kt index a60655a2a..fb96f6e64 100644 --- a/app/src/debug/java/org/koitharu/kotatsu/core/parser/DummyParser.kt +++ b/app/src/debug/java/org/koitharu/kotatsu/core/parser/DummyParser.kt @@ -17,7 +17,7 @@ import java.util.EnumSet class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.DUMMY) { override val configKeyDomain: ConfigKey.Domain - get() = ConfigKey.Domain("", null) + get() = ConfigKey.Domain() override val sortOrders: Set get() = EnumSet.allOf(SortOrder::class.java) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsExt.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsExt.kt index 293f4847d..6a83af617 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsExt.kt @@ -19,10 +19,12 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang val preference: Preference = when (key) { is ConfigKey.Domain -> { val presetValues = key.presetValues - if (presetValues.isNullOrEmpty()) { + if (presetValues.size <= 1) { EditTextPreference(requireContext()) } else { - AutoCompleteTextViewPreference(requireContext()).apply { entries = presetValues } + AutoCompleteTextViewPreference(requireContext()).apply { + entries = presetValues.toStringArray() + } }.apply { summaryProvider = EditTextDefaultSummaryProvider(key.defaultValue) setOnBindEditTextListener( @@ -64,3 +66,7 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang screen.addPreference(preference) } } + +private fun Array.toStringArray(): Array { + return Array(size) { i -> this[i] as? String ?: "" } +} diff --git a/app/src/release/java/org/koitharu/kotatsu/core/parser/DummyParser.kt b/app/src/release/java/org/koitharu/kotatsu/core/parser/DummyParser.kt index 12722988c..030cb28de 100644 --- a/app/src/release/java/org/koitharu/kotatsu/core/parser/DummyParser.kt +++ b/app/src/release/java/org/koitharu/kotatsu/core/parser/DummyParser.kt @@ -15,7 +15,7 @@ import java.util.EnumSet class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.DUMMY) { override val configKeyDomain: ConfigKey.Domain - get() = ConfigKey.Domain("localhost", null) + get() = ConfigKey.Domain("localhost") override val sortOrders: Set get() = EnumSet.allOf(SortOrder::class.java) From 2442e7cbe14fe3d6975d84454a3fe2de46614843 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 29 May 2023 13:06:50 +0300 Subject: [PATCH 02/21] Fix warnings --- app/build.gradle | 10 ++--- .../core/parser/RemoteMangaRepository.kt | 2 +- .../core/parser/favicon/FaviconFetcher.kt | 6 +-- .../kotatsu/core/ui/BaseFullscreenActivity.kt | 38 ++++--------------- .../org/koitharu/kotatsu/core/util/Event.kt | 2 +- .../koitharu/kotatsu/core/util/ext/Android.kt | 2 + .../kotatsu/core/util/ext/FlowObserver.kt | 4 -- .../kotatsu/explore/ui/ExploreFragment.kt | 2 +- .../kotatsu/explore/ui/model/ExploreItem.kt | 13 ++----- .../local/data/input/LocalMangaDirInput.kt | 1 - .../koitharu/kotatsu/main/ui/MainActivity.kt | 2 +- .../kotatsu/main/ui/MainNavigationDelegate.kt | 3 +- .../ui/config/ScrobblerConfigActivity.kt | 2 +- .../settings/protect/ProtectSetupActivity.kt | 7 ++-- .../kotatsu/sync/ui/SyncAuthActivity.kt | 2 +- .../kotatsu/tracker/ui/feed/FeedFragment.kt | 2 +- build.gradle | 2 +- 17 files changed, 34 insertions(+), 66 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9ee4d3fd9..5e3d86120 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -106,7 +106,7 @@ dependencies { implementation 'androidx.work:work-runtime-ktx:2.8.1' //noinspection GradleDependency - implementation('com.google.guava:guava:31.1-android') { + implementation('com.google.guava:guava:32.0.0-android') { exclude group: 'com.google.guava', module: 'failureaccess' exclude group: 'org.checkerframework', module: 'checker-qual' exclude group: 'com.google.j2objc', module: 'j2objc-annotations' @@ -116,8 +116,8 @@ dependencies { implementation 'androidx.room:room-ktx:2.5.1' kapt 'androidx.room:room-compiler:2.5.1' - implementation 'com.squareup.okhttp3:okhttp:4.10.0' - implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3' + implementation 'com.squareup.okhttp3:okhttp:4.11.0' + implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.11.0' implementation 'com.squareup.okio:okio:3.3.0' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2' @@ -128,8 +128,8 @@ dependencies { implementation 'androidx.hilt:hilt-work:1.0.0' kapt 'androidx.hilt:hilt-compiler:1.0.0' - implementation 'io.coil-kt:coil-base:2.3.0' - implementation 'io.coil-kt:coil-svg:2.3.0' + implementation 'io.coil-kt:coil-base:2.4.0' + implementation 'io.coil-kt:coil-svg:2.4.0' implementation 'com.github.KotatsuApp:subsampling-scale-image-view:1b19231b2f' implementation 'com.github.solkin:disk-lru-cache:1.4' implementation 'io.noties.markwon:core:4.6.2' diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index 0f3d6d482..65758fd99 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -101,7 +101,7 @@ class RemoteMangaRepository( } fun getAvailableMirrors(): List { - return parser.configKeyDomain.presetValues?.toList().orEmpty() + return parser.configKeyDomain.presetValues.toList() } private fun getConfig() = parser.config as SourceSettings diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt index be0d0dcfc..6af4218ae 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt @@ -86,7 +86,7 @@ class FaviconFetcher( if (!options.diskCachePolicy.readEnabled) { return null } - val snapshot = diskCache.value?.get(diskCacheKey) ?: return null + val snapshot = diskCache.value?.openSnapshot(diskCacheKey) ?: return null return SourceResult( source = snapshot.toImageSource(), mimeType = null, @@ -98,12 +98,12 @@ class FaviconFetcher( if (!options.diskCachePolicy.writeEnabled || body.contentLength() == 0L) { return null } - val editor = diskCache.value?.edit(diskCacheKey) ?: return null + val editor = diskCache.value?.openEditor(diskCacheKey) ?: return null try { fileSystem.write(editor.data) { body.source().readAll(this) } - return editor.commitAndGet() + return editor.commitAndOpenSnapshot() } catch (e: Throwable) { try { editor.abort() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt index 96a240e55..27f2b5fb2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt @@ -3,57 +3,35 @@ package org.koitharu.kotatsu.core.ui import android.graphics.Color import android.os.Build import android.os.Bundle -import android.view.View import android.view.WindowManager +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import androidx.viewbinding.ViewBinding -@Suppress("DEPRECATION") -private const val SYSTEM_UI_FLAGS_SHOWN = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - -@Suppress("DEPRECATION") -private const val SYSTEM_UI_FLAGS_HIDDEN = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_FULLSCREEN or - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - abstract class BaseFullscreenActivity : - BaseActivity(), - View.OnSystemUiVisibilityChangeListener { + BaseActivity() { + + private lateinit var insetsControllerCompat: WindowInsetsControllerCompat override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) with(window) { + insetsControllerCompat = WindowInsetsControllerCompat(this, decorView) statusBarColor = Color.TRANSPARENT navigationBarColor = Color.TRANSPARENT if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } - decorView.setOnSystemUiVisibilityChangeListener(this@BaseFullscreenActivity) } showSystemUI() } - @Suppress("DEPRECATION", "DeprecatedCallableAddReplaceWith") - @Deprecated("Deprecated in Java") - final override fun onSystemUiVisibilityChange(visibility: Int) { - onSystemUiVisibilityChanged(visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) - } - - // TODO WindowInsetsControllerCompat works incorrect - @Suppress("DEPRECATION") protected fun hideSystemUI() { - window.decorView.systemUiVisibility = SYSTEM_UI_FLAGS_HIDDEN + insetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars()) } - @Suppress("DEPRECATION") protected fun showSystemUI() { - window.decorView.systemUiVisibility = SYSTEM_UI_FLAGS_SHOWN + insetsControllerCompat.show(WindowInsetsCompat.Type.systemBars()) } - - protected open fun onSystemUiVisibilityChanged(isVisible: Boolean) = Unit } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt index e14d2703c..1fd768af1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt @@ -8,7 +8,7 @@ class Event( private var isConsumed = false suspend fun consume(collector: FlowCollector) { - if (isConsumed) { + if (!isConsumed) { collector.emit(data) isConsumed = true } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt index 02c0f3ba0..9f5941449 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt @@ -23,6 +23,8 @@ import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.annotation.IntegerRes import androidx.core.app.ActivityOptionsCompat +import androidx.core.content.IntentCompat +import androidx.core.content.PackageManagerCompat import androidx.core.os.LocaleListCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt index bfd2db25f..e13f221e1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt @@ -9,13 +9,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.util.Event fun Flow.observe(owner: LifecycleOwner, collector: FlowCollector) { - if (BuildConfig.DEBUG) { - require((this as? StateFlow)?.value !is Event<*>) - } val start = if (this is StateFlow) CoroutineStart.UNDISPATCHED else CoroutineStart.DEFAULT owner.lifecycleScope.launch(start = start) { owner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index 69b6c9d5c..bf58733d5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -80,7 +80,7 @@ class ExploreFragment : viewModel.onOpenManga.observeEvent(viewLifecycleOwner, ::onOpenManga) viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView)) viewModel.isGrid.observe(viewLifecycleOwner, ::onGridModeChanged) - viewModel.onShowSuggestionsTip.observe(viewLifecycleOwner) { + viewModel.onShowSuggestionsTip.observeEvent(viewLifecycleOwner) { showSuggestionsTip() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt index e9d4603f3..9449ee21a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreItem.kt @@ -18,9 +18,7 @@ sealed interface ExploreItem : ListModel { other as Buttons - if (isSuggestionsEnabled != other.isSuggestionsEnabled) return false - - return true + return isSuggestionsEnabled == other.isSuggestionsEnabled } override fun hashCode(): Int { @@ -40,9 +38,7 @@ sealed interface ExploreItem : ListModel { other as Header if (titleResId != other.titleResId) return false - if (isButtonVisible != other.isButtonVisible) return false - - return true + return isButtonVisible == other.isButtonVisible } override fun hashCode(): Int { @@ -64,9 +60,7 @@ sealed interface ExploreItem : ListModel { other as Source if (source != other.source) return false - if (isGrid != other.isGrid) return false - - return true + return isGrid == other.isGrid } override fun hashCode(): Int { @@ -76,7 +70,6 @@ sealed interface ExploreItem : ListModel { } } - @Deprecated("") class EmptyHint( @DrawableRes icon: Int, @StringRes textPrimary: Int, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaDirInput.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaDirInput.kt index e62943d07..c2cac2c60 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaDirInput.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaDirInput.kt @@ -130,7 +130,6 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) { } val cbz = root.listFilesRecursive(CbzFilter()).firstOrNull() ?: return null return ZipFile(cbz).use { zip -> - val filter = ImageFileFilter() zip.entries().asSequence() .firstOrNull { x -> !x.isDirectory && filter.accept(x) } ?.let { entry -> zipUri(cbz, entry.name) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt index 024cb6994..731aa12de 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -129,7 +129,7 @@ class MainActivity : navigationDelegate = MainNavigationDelegate(checkNotNull(bottomNav ?: viewBinding.navRail), supportFragmentManager) navigationDelegate.addOnFragmentChangedListener(this) - navigationDelegate.onCreate(savedInstanceState) + navigationDelegate.onCreate() onBackPressedDispatcher.addCallback(ExitCallback(this, viewBinding.container)) onBackPressedDispatcher.addCallback(navigationDelegate) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt index fe23e3cc2..adfec639a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.main.ui -import android.os.Bundle import android.view.MenuItem import androidx.activity.OnBackPressedCallback import androidx.annotation.IdRes @@ -57,7 +56,7 @@ class MainNavigationDelegate( navBar.selectedItemId = R.id.nav_shelf } - fun onCreate(savedInstanceState: Bundle?) { + fun onCreate() { primaryFragment?.let { onFragmentChanged(it, fromUser = false) val itemId = getItemId(it) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt index f1a4fce2b..ac1784635 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt @@ -67,7 +67,7 @@ class ScrobblerConfigActivity : BaseActivity(), viewModel.user.observe(this, this::onUserChanged) viewModel.isLoading.observe(this, this::onLoadingStateChanged) viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null)) - viewModel.onLoggedOut.observe(this) { + viewModel.onLoggedOut.observeEvent(this) { finishAfterTransition() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt index 88a1f64e4..cc086c8cd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt @@ -19,6 +19,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.util.ext.observe +import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding private const val MIN_PASSWORD_LENGTH = 4 @@ -46,13 +47,13 @@ class ProtectSetupActivity : viewBinding.switchBiometric.setOnCheckedChangeListener(this) viewModel.isSecondStep.observe(this, this::onStepChanged) - viewModel.onPasswordSet.observe(this) { + viewModel.onPasswordSet.observeEvent(this) { finishAfterTransition() } - viewModel.onPasswordMismatch.observe(this) { + viewModel.onPasswordMismatch.observeEvent(this) { viewBinding.editPassword.error = getString(R.string.passwords_mismatch) } - viewModel.onClearText.observe(this) { + viewModel.onClearText.observeEvent(this) { viewBinding.editPassword.text?.clear() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt index f26b3dd2d..a7cd65491 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt @@ -57,7 +57,7 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi viewModel.onTokenObtained.observeEvent(this, ::onTokenReceived) viewModel.onError.observeEvent(this, ::onError) viewModel.isLoading.observe(this, ::onLoadingStateChanged) - viewModel.onAccountAlreadyExists.observe(this) { + viewModel.onAccountAlreadyExists.observeEvent(this) { onAccountAlreadyExists() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt index 2e9cfd162..588e22677 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt @@ -79,7 +79,7 @@ class FeedFragment : viewModel.content.observe(viewLifecycleOwner, this::onListChanged) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) - viewModel.onFeedCleared.observe(viewLifecycleOwner) { + viewModel.onFeedCleared.observeEvent(viewLifecycleOwner) { onFeedCleared() } TrackWorker.observeIsRunning(binding.root.context.applicationContext) diff --git a/build.gradle b/build.gradle index c420893c0..bd956dc1e 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.0.1' + classpath 'com.android.tools.build:gradle:8.0.2' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21' classpath 'com.google.dagger:hilt-android-gradle-plugin:2.46.1' } From 3d05541f61d64527de3158e46709c3a1b008ab9b Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 30 May 2023 08:49:21 +0300 Subject: [PATCH 03/21] Update reader activity ui --- .../kotatsu/core/ui/BaseFullscreenActivity.kt | 1 + .../list/fastscroll/FastScrollRecyclerView.kt | 9 ++- .../kotatsu/reader/ui/ReaderActivity.kt | 22 ++++--- .../kotatsu/reader/ui/ReaderViewModel.kt | 1 + .../ui/config/ReaderConfigBottomSheet.kt | 3 +- .../kotatsu/reader/ui/pager/ReaderUiState.kt | 10 ++++ .../ui/thumbnails/PagesThumbnailsSheet.kt | 1 + .../adapter/PageThumbnailAdapter.kt | 15 ++++- .../tracker/ui/feed/adapter/FeedAdapter.kt | 15 ++++- .../res/layout-w600dp/activity_reader.xml | 44 +++++++------- .../res/layout-w600dp/fragment_details.xml | 1 + app/src/main/res/layout/activity_browser.xml | 3 +- app/src/main/res/layout/activity_reader.xml | 58 +++++++++---------- app/src/main/res/layout/fragment_details.xml | 1 + app/src/main/res/layout/item_download.xml | 1 - app/src/main/res/layout/sheet_pages.xml | 26 +++++---- app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 8 +++ app/src/main/res/values/themes.xml | 2 + 19 files changed, 142 insertions(+), 80 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt index 27f2b5fb2..9a7ccbd2e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt @@ -24,6 +24,7 @@ abstract class BaseFullscreenActivity : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } } + insetsControllerCompat.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE showSystemUI() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt index 2b62a6d49..1a1590019 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.AttributeSet import android.view.ViewGroup import androidx.annotation.AttrRes +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import org.koitharu.kotatsu.R @@ -15,6 +16,12 @@ class FastScrollRecyclerView @JvmOverloads constructor( val fastScroller = FastScroller(context, attrs) + var isFastScrollerEnabled: Boolean = true + set(value) { + field = value + fastScroller.isVisible = value && isVisible + } + init { fastScroller.id = R.id.fast_scroller fastScroller.layoutParams = ViewGroup.LayoutParams( @@ -30,7 +37,7 @@ class FastScrollRecyclerView @JvmOverloads constructor( override fun setVisibility(visibility: Int) { super.setVisibility(visibility) - fastScroller.visibility = visibility + fastScroller.visibility = if (isFastScrollerEnabled) visibility else GONE } override fun onAttachedToWindow() { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index f3f655ede..0b8ce9908 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -14,6 +14,7 @@ import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View +import android.view.ViewGroup.MarginLayoutParams import android.view.WindowManager import androidx.activity.viewModels import androidx.core.graphics.Insets @@ -21,6 +22,7 @@ import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.WindowInsetsCompat import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar @@ -333,11 +335,11 @@ class ReaderActivity : right = systemBars.right, left = systemBars.left, ) - viewBinding.appbarBottom?.updatePadding( - bottom = systemBars.bottom, - right = systemBars.right, - left = systemBars.left, - ) + viewBinding.appbarBottom?.updateLayoutParams { + bottomMargin = systemBars.bottom + topMargin + rightMargin = systemBars.right + topMargin + leftMargin = systemBars.left + topMargin + } return WindowInsetsCompat.Builder(insets) .setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE) .build() @@ -373,19 +375,15 @@ class ReaderActivity : } private fun onUiStateChanged(pair: Pair) { - val (uiState: ReaderUiState?, previous: ReaderUiState?) = pair - title = uiState?.chapterName ?: uiState?.mangaName ?: getString(R.string.loading_) + val (previous: ReaderUiState?, uiState: ReaderUiState?) = pair + title = uiState?.resolveTitle(this) ?: getString(R.string.loading_) viewBinding.infoBar.update(uiState) if (uiState == null) { supportActionBar?.subtitle = null viewBinding.slider.isVisible = false return } - supportActionBar?.subtitle = if (uiState.chapterNumber in 1..uiState.chaptersTotal) { - getString(R.string.chapter_d_of_d, uiState.chapterNumber, uiState.chaptersTotal) - } else { - null - } + supportActionBar?.subtitle = uiState.chapterName if (previous?.chapterName != null && uiState.chapterName != previous.chapterName) { if (!uiState.chapterName.isNullOrEmpty()) { viewBinding.toastView.showTemporary(uiState.chapterName, TOAST_DURATION) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index f32dbbbb6..0ccbac877 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -366,6 +366,7 @@ class ReaderViewModel @Inject constructor( val chapter = state?.chapterId?.let { chaptersLoader.peekChapter(it) } val newState = ReaderUiState( mangaName = manga?.any?.title, + branch = chapter?.branch, chapterName = chapter?.name, chapterNumber = chapter?.number ?: 0, chaptersTotal = manga?.any?.getChapters(chapter?.branch)?.size ?: 0, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigBottomSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigBottomSheet.kt index 4fa2db3f8..3994526dd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigBottomSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigBottomSheet.kt @@ -41,7 +41,8 @@ class ReaderConfigBottomSheet : ActivityResultCallback, View.OnClickListener, MaterialButtonToggleGroup.OnButtonCheckedListener, - Slider.OnChangeListener, CompoundButton.OnCheckedChangeListener { + Slider.OnChangeListener, + CompoundButton.OnCheckedChangeListener { private val viewModel by activityViewModels() private val savePageRequest = registerForActivityResult(PageSaveContract(), this) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt index e97192ecf..650c4439a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt @@ -1,7 +1,11 @@ package org.koitharu.kotatsu.reader.ui.pager +import android.content.Context +import org.koitharu.kotatsu.R + data class ReaderUiState( val mangaName: String?, + val branch: String?, val chapterName: String?, val chapterNumber: Int, val chaptersTotal: Int, @@ -14,4 +18,10 @@ data class ReaderUiState( fun isSliderAvailable(): Boolean { return isSliderEnabled && totalPages > 1 && currentPage < totalPages } + + fun resolveTitle(context: Context): String? = when { + mangaName == null -> null + branch == null -> mangaName + else -> context.getString(R.string.manga_branch_title_template, mangaName, branch) + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt index 39255497c..72293932c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt @@ -124,6 +124,7 @@ class PagesThumbnailsSheet : } else { headerBar.subtitle = null } + viewBinding?.recyclerView?.isFastScrollerEnabled = isExpanded } private inner class ScrollListener : BoundsScrollListener(3, 3) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt index e1169638a..1b197fdb6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt @@ -1,10 +1,12 @@ package org.koitharu.kotatsu.reader.ui.thumbnails.adapter +import android.content.Context import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD import org.koitharu.kotatsu.list.ui.model.ListHeader @@ -16,7 +18,7 @@ class PageThumbnailAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, clickListener: OnListItemClickListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(DiffCallback()), FastScroller.SectionIndexer { init { delegatesManager.addDelegate(ITEM_TYPE_THUMBNAIL, pageThumbnailAD(coil, lifecycleOwner, clickListener)) @@ -24,6 +26,17 @@ class PageThumbnailAdapter( .addDelegate(ITEM_LOADING, loadingFooterAD()) } + override fun getSectionText(context: Context, position: Int): CharSequence? { + val list = items + for (i in (0..position).reversed()) { + val item = list.getOrNull(i) ?: continue + if (item is ListHeader) { + return item.getText(context) + } + } + return null + } + private class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt index 64963e946..063e17d8c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt @@ -1,9 +1,11 @@ package org.koitharu.kotatsu.tracker.ui.feed.adapter +import android.content.Context import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.core.ui.model.DateTimeAgo import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD @@ -21,7 +23,7 @@ class FeedAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, listener: MangaListListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(DiffCallback()), FastScroller.SectionIndexer { init { delegatesManager @@ -34,6 +36,17 @@ class FeedAdapter( .addDelegate(ITEM_TYPE_DATE_HEADER, relatedDateItemAD()) } + override fun getSectionText(context: Context, position: Int): CharSequence? { + val list = items + for (i in (0..position).reversed()) { + val item = list.getOrNull(i) ?: continue + if (item is DateTimeAgo) { + return item.format(context.resources) + } + } + return null + } + private class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel) = when { diff --git a/app/src/main/res/layout-w600dp/activity_reader.xml b/app/src/main/res/layout-w600dp/activity_reader.xml index 051bfefe5..50b3da7ee 100644 --- a/app/src/main/res/layout-w600dp/activity_reader.xml +++ b/app/src/main/res/layout-w600dp/activity_reader.xml @@ -1,5 +1,5 @@ - - - + android:elevation="@dimen/m3_card_elevated_elevation" + app:elevation="@dimen/m3_card_elevated_elevation" + app:liftOnScroll="false"> + android:layout_weight="1" /> + app:menu="@menu/opt_reader_bottom"> + + - + diff --git a/app/src/main/res/layout-w600dp/fragment_details.xml b/app/src/main/res/layout-w600dp/fragment_details.xml index 81e1c7403..bad58eb53 100644 --- a/app/src/main/res/layout-w600dp/fragment_details.xml +++ b/app/src/main/res/layout-w600dp/fragment_details.xml @@ -206,6 +206,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:showAnimationBehavior="inward" + app:trackCornerRadius="0dp" tools:visibility="visible" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_reader.xml b/app/src/main/res/layout/activity_reader.xml index 06ad562fe..8c97641da 100644 --- a/app/src/main/res/layout/activity_reader.xml +++ b/app/src/main/res/layout/activity_reader.xml @@ -1,5 +1,5 @@ - - - + android:elevation="@dimen/m3_card_elevated_elevation" + app:elevation="@dimen/m3_card_elevated_elevation" + app:liftOnScroll="false"> - + android:layout_margin="8dp" + app:layout_insetEdge="bottom"> + app:menu="@menu/opt_reader_bottom"> - + + + + android:indeterminate="true" + app:trackCornerRadius="@dimen/mtrl_progress_indicator_full_rounded_corner_radius" /> - + diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 4af56cca4..ff371e036 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -218,6 +218,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:showAnimationBehavior="inward" + app:trackCornerRadius="0dp" tools:visibility="visible" /> - + android:layout_height="match_parent"> + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aee909a83..2c3977217 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -424,4 +424,5 @@ Port Proxy Invalid value + %1$s (%2$s) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 098dd4768..452574345 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -76,6 +76,14 @@ 18sp + + + + + + From da4aedca979c54f0b42548b72f705984c4ab7fb1 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 3 Jun 2023 16:54:04 +0300 Subject: [PATCH 16/21] Fix proxy authentication --- .../core/network/ProxyAuthenticator.kt | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/ProxyAuthenticator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/ProxyAuthenticator.kt index bc1fb8d53..fb4ffad7e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/ProxyAuthenticator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/ProxyAuthenticator.kt @@ -6,12 +6,24 @@ import okhttp3.Request import okhttp3.Response import okhttp3.Route import org.koitharu.kotatsu.core.prefs.AppSettings +import java.net.PasswordAuthentication +import java.net.Proxy class ProxyAuthenticator( private val settings: AppSettings, -) : Authenticator { +) : Authenticator, java.net.Authenticator() { + + init { + setDefault(this) + } override fun authenticate(route: Route?, response: Response): Request? { + if (!isProxyEnabled()) { + return null + } + if (response.request.header(CommonHeaders.PROXY_AUTHORIZATION) != null) { + return null + } val login = settings.proxyLogin ?: return null val password = settings.proxyPassword ?: return null val credential = Credentials.basic(login, password) @@ -19,4 +31,15 @@ class ProxyAuthenticator( .header(CommonHeaders.PROXY_AUTHORIZATION, credential) .build() } + + override fun getPasswordAuthentication(): PasswordAuthentication? { + if (!isProxyEnabled()) { + return null + } + val login = settings.proxyLogin ?: return null + val password = settings.proxyPassword ?: return null + return PasswordAuthentication(login, password.toCharArray()) + } + + private fun isProxyEnabled() = settings.proxyType != Proxy.Type.DIRECT } From b48c6d7d3888175ad22fb0b575055a13c2aacc94 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 4 Jun 2023 15:42:15 +0300 Subject: [PATCH 17/21] Fix pages bottom sheet scroll --- .../core/ui/list/BoundsScrollListener.kt | 13 +++++- .../ScrollListenerInvalidationObserver.kt | 30 ------------- .../kotatsu/core/util/CompositeRunnable.kt | 12 ++++++ .../koitharu/kotatsu/core/util/ext/Other.kt | 14 ++++++ .../ui/thumbnails/PagesThumbnailsSheet.kt | 43 +++++++++++++++---- .../ui/thumbnails/PagesThumbnailsViewModel.kt | 14 +++++- app/src/main/res/layout/sheet_pages.xml | 2 +- 7 files changed, 84 insertions(+), 44 deletions(-) delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ScrollListenerInvalidationObserver.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeRunnable.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BoundsScrollListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BoundsScrollListener.kt index f9d41fec8..9aa7cdf93 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BoundsScrollListener.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BoundsScrollListener.kt @@ -3,8 +3,10 @@ package org.koitharu.kotatsu.core.ui.list import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -abstract class BoundsScrollListener(private val offsetTop: Int, private val offsetBottom: Int) : - RecyclerView.OnScrollListener() { +abstract class BoundsScrollListener( + @JvmField protected val offsetTop: Int, + @JvmField protected val offsetBottom: Int +) : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) @@ -24,9 +26,16 @@ abstract class BoundsScrollListener(private val offsetTop: Int, private val offs if (firstVisibleItemPosition <= offsetTop) { onScrolledToStart(recyclerView) } + onPostScrolled(recyclerView, firstVisibleItemPosition, visibleItemCount) } abstract fun onScrolledToStart(recyclerView: RecyclerView) abstract fun onScrolledToEnd(recyclerView: RecyclerView) + + protected open fun onPostScrolled( + recyclerView: RecyclerView, + firstVisibleItemPosition: Int, + visibleItemCount: Int + ) = Unit } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ScrollListenerInvalidationObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ScrollListenerInvalidationObserver.kt deleted file mode 100644 index 5acc5862c..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ScrollListenerInvalidationObserver.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.koitharu.kotatsu.core.ui.list - -import androidx.recyclerview.widget.RecyclerView - -class ScrollListenerInvalidationObserver( - private val recyclerView: RecyclerView, - private val scrollListener: RecyclerView.OnScrollListener, -) : RecyclerView.AdapterDataObserver() { - - override fun onChanged() { - super.onChanged() - invalidateScroll() - } - - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - super.onItemRangeInserted(positionStart, itemCount) - invalidateScroll() - } - - override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { - super.onItemRangeRemoved(positionStart, itemCount) - invalidateScroll() - } - - private fun invalidateScroll() { - recyclerView.post { - scrollListener.onScrolled(recyclerView, 0, 0) - } - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeRunnable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeRunnable.kt new file mode 100644 index 000000000..fe56ffc3c --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeRunnable.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.core.util + +class CompositeRunnable( + private val children: List, +) : Runnable, Collection by children { + + override fun run() { + for (child in children) { + child.run() + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt index baf078b7f..046bd94ca 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt @@ -1,5 +1,7 @@ package org.koitharu.kotatsu.core.util.ext +import org.koitharu.kotatsu.core.util.CompositeRunnable + @Suppress("UNCHECKED_CAST") fun Class.castOrNull(obj: Any?): T? { if (obj == null || !isInstance(obj)) { @@ -7,3 +9,15 @@ fun Class.castOrNull(obj: Any?): T? { } return obj as T } + +/* CompositeRunnable */ + +operator fun Runnable.plus(other: Runnable): Runnable { + val list = ArrayList(this.size + other.size) + if (this is CompositeRunnable) list.addAll(this) else list.add(this) + if (other is CompositeRunnable) list.addAll(other) else list.add(other) + return CompositeRunnable(list) +} + +private val Runnable.size: Int + get() = if (this is CompositeRunnable) size else 1 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt index 4f49f9bad..a5aa7ac9c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt @@ -16,23 +16,25 @@ import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.ui.list.ScrollListenerInvalidationObserver import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet +import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent +import org.koitharu.kotatsu.core.util.ext.plus import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.databinding.SheetPagesBinding import org.koitharu.kotatsu.list.ui.MangaListSpanResolver +import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter -import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.TargetScrollObserver import javax.inject.Inject +import kotlin.math.roundToInt @AndroidEntryPoint class PagesThumbnailsSheet : @@ -79,14 +81,8 @@ class PagesThumbnailsSheet : spanResolver?.setGridSize(settings.gridSize / 100f, this) addOnScrollListener(ScrollListener().also { scrollListener = it }) (layoutManager as GridLayoutManager).spanSizeLookup = spanSizeLookup - thumbnailsAdapter?.registerAdapterDataObserver( - ScrollListenerInvalidationObserver(this, checkNotNull(scrollListener)), - ) - thumbnailsAdapter?.registerAdapterDataObserver(TargetScrollObserver(this)) - } - viewModel.thumbnails.observe(viewLifecycleOwner) { - thumbnailsAdapter?.setItems(it, listCommitCallback) } + viewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged) viewModel.branch.observe(viewLifecycleOwner, ::updateTitle) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) } @@ -124,6 +120,28 @@ class PagesThumbnailsSheet : } } + private fun onThumbnailsChanged(list: List) { + val adapter = thumbnailsAdapter ?: return + if (adapter.itemCount == 0) { + var position = list.indexOfFirst { it is PageThumbnail && it.isCurrent } + if (position > 0) { + val spanCount = spanResolver?.spanCount ?: 0 + val offset = if (position > spanCount + 1) { + (resources.getDimensionPixelSize(R.dimen.manga_list_details_item_height) * 0.6).roundToInt() + } else { + position = 0 + 0 + } + val scrollCallback = RecyclerViewScrollCallback(requireViewBinding().recyclerView, position, offset) + adapter.setItems(list, listCommitCallback + scrollCallback) + } else { + adapter.setItems(list, listCommitCallback) + } + } else { + adapter.setItems(list, listCommitCallback) + } + } + private inner class ScrollListener : BoundsScrollListener(3, 3) { override fun onScrolledToStart(recyclerView: RecyclerView) { @@ -133,6 +151,13 @@ class PagesThumbnailsSheet : override fun onScrolledToEnd(recyclerView: RecyclerView) { viewModel.loadNextChapter() } + + override fun onPostScrolled(recyclerView: RecyclerView, firstVisibleItemPosition: Int, visibleItemCount: Int) { + super.onPostScrolled(recyclerView, firstVisibleItemPosition, visibleItemCount) + if (firstVisibleItemPosition > offsetTop) { + viewModel.allowLoadAbove() + } + } } private inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsViewModel.kt index 8116560fa..ef0463ba0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsViewModel.kt @@ -39,6 +39,7 @@ class PagesThumbnailsViewModel @Inject constructor( private var loadingJob: Job? = null private var loadingPrevJob: Job? = null private var loadingNextJob: Job? = null + private var isLoadAboveAllowed = false val thumbnails = MutableStateFlow>(emptyList()) val branch = MutableStateFlow(null) @@ -51,8 +52,17 @@ class PagesThumbnailsViewModel @Inject constructor( } } + fun allowLoadAbove() { + if (!isLoadAboveAllowed) { + loadingJob = launchJob(Dispatchers.Default) { + isLoadAboveAllowed = true + updateList() + } + } + } + fun loadPrevChapter() { - if (loadingJob?.isActive == true || loadingPrevJob?.isActive == true) { + if (!isLoadAboveAllowed || loadingJob?.isActive == true || loadingPrevJob?.isActive == true) { return } loadingPrevJob = loadPrevNextChapter(isNext = false) @@ -74,7 +84,7 @@ class PagesThumbnailsViewModel @Inject constructor( private suspend fun updateList() { val snapshot = chaptersLoader.snapshot() val mangaChapters = mangaDetails.tryGet().getOrNull()?.chapters.orEmpty() - val hasPrevChapter = snapshot.firstOrNull()?.chapterId != mangaChapters.firstOrNull()?.id + val hasPrevChapter = isLoadAboveAllowed && snapshot.firstOrNull()?.chapterId != mangaChapters.firstOrNull()?.id val hasNextChapter = snapshot.lastOrNull()?.chapterId != mangaChapters.lastOrNull()?.id val pages = buildList(snapshot.size + chaptersLoader.size + 2) { if (hasPrevChapter) { diff --git a/app/src/main/res/layout/sheet_pages.xml b/app/src/main/res/layout/sheet_pages.xml index 986ea651b..140e944af 100644 --- a/app/src/main/res/layout/sheet_pages.xml +++ b/app/src/main/res/layout/sheet_pages.xml @@ -19,7 +19,7 @@ Date: Sun, 4 Jun 2023 16:02:38 +0300 Subject: [PATCH 18/21] Set style for drag handle globally --- app/src/main/res/layout/activity_details.xml | 1 - app/src/main/res/layout/layout_sheet_header_adaptive.xml | 1 - app/src/main/res/values/themes.xml | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml index c73fd7075..7e6f0e8d1 100644 --- a/app/src/main/res/layout/activity_details.xml +++ b/app/src/main/res/layout/activity_details.xml @@ -55,7 +55,6 @@ android:orientation="vertical"> diff --git a/app/src/main/res/layout/layout_sheet_header_adaptive.xml b/app/src/main/res/layout/layout_sheet_header_adaptive.xml index 616360f18..0f5bead1c 100644 --- a/app/src/main/res/layout/layout_sheet_header_adaptive.xml +++ b/app/src/main/res/layout/layout_sheet_header_adaptive.xml @@ -9,7 +9,6 @@ tools:parentTag="android.widget.LinearLayout"> @style/Widget.Material3.CollapsingToolbar.Medium @style/Widget.Kotatsu.CircularProgressIndicator @style/Widget.Kotatsu.LinearProgressIndicator + @style/Widget.Kotatsu.BottomSheet.DragHandle @style/TextAppearance.Kotatsu.Menu From 46b797fc67bf16af0928a7e2f6f01b8ef62537a0 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 4 Jun 2023 15:56:57 +0300 Subject: [PATCH 19/21] Fix chapters bottom sheet header size --- .../koitharu/kotatsu/details/ui/DetailsActivity.kt | 3 +++ app/src/main/res/layout/activity_details.xml | 13 +++++++++---- .../res/layout/layout_sheet_header_adaptive.xml | 2 ++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 0acb146c1..038348767 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -243,6 +243,9 @@ class DetailsActivity : viewBinding.cardChapters?.updateLayoutParams { bottomMargin = insets.bottom + marginEnd } + viewBinding.dragHandle?.updateLayoutParams { + bottomMargin = insets.bottom + } } private fun onHistoryChanged(info: HistoryInfo) { diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml index 7e6f0e8d1..579adf35d 100644 --- a/app/src/main/res/layout/activity_details.xml +++ b/app/src/main/res/layout/activity_details.xml @@ -48,7 +48,7 @@ app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.BottomSheet" tools:visibility="visible"> - + android:layout_height="?actionBarSize" + android:layout_gravity="top" + android:paddingTop="0dp" + android:paddingBottom="32dp" + tools:layout_marginBottom="14dp" /> @@ -92,7 +97,7 @@ - + From b5705b45dfa26ad6c2bcaeb09ed174ebd3628253 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 4 Jun 2023 15:59:44 +0300 Subject: [PATCH 20/21] Add port number validation --- .../kotatsu/settings/ProxySettingsFragment.kt | 4 +++- .../kotatsu/settings/SourceSettingsExt.kt | 2 ++ .../{ => utils/validation}/DomainValidator.kt | 2 +- .../{ => utils/validation}/HeaderValidator.kt | 2 +- .../utils/validation/PortNumberValidator.kt | 24 +++++++++++++++++++ .../kotatsu/sync/ui/SyncHostDialogFragment.kt | 2 +- app/src/main/res/values/strings.xml | 1 + 7 files changed, 33 insertions(+), 4 deletions(-) rename app/src/main/kotlin/org/koitharu/kotatsu/settings/{ => utils/validation}/DomainValidator.kt (93%) rename app/src/main/kotlin/org/koitharu/kotatsu/settings/{ => utils/validation}/HeaderValidator.kt (92%) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/PortNumberValidator.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/ProxySettingsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/ProxySettingsFragment.kt index ab6097f5f..4125d46e2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/ProxySettingsFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/ProxySettingsFragment.kt @@ -13,6 +13,8 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.ui.BasePreferenceFragment import org.koitharu.kotatsu.settings.utils.EditTextBindListener import org.koitharu.kotatsu.settings.utils.PasswordSummaryProvider +import org.koitharu.kotatsu.settings.utils.validation.DomainValidator +import org.koitharu.kotatsu.settings.utils.validation.PortNumberValidator import java.net.Proxy @AndroidEntryPoint @@ -32,7 +34,7 @@ class ProxySettingsFragment : BasePreferenceFragment(R.string.proxy), EditTextBindListener( inputType = EditorInfo.TYPE_CLASS_NUMBER, hint = null, - validator = null, + validator = PortNumberValidator(), ), ) findPreference(AppSettings.KEY_PROXY_PASSWORD)?.let { pref -> diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/SourceSettingsExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/SourceSettingsExt.kt index 6a83af617..d0123bee7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/SourceSettingsExt.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/SourceSettingsExt.kt @@ -11,6 +11,8 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.settings.utils.AutoCompleteTextViewPreference import org.koitharu.kotatsu.settings.utils.EditTextBindListener import org.koitharu.kotatsu.settings.utils.EditTextDefaultSummaryProvider +import org.koitharu.kotatsu.settings.utils.validation.DomainValidator +import org.koitharu.kotatsu.settings.utils.validation.HeaderValidator fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMangaRepository) { val configKeys = repository.getConfigKeys() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/DomainValidator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/DomainValidator.kt similarity index 93% rename from app/src/main/kotlin/org/koitharu/kotatsu/settings/DomainValidator.kt rename to app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/DomainValidator.kt index 0ac467edc..201fabeec 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/DomainValidator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/DomainValidator.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.settings +package org.koitharu.kotatsu.settings.utils.validation import okhttp3.HttpUrl import org.koitharu.kotatsu.R diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/HeaderValidator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/HeaderValidator.kt similarity index 92% rename from app/src/main/kotlin/org/koitharu/kotatsu/settings/HeaderValidator.kt rename to app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/HeaderValidator.kt index b25be70d6..36891f980 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/HeaderValidator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/HeaderValidator.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.settings +package org.koitharu.kotatsu.settings.utils.validation import okhttp3.Headers import org.koitharu.kotatsu.R diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/PortNumberValidator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/PortNumberValidator.kt new file mode 100644 index 000000000..3bee9f9a5 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/PortNumberValidator.kt @@ -0,0 +1,24 @@ +package org.koitharu.kotatsu.settings.utils.validation + +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.EditTextValidator + +class PortNumberValidator : EditTextValidator() { + + override fun validate(text: String): ValidationResult { + val trimmed = text.trim() + if (trimmed.isEmpty()) { + return ValidationResult.Success + } + return if (!checkCharacters(trimmed)) { + ValidationResult.Failed(context.getString(R.string.invalid_port_number)) + } else { + ValidationResult.Success + } + } + + private fun checkCharacters(value: String): Boolean { + val intValue = value.toIntOrNull() ?: return false + return intValue in 1..65535 + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncHostDialogFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncHostDialogFragment.kt index 735adb19a..2de3183f4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncHostDialogFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncHostDialogFragment.kt @@ -14,7 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.databinding.PreferenceDialogAutocompletetextviewBinding -import org.koitharu.kotatsu.settings.DomainValidator +import org.koitharu.kotatsu.settings.utils.validation.DomainValidator import org.koitharu.kotatsu.sync.data.SyncSettings import javax.inject.Inject diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eb9f3d1f4..5dad409f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -432,4 +432,5 @@ Username Password Authorization (optional) + Invalid port number From 12d8d3e2d117354e810a0ac3872afb541954e74a Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 4 Jun 2023 16:04:36 +0300 Subject: [PATCH 21/21] Set AppProxySelector as default --- .../org/koitharu/kotatsu/core/network/AppProxySelector.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/AppProxySelector.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/AppProxySelector.kt index 5beb6e42a..7d67a40fd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/AppProxySelector.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/AppProxySelector.kt @@ -13,6 +13,10 @@ class AppProxySelector( private val settings: AppSettings, ) : ProxySelector() { + init { + setDefault(this) + } + private var cachedProxy: Proxy? = null override fun select(uri: URI?): List {