diff --git a/app/build.gradle b/app/build.gradle
index f0dde7b78..64aab0ef4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -15,8 +15,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 33
- versionCode 540
- versionName '5.0.2'
+ versionCode 546
+ versionName '5.1.2'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -42,6 +42,7 @@ android {
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
+ main.java.srcDirs += 'src/main/kotlin/'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
@@ -59,7 +60,7 @@ android {
}
lint {
abortOnError true
- disable 'MissingTranslation', 'PrivateResource', 'NotifyDataSetChanged'
+ disable 'MissingTranslation', 'PrivateResource', 'NotifyDataSetChanged', 'SetJavaScriptEnabled'
}
testOptions {
unitTests.includeAndroidResources true
@@ -78,15 +79,15 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
- implementation('com.github.KotatsuApp:kotatsu-parsers:96b9ac36f3') {
+ implementation('com.github.KotatsuApp:kotatsu-parsers:ebcc6391d6') {
exclude group: 'org.json', module: 'json'
}
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.21'
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'androidx.core:core-ktx:1.10.0'
+ implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.activity:activity-ktx:1.7.1'
implementation 'androidx.fragment:fragment-ktx:1.5.7'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
@@ -98,12 +99,19 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.preference:preference-ktx:1.2.0'
- implementation 'androidx.work:work-runtime-ktx:2.8.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
implementation 'com.google.android.material:material:1.9.0'
//noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.6.1'
+ implementation 'androidx.work:work-runtime-ktx:2.8.1'
+ //noinspection GradleDependency
+ implementation('com.google.guava:guava:31.1-android') {
+ exclude group: 'com.google.guava', module: 'failureaccess'
+ exclude group: 'org.checkerframework', module: 'checker-qual'
+ exclude group: 'com.google.j2objc', module: 'j2objc-annotations'
+ }
+
implementation 'androidx.room:room-runtime:2.5.1'
implementation 'androidx.room:room-ktx:2.5.1'
kapt 'androidx.room:room-compiler:2.5.1'
@@ -115,8 +123,8 @@ dependencies {
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
- implementation 'com.google.dagger:hilt-android:2.45'
- kapt 'com.google.dagger:hilt-compiler:2.45'
+ implementation 'com.google.dagger:hilt-android:2.46.1'
+ kapt 'com.google.dagger:hilt-compiler:2.46.1'
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
@@ -133,18 +141,18 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.json:json:20230227'
- testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
- androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
+ androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1'
androidTestImplementation 'androidx.room:room-testing:2.5.1'
- androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
+ androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
- androidTestImplementation 'com.google.dagger:hilt-android-testing:2.45'
- kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.45'
+ androidTestImplementation 'com.google.dagger:hilt-android-testing:2.46.1'
+ kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.46.1'
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index f2f5b932b..a81d9ac31 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -1,4 +1,5 @@
-optimizationpasses 8
+-dontobfuscate
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void checkExpressionValueIsNotNull(...);
public static void checkNotNullExpressionValue(...);
@@ -7,7 +8,7 @@
public static void checkParameterIsNotNull(...);
public static void checkNotNullParameter(...);
}
--keep public class ** extends org.koitharu.kotatsu.base.ui.BaseFragment
+-keep public class ** extends org.koitharu.kotatsu.core.ui.BaseFragment
-keep class org.koitharu.kotatsu.core.db.entity.* { *; }
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
diff --git a/app/src/androidTest/java/org/koitharu/kotatsu/core/os/ShortcutsUpdaterTest.kt b/app/src/androidTest/java/org/koitharu/kotatsu/core/os/ShortcutsUpdaterTest.kt
index 4b1784bed..6b9de214f 100644
--- a/app/src/androidTest/java/org/koitharu/kotatsu/core/os/ShortcutsUpdaterTest.kt
+++ b/app/src/androidTest/java/org/koitharu/kotatsu/core/os/ShortcutsUpdaterTest.kt
@@ -8,7 +8,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
-import javax.inject.Inject
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -19,7 +18,8 @@ import org.junit.runner.RunWith
import org.koitharu.kotatsu.SampleData
import org.koitharu.kotatsu.awaitForIdle
import org.koitharu.kotatsu.core.db.MangaDatabase
-import org.koitharu.kotatsu.history.domain.HistoryRepository
+import org.koitharu.kotatsu.history.data.HistoryRepository
+import javax.inject.Inject
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
diff --git a/app/src/androidTest/java/org/koitharu/kotatsu/settings/backup/AppBackupAgentTest.kt b/app/src/androidTest/java/org/koitharu/kotatsu/settings/backup/AppBackupAgentTest.kt
index f4448baa4..9984821cc 100644
--- a/app/src/androidTest/java/org/koitharu/kotatsu/settings/backup/AppBackupAgentTest.kt
+++ b/app/src/androidTest/java/org/koitharu/kotatsu/settings/backup/AppBackupAgentTest.kt
@@ -5,8 +5,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
-import java.io.File
-import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Assert.*
@@ -19,7 +17,9 @@ import org.koitharu.kotatsu.core.backup.BackupRepository
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
-import org.koitharu.kotatsu.history.domain.HistoryRepository
+import org.koitharu.kotatsu.history.data.HistoryRepository
+import java.io.File
+import javax.inject.Inject
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
@@ -52,6 +52,7 @@ class AppBackupAgentTest {
title = SampleData.favouriteCategory.title,
sortOrder = SampleData.favouriteCategory.order,
isTrackerEnabled = SampleData.favouriteCategory.isTrackingEnabled,
+ isVisibleOnShelf = SampleData.favouriteCategory.isVisibleInLibrary,
)
favouritesRepository.addToCategory(categoryId = category.id, mangas = listOf(SampleData.manga))
historyRepository.addOrUpdate(
diff --git a/app/src/androidTest/java/org/koitharu/kotatsu/tracker/domain/TrackerTest.kt b/app/src/androidTest/java/org/koitharu/kotatsu/tracker/domain/TrackerTest.kt
index 37b5ebf02..5e38b4d15 100644
--- a/app/src/androidTest/java/org/koitharu/kotatsu/tracker/domain/TrackerTest.kt
+++ b/app/src/androidTest/java/org/koitharu/kotatsu/tracker/domain/TrackerTest.kt
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.tracker.domain
import androidx.test.ext.junit.runners.AndroidJUnit4
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
-import javax.inject.Inject
import junit.framework.TestCase.*
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -11,8 +10,9 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.koitharu.kotatsu.SampleData
-import org.koitharu.kotatsu.base.domain.MangaDataRepository
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.parsers.model.Manga
+import javax.inject.Inject
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
diff --git a/app/src/debug/java/org/koitharu/kotatsu/util/LoggingAdapterDataObserver.kt b/app/src/debug/java/org/koitharu/kotatsu/util/LoggingAdapterDataObserver.kt
new file mode 100644
index 000000000..07ae3c4bb
--- /dev/null
+++ b/app/src/debug/java/org/koitharu/kotatsu/util/LoggingAdapterDataObserver.kt
@@ -0,0 +1,37 @@
+package org.koitharu.kotatsu.util
+
+import android.util.Log
+import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
+
+class LoggingAdapterDataObserver(
+ private val tag: String,
+) : AdapterDataObserver() {
+
+ override fun onChanged() {
+ Log.d(tag, "onChanged()")
+ }
+
+ override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
+ Log.d(tag, "onItemRangeChanged(positionStart=$positionStart, itemCount=$itemCount)")
+ }
+
+ override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
+ Log.d(tag, "onItemRangeChanged(positionStart=$positionStart, itemCount=$itemCount, payload=$payload)")
+ }
+
+ override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+ Log.d(tag, "onItemRangeInserted(positionStart=$positionStart, itemCount=$itemCount)")
+ }
+
+ override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
+ Log.d(tag, "onItemRangeRemoved(positionStart=$positionStart, itemCount=$itemCount)")
+ }
+
+ override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
+ Log.d(tag, "onItemRangeMoved(fromPosition=$fromPosition, toPosition=$toPosition, itemCount=$itemCount)")
+ }
+
+ override fun onStateRestorationPolicyChanged() {
+ Log.d(tag, "onStateRestorationPolicyChanged()")
+ }
+}
diff --git a/app/src/debug/java/org/koitharu/kotatsu/util/ext/DebugExt.kt b/app/src/debug/java/org/koitharu/kotatsu/util/ext/DebugExt.kt
new file mode 100644
index 000000000..acaab0f0c
--- /dev/null
+++ b/app/src/debug/java/org/koitharu/kotatsu/util/ext/DebugExt.kt
@@ -0,0 +1,3 @@
+package org.koitharu.kotatsu.util.ext
+
+fun Throwable.printStackTraceDebug() = printStackTrace()
diff --git a/app/src/debug/java/org/koitharu/kotatsu/utils/ext/DebugExt.kt b/app/src/debug/java/org/koitharu/kotatsu/utils/ext/DebugExt.kt
deleted file mode 100644
index e00bb6a83..000000000
--- a/app/src/debug/java/org/koitharu/kotatsu/utils/ext/DebugExt.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package org.koitharu.kotatsu.utils.ext
-
-fun Throwable.printStackTraceDebug() = printStackTrace()
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fb10865a6..128fba69f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -95,6 +95,7 @@
+
@@ -174,10 +175,6 @@
-
+ android:exported="false"
+ tools:node="remove">
+
+
(false) {
-
- private val counter = AtomicInteger(0)
-
- @AnyThread
- fun increment() {
- if (counter.getAndIncrement() == 0) {
- postValue(true)
- }
- }
-
- @AnyThread
- fun decrement() {
- if (counter.decrementAndGet() == 0) {
- postValue(false)
- }
- }
-
- @AnyThread
- fun reset() {
- if (counter.getAndSet(0) != 0) {
- postValue(false)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/DeferredLruCache.kt b/app/src/main/java/org/koitharu/kotatsu/core/cache/DeferredLruCache.kt
deleted file mode 100644
index 8b9e08aa3..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/core/cache/DeferredLruCache.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.koitharu.kotatsu.core.cache
-
-import androidx.collection.LruCache
-
-class DeferredLruCache(maxSize: Int) : LruCache>(maxSize)
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/domain/BranchComparator.kt b/app/src/main/java/org/koitharu/kotatsu/details/domain/BranchComparator.kt
deleted file mode 100644
index d0b5a23c3..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/details/domain/BranchComparator.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.koitharu.kotatsu.details.domain
-
-class BranchComparator : Comparator {
-
- override fun compare(o1: String?, o2: String?): Int = compareValues(o1, o2)
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
deleted file mode 100644
index bffa668a0..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
+++ /dev/null
@@ -1,340 +0,0 @@
-package org.koitharu.kotatsu.details.ui
-
-import android.text.Html
-import android.text.SpannableString
-import android.text.Spanned
-import android.text.style.ForegroundColorSpan
-import androidx.core.net.toUri
-import androidx.core.text.getSpans
-import androidx.core.text.parseAsHtml
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.asFlow
-import androidx.lifecycle.asLiveData
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChangedBy
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.flow.transformLatest
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.plus
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.BaseViewModel
-import org.koitharu.kotatsu.bookmarks.domain.Bookmark
-import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
-import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.core.prefs.observeAsFlow
-import org.koitharu.kotatsu.details.domain.BranchComparator
-import org.koitharu.kotatsu.details.ui.model.ChapterListItem
-import org.koitharu.kotatsu.details.ui.model.HistoryInfo
-import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
-import org.koitharu.kotatsu.history.domain.HistoryRepository
-import org.koitharu.kotatsu.local.data.LocalManga
-import org.koitharu.kotatsu.local.data.LocalStorageChanges
-import org.koitharu.kotatsu.local.domain.LocalMangaRepository
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.util.mapToSet
-import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
-import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
-import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
-import org.koitharu.kotatsu.tracker.domain.TrackingRepository
-import org.koitharu.kotatsu.utils.SingleLiveEvent
-import org.koitharu.kotatsu.utils.asFlowLiveData
-import org.koitharu.kotatsu.utils.ext.computeSize
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
-import org.koitharu.kotatsu.utils.ext.toFileOrNull
-import java.io.IOException
-import javax.inject.Inject
-
-@HiltViewModel
-class DetailsViewModel @Inject constructor(
- private val historyRepository: HistoryRepository,
- favouritesRepository: FavouritesRepository,
- private val localMangaRepository: LocalMangaRepository,
- trackingRepository: TrackingRepository,
- private val bookmarksRepository: BookmarksRepository,
- private val settings: AppSettings,
- private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
- private val imageGetter: Html.ImageGetter,
- private val delegate: MangaDetailsDelegate,
- @LocalStorageChanges private val localStorageChanges: SharedFlow,
-) : BaseViewModel() {
-
- private var loadingJob: Job
-
- val onShowToast = SingleLiveEvent()
-
- private val history = historyRepository.observeOne(delegate.mangaId)
- .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
-
- private val favourite = favouritesRepository.observeCategoriesIds(delegate.mangaId).map { it.isNotEmpty() }
- .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
-
- private val newChapters = settings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled }
- .flatMapLatest { isEnabled ->
- if (isEnabled) {
- trackingRepository.observeNewChaptersCount(delegate.mangaId)
- } else {
- flowOf(0)
- }
- }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
-
- private val chaptersQuery = MutableStateFlow("")
-
- private val chaptersReversed = settings.observeAsFlow(AppSettings.KEY_REVERSE_CHAPTERS) { chaptersReverse }
- .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
-
- val manga = delegate.manga.filterNotNull().asLiveData(viewModelScope.coroutineContext)
- val favouriteCategories = favourite.asLiveData(viewModelScope.coroutineContext)
- val newChaptersCount = newChapters.asLiveData(viewModelScope.coroutineContext)
- val isChaptersReversed = chaptersReversed.asLiveData(viewModelScope.coroutineContext)
-
- val historyInfo: LiveData = combine(
- delegate.manga,
- delegate.selectedBranch,
- history,
- historyRepository.observeShouldSkip(delegate.manga),
- ) { m, b, h, im ->
- HistoryInfo(m, b, h, im)
- }.asFlowLiveData(
- context = viewModelScope.coroutineContext + Dispatchers.Default,
- defaultValue = HistoryInfo(null, null, null, false),
- )
-
- val bookmarks = delegate.manga.flatMapLatest {
- if (it != null) bookmarksRepository.observeBookmarks(it) else flowOf(emptyList())
- }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
-
- val localSize = combine(
- delegate.manga,
- delegate.relatedManga,
- ) { m1, m2 ->
- val url = when {
- m1?.source == MangaSource.LOCAL -> m1.url
- m2?.source == MangaSource.LOCAL -> m2.url
- else -> null
- }
- if (url != null) {
- val file = url.toUri().toFileOrNull()
- file?.computeSize() ?: 0L
- } else {
- 0L
- }
- }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, 0)
-
- val description = delegate.manga
- .distinctUntilChangedBy { it?.description.orEmpty() }
- .transformLatest {
- val description = it?.description
- if (description.isNullOrEmpty()) {
- emit(null)
- } else {
- emit(description.parseAsHtml().filterSpans())
- emit(description.parseAsHtml(imageGetter = imageGetter).filterSpans())
- }
- }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, null)
-
- val onMangaRemoved = SingleLiveEvent()
- val isScrobblingAvailable: Boolean
- get() = scrobblers.any { it.isAvailable }
-
- val scrobblingInfo: LiveData> = combine(
- scrobblers.map { it.observeScrobblingInfo(delegate.mangaId) },
- ) { scrobblingInfo ->
- scrobblingInfo.filterNotNull()
- }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
-
- val branches: LiveData> = delegate.manga.map {
- val chapters = it?.chapters ?: return@map emptyList()
- chapters.mapToSet { x -> x.branch }.sortedWith(BranchComparator())
- }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
-
- val selectedBranchIndex = combine(
- branches.asFlow(),
- delegate.selectedBranch,
- ) { branches, selected ->
- branches.indexOf(selected)
- }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, -1)
-
- val selectedBranchName = delegate.selectedBranch
- .asFlowLiveData(viewModelScope.coroutineContext, null)
-
- val isChaptersEmpty: LiveData = combine(
- delegate.manga,
- isLoading.asFlow(),
- ) { m, loading ->
- m != null && m.chapters.isNullOrEmpty() && !loading
- }.asFlowLiveData(viewModelScope.coroutineContext, false)
-
- val chapters = combine(
- combine(
- delegate.manga,
- delegate.relatedManga,
- history,
- delegate.selectedBranch,
- newChapters,
- ) { manga, related, history, branch, news ->
- delegate.mapChapters(manga, related, history, news, branch)
- },
- chaptersReversed,
- chaptersQuery,
- ) { list, reversed, query ->
- (if (reversed) list.asReversed() else list).filterSearch(query)
- }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
-
- val selectedBranchValue: String?
- get() = delegate.selectedBranch.value
-
- init {
- loadingJob = doLoad()
- launchJob(Dispatchers.Default) {
- localStorageChanges
- .collect { onDownloadComplete(it) }
- }
- }
-
- fun reload() {
- loadingJob.cancel()
- loadingJob = doLoad()
- }
-
- fun deleteLocal() {
- val m = delegate.manga.value
- if (m == null) {
- onShowToast.call(R.string.file_not_found)
- return
- }
- launchLoadingJob(Dispatchers.Default) {
- val manga = if (m.source == MangaSource.LOCAL) m else localMangaRepository.findSavedManga(m)?.manga
- checkNotNull(manga) { "Cannot find saved manga for ${m.title}" }
- val original = localMangaRepository.getRemoteManga(manga)
- localMangaRepository.delete(manga) || throw IOException("Unable to delete file")
- runCatchingCancellable {
- historyRepository.deleteOrSwap(manga, original)
- }
- onMangaRemoved.emitCall(manga)
- }
- }
-
- fun removeBookmark(bookmark: Bookmark) {
- launchJob {
- bookmarksRepository.removeBookmark(bookmark.manga.id, bookmark.pageId)
- onShowToast.call(R.string.bookmark_removed)
- }
- }
-
- fun setChaptersReversed(newValue: Boolean) {
- settings.chaptersReverse = newValue
- }
-
- fun setSelectedBranch(branch: String?) {
- delegate.selectedBranch.value = branch
- }
-
- fun getRemoteManga(): Manga? {
- return delegate.relatedManga.value?.takeUnless { it.source == MangaSource.LOCAL }
- }
-
- fun performChapterSearch(query: String?) {
- chaptersQuery.value = query?.trim().orEmpty()
- }
-
- fun updateScrobbling(index: Int, rating: Float, status: ScrobblingStatus?) {
- val scrobbler = getScrobbler(index) ?: return
- launchJob(Dispatchers.Default) {
- scrobbler.updateScrobblingInfo(
- mangaId = delegate.mangaId,
- rating = rating,
- status = status,
- comment = null,
- )
- }
- }
-
- fun unregisterScrobbling(index: Int) {
- val scrobbler = getScrobbler(index) ?: return
- launchJob(Dispatchers.Default) {
- scrobbler.unregisterScrobbling(
- mangaId = delegate.mangaId,
- )
- }
- }
-
- fun markChapterAsCurrent(chapterId: Long) {
- launchJob(Dispatchers.Default) {
- val manga = checkNotNull(delegate.manga.value)
- val chapters = checkNotNull(manga.getChapters(selectedBranchValue))
- val chapterIndex = chapters.indexOfFirst { it.id == chapterId }
- check(chapterIndex in chapters.indices) { "Chapter not found" }
- val percent = chapterIndex / chapters.size.toFloat()
- historyRepository.addOrUpdate(manga = manga, chapterId = chapterId, page = 0, scroll = 0, percent = percent)
- }
- }
-
- private fun doLoad() = launchLoadingJob(Dispatchers.Default) {
- delegate.doLoad()
- }
-
- private fun List.filterSearch(query: String): List {
- if (query.isEmpty() || this.isEmpty()) {
- return this
- }
- return filter {
- it.chapter.name.contains(query, ignoreCase = true)
- }
- }
-
- private suspend fun onDownloadComplete(downloadedManga: LocalManga?) {
- downloadedManga ?: return
- val currentManga = delegate.manga.value ?: return
- if (currentManga.id != downloadedManga.manga.id) {
- return
- }
- if (currentManga.source == MangaSource.LOCAL) {
- reload()
- } else {
- viewModelScope.launch(Dispatchers.Default) {
- runCatchingCancellable {
- localMangaRepository.getDetails(downloadedManga.manga)
- }.onSuccess {
- delegate.relatedManga.value = it
- }.onFailure {
- it.printStackTraceDebug()
- }
- }
- }
- }
-
- private fun Spanned.filterSpans(): CharSequence {
- val spannable = SpannableString.valueOf(this)
- val spans = spannable.getSpans()
- for (span in spans) {
- spannable.removeSpan(span)
- }
- return spannable.trim()
- }
-
- private fun getScrobbler(index: Int): Scrobbler? {
- val info = scrobblingInfo.value?.getOrNull(index)
- val scrobbler = if (info != null) {
- scrobblers.find { it.scrobblerService == info.scrobbler && it.isAvailable }
- } else {
- null
- }
- if (scrobbler == null) {
- errorEvent.call(IllegalStateException("Scrobbler [$index] is not available"))
- }
- return scrobbler
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt
deleted file mode 100644
index b0072f4a8..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt
+++ /dev/null
@@ -1,162 +0,0 @@
-package org.koitharu.kotatsu.details.ui
-
-import androidx.lifecycle.SavedStateHandle
-import dagger.hilt.android.scopes.ViewModelScoped
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import org.koitharu.kotatsu.base.domain.MangaDataRepository
-import org.koitharu.kotatsu.base.domain.MangaIntent
-import org.koitharu.kotatsu.core.model.MangaHistory
-import org.koitharu.kotatsu.core.model.getPreferredBranch
-import org.koitharu.kotatsu.core.parser.MangaRepository
-import org.koitharu.kotatsu.details.ui.model.ChapterListItem
-import org.koitharu.kotatsu.details.ui.model.toListItem
-import org.koitharu.kotatsu.history.domain.HistoryRepository
-import org.koitharu.kotatsu.local.domain.LocalMangaRepository
-import org.koitharu.kotatsu.parsers.exception.NotFoundException
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaChapter
-import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
-import javax.inject.Inject
-
-@ViewModelScoped
-class MangaDetailsDelegate @Inject constructor(
- savedStateHandle: SavedStateHandle,
- private val mangaDataRepository: MangaDataRepository,
- private val historyRepository: HistoryRepository,
- private val localMangaRepository: LocalMangaRepository,
- private val mangaRepositoryFactory: MangaRepository.Factory,
-) {
- private val intent = MangaIntent(savedStateHandle)
- private val mangaData = MutableStateFlow(intent.manga)
-
- val selectedBranch = MutableStateFlow(null)
-
- // Remote manga for saved and saved for remote
- val relatedManga = MutableStateFlow(null)
- val manga: StateFlow
- get() = mangaData
- val mangaId = intent.manga?.id ?: intent.mangaId
-
- suspend fun doLoad() {
- var manga = mangaDataRepository.resolveIntent(intent) ?: throw NotFoundException("Cannot find manga", "")
- mangaData.value = manga
- manga = mangaRepositoryFactory.create(manga.source).getDetails(manga)
- // find default branch
- val hist = historyRepository.getOne(manga)
- selectedBranch.value = manga.getPreferredBranch(hist)
- mangaData.value = manga
- relatedManga.value = runCatchingCancellable {
- if (manga.source == MangaSource.LOCAL) {
- val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatchingCancellable null
- mangaRepositoryFactory.create(m.source).getDetails(m)
- } else {
- localMangaRepository.findSavedManga(manga)?.manga
- }
- }.onFailure { error ->
- error.printStackTraceDebug()
- }.getOrNull()
- }
-
- fun mapChapters(
- manga: Manga?,
- related: Manga?,
- history: MangaHistory?,
- newCount: Int,
- branch: String?,
- ): List {
- val chapters = manga?.chapters ?: return emptyList()
- val relatedChapters = related?.chapters
- return if (related?.source != MangaSource.LOCAL && !relatedChapters.isNullOrEmpty()) {
- mapChaptersWithSource(chapters, relatedChapters, history?.chapterId, newCount, branch)
- } else {
- mapChapters(chapters, relatedChapters, history?.chapterId, newCount, branch)
- }
- }
-
- private fun mapChapters(
- chapters: List,
- downloadedChapters: List?,
- currentId: Long?,
- newCount: Int,
- branch: String?,
- ): List {
- val result = ArrayList(chapters.size)
- val currentIndex = chapters.indexOfFirst { it.id == currentId }
- val firstNewIndex = chapters.size - newCount
- val downloadedIds = downloadedChapters?.mapTo(HashSet(downloadedChapters.size)) { it.id }
- for (i in chapters.indices) {
- val chapter = chapters[i]
- if (chapter.branch != branch) {
- continue
- }
- result += chapter.toListItem(
- isCurrent = i == currentIndex,
- isUnread = i > currentIndex,
- isNew = i >= firstNewIndex,
- isMissing = false,
- isDownloaded = downloadedIds?.contains(chapter.id) == true,
- )
- }
- if (result.size < chapters.size / 2) {
- result.trimToSize()
- }
- return result
- }
-
- private fun mapChaptersWithSource(
- chapters: List,
- sourceChapters: List,
- currentId: Long?,
- newCount: Int,
- branch: String?,
- ): List {
- val chaptersMap = chapters.associateByTo(HashMap(chapters.size)) { it.id }
- val result = ArrayList(sourceChapters.size)
- val currentIndex = sourceChapters.indexOfFirst { it.id == currentId }
- val firstNewIndex = sourceChapters.size - newCount
- for (i in sourceChapters.indices) {
- val chapter = sourceChapters[i]
- val localChapter = chaptersMap.remove(chapter.id)
- if (chapter.branch != branch) {
- continue
- }
- result += localChapter?.toListItem(
- isCurrent = i == currentIndex,
- isUnread = i > currentIndex,
- isNew = i >= firstNewIndex,
- isMissing = false,
- isDownloaded = false,
- ) ?: chapter.toListItem(
- isCurrent = i == currentIndex,
- isUnread = i > currentIndex,
- isNew = i >= firstNewIndex,
- isMissing = true,
- isDownloaded = false,
- )
- }
- if (chaptersMap.isNotEmpty()) { // some chapters on device but not online source
- result.ensureCapacity(result.size + chaptersMap.size)
- chaptersMap.values.mapNotNullTo(result) {
- if (it.branch == branch) {
- it.toListItem(
- isCurrent = false,
- isUnread = true,
- isNew = false,
- isMissing = false,
- isDownloaded = false,
- )
- } else {
- null
- }
- }
- result.sortBy { it.chapter.number }
- }
- if (result.size < sourceChapters.size / 2) {
- result.trimToSize()
- }
- return result
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt
deleted file mode 100644
index 2ca602df9..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.koitharu.kotatsu.details.ui.adapter
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.BaseAdapter
-import android.widget.TextView
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.parsers.util.replaceWith
-
-class BranchesAdapter : BaseAdapter() {
-
- private val dataSet = ArrayList()
-
- override fun getCount(): Int {
- return dataSet.size
- }
-
- override fun getItem(position: Int): Any? {
- return dataSet[position]
- }
-
- override fun getItemId(position: Int): Long {
- return dataSet[position].hashCode().toLong()
- }
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val view = convertView ?: LayoutInflater.from(parent.context)
- .inflate(R.layout.item_branch, parent, false)
- (view as TextView).text = dataSet[position]
- return view
- }
-
- override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
- val view = convertView ?: LayoutInflater.from(parent.context)
- .inflate(R.layout.item_branch_dropdown, parent, false)
- (view as TextView).text = dataSet[position]
- return view
- }
-
- fun setItems(items: Collection) {
- dataSet.replaceWith(items)
- notifyDataSetChanged()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt
deleted file mode 100644
index ab051c176..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.koitharu.kotatsu.details.ui.adapter
-
-import androidx.core.view.isVisible
-import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
-import org.koitharu.kotatsu.databinding.ItemChapterBinding
-import org.koitharu.kotatsu.details.ui.model.ChapterListItem
-import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_CURRENT
-import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_DOWNLOADED
-import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_MISSING
-import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_NEW
-import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_UNREAD
-import org.koitharu.kotatsu.utils.ext.getThemeColor
-import org.koitharu.kotatsu.utils.ext.textAndVisible
-
-fun chapterListItemAD(
- clickListener: OnListItemClickListener,
-) = adapterDelegateViewBinding(
- { inflater, parent -> ItemChapterBinding.inflate(inflater, parent, false) }
-) {
-
- val eventListener = AdapterDelegateClickListenerAdapter(this, clickListener)
- itemView.setOnClickListener(eventListener)
- itemView.setOnLongClickListener(eventListener)
-
- bind { payloads ->
- if (payloads.isEmpty()) {
- binding.textViewTitle.text = item.chapter.name
- binding.textViewNumber.text = item.chapter.number.toString()
- binding.textViewDescription.textAndVisible = item.description()
- }
- when (item.status) {
- FLAG_UNREAD -> {
- binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_default)
- binding.textViewNumber.setTextColor(context.getThemeColor(com.google.android.material.R.attr.colorOnTertiary))
- }
- FLAG_CURRENT -> {
- binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_accent)
- binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse))
- }
- else -> {
- binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_outline)
- binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorTertiary))
- }
- }
- val isMissing = item.hasFlag(FLAG_MISSING)
- binding.textViewTitle.alpha = if (isMissing) 0.3f else 1f
- binding.textViewDescription.alpha = if (isMissing) 0.3f else 1f
- binding.textViewNumber.alpha = if (isMissing) 0.3f else 1f
-
- binding.imageViewDownloaded.isVisible = item.hasFlag(FLAG_DOWNLOADED)
- binding.imageViewNew.isVisible = item.hasFlag(FLAG_NEW)
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt
deleted file mode 100644
index 88f4a6aa0..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt
+++ /dev/null
@@ -1,281 +0,0 @@
-package org.koitharu.kotatsu.download.domain
-
-import android.app.Service
-import android.content.Context
-import android.webkit.MimeTypeMap
-import androidx.lifecycle.LifecycleService
-import androidx.lifecycle.lifecycleScope
-import coil.ImageLoader
-import coil.request.ImageRequest
-import coil.size.Scale
-import dagger.hilt.android.qualifiers.ApplicationContext
-import dagger.hilt.android.scopes.ServiceScoped
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.CoroutineExceptionHandler
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.NonCancellable
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Semaphore
-import kotlinx.coroutines.sync.withPermit
-import kotlinx.coroutines.withContext
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.internal.closeQuietly
-import okio.IOException
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.core.network.CommonHeaders
-import org.koitharu.kotatsu.core.parser.MangaRepository
-import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.download.ui.service.PausingHandle
-import org.koitharu.kotatsu.local.data.LocalManga
-import org.koitharu.kotatsu.local.data.LocalStorageChanges
-import org.koitharu.kotatsu.local.data.PagesCache
-import org.koitharu.kotatsu.local.data.input.LocalMangaInput
-import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
-import org.koitharu.kotatsu.local.domain.LocalMangaRepository
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.util.await
-import org.koitharu.kotatsu.utils.ext.copyToSuspending
-import org.koitharu.kotatsu.utils.ext.deleteAwait
-import org.koitharu.kotatsu.utils.ext.ifNullOrEmpty
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
-import org.koitharu.kotatsu.utils.progress.PausingProgressJob
-import java.io.File
-import javax.inject.Inject
-
-private const val MAX_FAILSAFE_ATTEMPTS = 2
-private const val DOWNLOAD_ERROR_DELAY = 500L
-private const val SLOWDOWN_DELAY = 150L
-
-@ServiceScoped
-class DownloadManager @Inject constructor(
- service: Service,
- @ApplicationContext private val context: Context,
- private val imageLoader: ImageLoader,
- private val okHttp: OkHttpClient,
- private val cache: PagesCache,
- private val localMangaRepository: LocalMangaRepository,
- private val settings: AppSettings,
- private val mangaRepositoryFactory: MangaRepository.Factory,
- @LocalStorageChanges private val localStorageChanges: MutableSharedFlow,
-) {
-
- private val coverWidth = context.resources.getDimensionPixelSize(
- androidx.core.R.dimen.compat_notification_large_icon_max_width,
- )
- private val coverHeight = context.resources.getDimensionPixelSize(
- androidx.core.R.dimen.compat_notification_large_icon_max_height,
- )
- private val semaphore = Semaphore(settings.downloadsParallelism)
- private val coroutineScope = (service as LifecycleService).lifecycleScope
-
- fun downloadManga(
- manga: Manga,
- chaptersIds: LongArray?,
- startId: Int,
- ): PausingProgressJob {
- val stateFlow = MutableStateFlow(
- DownloadState.Queued(startId = startId, manga = manga, cover = null),
- )
- val pausingHandle = PausingHandle()
- val job = coroutineScope.launch(Dispatchers.Default + errorStateHandler(stateFlow)) {
- try {
- downloadMangaImpl(manga, chaptersIds?.takeUnless { it.isEmpty() }, stateFlow, pausingHandle, startId)
- } catch (e: CancellationException) { // handle cancellation if not handled already
- val state = stateFlow.value
- if (state !is DownloadState.Cancelled) {
- stateFlow.value = DownloadState.Cancelled(startId, state.manga, state.cover)
- }
- throw e
- }
- }
- return PausingProgressJob(job, stateFlow, pausingHandle)
- }
-
- private suspend fun downloadMangaImpl(
- manga: Manga,
- chaptersIds: LongArray?,
- outState: MutableStateFlow,
- pausingHandle: PausingHandle,
- startId: Int,
- ) {
- @Suppress("NAME_SHADOWING")
- var manga = manga
- val chaptersIdsSet = chaptersIds?.toMutableSet()
- val cover = loadCover(manga)
- outState.value = DownloadState.Queued(startId, manga, cover)
- withMangaLock(manga) {
- semaphore.withPermit {
- outState.value = DownloadState.Preparing(startId, manga, null)
- val destination = localMangaRepository.getOutputDir(manga)
- checkNotNull(destination) { context.getString(R.string.cannot_find_available_storage) }
- val tempFileName = "${manga.id}_$startId.tmp"
- var output: LocalMangaOutput? = null
- try {
- if (manga.source == MangaSource.LOCAL) {
- manga = localMangaRepository.getRemoteManga(manga)
- ?: error("Cannot obtain remote manga instance")
- }
- val repo = mangaRepositoryFactory.create(manga.source)
- outState.value = DownloadState.Preparing(startId, manga, cover)
- val data = if (manga.chapters.isNullOrEmpty()) repo.getDetails(manga) else manga
- output = LocalMangaOutput.getOrCreate(destination, data)
- val coverUrl = data.largeCoverUrl.ifNullOrEmpty { data.coverUrl }
- if (coverUrl.isNotEmpty()) {
- downloadFile(coverUrl, destination, tempFileName, repo.source).let { file ->
- output.addCover(file, MimeTypeMap.getFileExtensionFromUrl(coverUrl))
- }
- }
- val chapters = checkNotNull(
- if (chaptersIdsSet == null) {
- data.chapters
- } else {
- data.chapters?.filter { x -> chaptersIdsSet.remove(x.id) }
- },
- ) { "Chapters list must not be null" }
- check(chapters.isNotEmpty()) { "Chapters list must not be empty" }
- check(chaptersIdsSet.isNullOrEmpty()) {
- "${chaptersIdsSet?.size} of ${chaptersIds?.size} requested chapters not found in manga"
- }
- for ((chapterIndex, chapter) in chapters.withIndex()) {
- val pages = runFailsafe(outState, pausingHandle) {
- repo.getPages(chapter)
- }
- for ((pageIndex, page) in pages.withIndex()) {
- runFailsafe(outState, pausingHandle) {
- val url = repo.getPageUrl(page)
- val file = cache.get(url)
- ?: downloadFile(url, destination, tempFileName, repo.source)
- output.addPage(
- chapter = chapter,
- file = file,
- pageNumber = pageIndex,
- ext = MimeTypeMap.getFileExtensionFromUrl(url),
- )
- }
- outState.value = DownloadState.Progress(
- startId = startId,
- manga = data,
- cover = cover,
- totalChapters = chapters.size,
- currentChapter = chapterIndex,
- totalPages = pages.size,
- currentPage = pageIndex,
- )
-
- if (settings.isDownloadsSlowdownEnabled) {
- delay(SLOWDOWN_DELAY)
- }
- }
- if (output.flushChapter(chapter)) {
- runCatchingCancellable {
- localStorageChanges.emit(LocalMangaInput.of(output.rootFile).getManga())
- }.onFailure(Throwable::printStackTraceDebug)
- }
- }
- outState.value = DownloadState.PostProcessing(startId, data, cover)
- output.mergeWithExisting()
- output.finish()
- val localManga = LocalMangaInput.of(output.rootFile).getManga()
- localStorageChanges.emit(localManga)
- outState.value = DownloadState.Done(startId, data, cover, localManga.manga)
- } catch (e: CancellationException) {
- outState.value = DownloadState.Cancelled(startId, manga, cover)
- throw e
- } catch (e: Throwable) {
- e.printStackTraceDebug()
- outState.value = DownloadState.Error(startId, manga, cover, e, false)
- } finally {
- withContext(NonCancellable) {
- output?.closeQuietly()
- output?.cleanup()
- File(destination, tempFileName).deleteAwait()
- }
- }
- }
- }
- }
-
- private suspend fun runFailsafe(
- outState: MutableStateFlow,
- pausingHandle: PausingHandle,
- block: suspend () -> R,
- ): R {
- var countDown = MAX_FAILSAFE_ATTEMPTS
- failsafe@ while (true) {
- try {
- return block()
- } catch (e: IOException) {
- if (countDown <= 0) {
- val state = outState.value
- outState.value = DownloadState.Error(state.startId, state.manga, state.cover, e, true)
- countDown = MAX_FAILSAFE_ATTEMPTS
- pausingHandle.pause()
- pausingHandle.awaitResumed()
- outState.value = state
- } else {
- countDown--
- delay(DOWNLOAD_ERROR_DELAY)
- }
- }
- }
- }
-
- private suspend fun downloadFile(
- url: String,
- destination: File,
- tempFileName: String,
- source: MangaSource,
- ): File {
- val request = Request.Builder()
- .url(url)
- .tag(MangaSource::class.java, source)
- .cacheControl(CommonHeaders.CACHE_CONTROL_NO_STORE)
- .get()
- .build()
- val call = okHttp.newCall(request)
- val file = File(destination, tempFileName)
- val response = call.clone().await()
- file.outputStream().use { out ->
- checkNotNull(response.body).byteStream().copyToSuspending(out)
- }
- return file
- }
-
- private fun errorStateHandler(outState: MutableStateFlow) =
- CoroutineExceptionHandler { _, throwable ->
- throwable.printStackTraceDebug()
- val prevValue = outState.value
- outState.value = DownloadState.Error(
- startId = prevValue.startId,
- manga = prevValue.manga,
- cover = prevValue.cover,
- error = throwable,
- canRetry = false,
- )
- }
-
- private suspend fun loadCover(manga: Manga) = runCatchingCancellable {
- imageLoader.execute(
- ImageRequest.Builder(context)
- .data(manga.coverUrl)
- .allowHardware(false)
- .tag(manga.source)
- .size(coverWidth, coverHeight)
- .scale(Scale.FILL)
- .build(),
- ).drawable
- }.getOrNull()
-
- private suspend inline fun withMangaLock(manga: Manga, block: () -> T) = try {
- localMangaRepository.lockManga(manga.id)
- block()
- } finally {
- localMangaRepository.unlockManga(manga.id)
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadState.kt b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadState.kt
deleted file mode 100644
index 0b874f6df..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadState.kt
+++ /dev/null
@@ -1,234 +0,0 @@
-package org.koitharu.kotatsu.download.domain
-
-import android.graphics.drawable.Drawable
-import org.koitharu.kotatsu.parsers.model.Manga
-
-sealed interface DownloadState {
-
- val startId: Int
- val manga: Manga
- val cover: Drawable?
-
- override fun equals(other: Any?): Boolean
-
- override fun hashCode(): Int
-
- val isTerminal: Boolean
- get() = this is Done || this is Cancelled || (this is Error && !canRetry)
-
- class Queued(
- override val startId: Int,
- override val manga: Manga,
- override val cover: Drawable?,
- ) : DownloadState {
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as Queued
-
- if (startId != other.startId) return false
- if (manga != other.manga) return false
- if (cover != other.cover) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = startId
- result = 31 * result + manga.hashCode()
- result = 31 * result + (cover?.hashCode() ?: 0)
- return result
- }
- }
-
- class Preparing(
- override val startId: Int,
- override val manga: Manga,
- override val cover: Drawable?,
- ) : DownloadState {
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as Preparing
-
- if (startId != other.startId) return false
- if (manga != other.manga) return false
- if (cover != other.cover) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = startId
- result = 31 * result + manga.hashCode()
- result = 31 * result + (cover?.hashCode() ?: 0)
- return result
- }
- }
-
- class Progress(
- override val startId: Int,
- override val manga: Manga,
- override val cover: Drawable?,
- val totalChapters: Int,
- val currentChapter: Int,
- val totalPages: Int,
- val currentPage: Int,
- ) : DownloadState {
-
- val max: Int = totalChapters * totalPages
-
- val progress: Int = totalPages * currentChapter + currentPage + 1
-
- val percent: Float = progress.toFloat() / max
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as Progress
-
- if (startId != other.startId) return false
- if (manga != other.manga) return false
- if (cover != other.cover) return false
- if (totalChapters != other.totalChapters) return false
- if (currentChapter != other.currentChapter) return false
- if (totalPages != other.totalPages) return false
- if (currentPage != other.currentPage) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = startId
- result = 31 * result + manga.hashCode()
- result = 31 * result + (cover?.hashCode() ?: 0)
- result = 31 * result + totalChapters
- result = 31 * result + currentChapter
- result = 31 * result + totalPages
- result = 31 * result + currentPage
- return result
- }
- }
-
- class Done(
- override val startId: Int,
- override val manga: Manga,
- override val cover: Drawable?,
- val localManga: Manga,
- ) : DownloadState {
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as Done
-
- if (startId != other.startId) return false
- if (manga != other.manga) return false
- if (cover != other.cover) return false
- if (localManga != other.localManga) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = startId
- result = 31 * result + manga.hashCode()
- result = 31 * result + (cover?.hashCode() ?: 0)
- result = 31 * result + localManga.hashCode()
- return result
- }
- }
-
- class Error(
- override val startId: Int,
- override val manga: Manga,
- override val cover: Drawable?,
- val error: Throwable,
- val canRetry: Boolean,
- ) : DownloadState {
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as Error
-
- if (startId != other.startId) return false
- if (manga != other.manga) return false
- if (cover != other.cover) return false
- if (error != other.error) return false
- if (canRetry != other.canRetry) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = startId
- result = 31 * result + manga.hashCode()
- result = 31 * result + (cover?.hashCode() ?: 0)
- result = 31 * result + error.hashCode()
- result = 31 * result + canRetry.hashCode()
- return result
- }
- }
-
- class Cancelled(
- override val startId: Int,
- override val manga: Manga,
- override val cover: Drawable?,
- ) : DownloadState {
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as Cancelled
-
- if (startId != other.startId) return false
- if (manga != other.manga) return false
- if (cover != other.cover) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = startId
- result = 31 * result + manga.hashCode()
- result = 31 * result + (cover?.hashCode() ?: 0)
- return result
- }
- }
-
- class PostProcessing(
- override val startId: Int,
- override val manga: Manga,
- override val cover: Drawable?,
- ) : DownloadState {
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as PostProcessing
-
- if (startId != other.startId) return false
- if (manga != other.manga) return false
- if (cover != other.cover) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = startId
- result = 31 * result + manga.hashCode()
- result = 31 * result + (cover?.hashCode() ?: 0)
- return result
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt
deleted file mode 100644
index 2afba0b27..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-package org.koitharu.kotatsu.download.ui
-
-import android.view.View
-import androidx.core.view.isVisible
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import coil.ImageLoader
-import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.databinding.ItemDownloadBinding
-import org.koitharu.kotatsu.details.ui.DetailsActivity
-import org.koitharu.kotatsu.download.domain.DownloadState
-import org.koitharu.kotatsu.parsers.util.format
-import org.koitharu.kotatsu.utils.ext.enqueueWith
-import org.koitharu.kotatsu.utils.ext.getDisplayMessage
-import org.koitharu.kotatsu.utils.ext.newImageRequest
-import org.koitharu.kotatsu.utils.ext.onFirst
-import org.koitharu.kotatsu.utils.ext.source
-
-fun downloadItemAD(
- lifecycleOwner: LifecycleOwner,
- coil: ImageLoader,
-) = adapterDelegateViewBinding(
- { inflater, parent -> ItemDownloadBinding.inflate(inflater, parent, false) },
-) {
- var job: Job? = null
- val percentPattern = context.resources.getString(R.string.percent_string_pattern)
-
- val clickListener = View.OnClickListener { v ->
- when (v.id) {
- R.id.button_cancel -> item.cancel()
- R.id.button_resume -> item.resume()
- else -> context.startActivity(
- DetailsActivity.newIntent(context, item.progressValue.manga),
- )
- }
- }
- binding.buttonCancel.setOnClickListener(clickListener)
- binding.buttonResume.setOnClickListener(clickListener)
- itemView.setOnClickListener(clickListener)
-
- bind {
- job?.cancel()
- job = item.progressAsFlow().onFirst { state ->
- binding.imageViewCover.newImageRequest(lifecycleOwner, state.manga.coverUrl)?.run {
- placeholder(state.cover)
- fallback(R.drawable.ic_placeholder)
- error(R.drawable.ic_error_placeholder)
- source(state.manga.source)
- allowRgb565(true)
- enqueueWith(coil)
- }
- }.onEach { state ->
- binding.textViewTitle.text = state.manga.title
- when (state) {
- is DownloadState.Cancelled -> {
- binding.textViewStatus.setText(R.string.cancelling_)
- binding.progressBar.isIndeterminate = true
- binding.progressBar.isVisible = true
- binding.textViewPercent.isVisible = false
- binding.textViewDetails.isVisible = false
- binding.buttonCancel.isVisible = false
- binding.buttonResume.isVisible = false
- }
-
- is DownloadState.Done -> {
- binding.textViewStatus.setText(R.string.download_complete)
- binding.progressBar.isIndeterminate = false
- binding.progressBar.isVisible = false
- binding.textViewPercent.isVisible = false
- binding.textViewDetails.isVisible = false
- binding.buttonCancel.isVisible = false
- binding.buttonResume.isVisible = false
- }
-
- is DownloadState.Error -> {
- binding.textViewStatus.setText(R.string.error_occurred)
- binding.progressBar.isIndeterminate = false
- binding.progressBar.isVisible = false
- binding.textViewPercent.isVisible = false
- binding.textViewDetails.text = state.error.getDisplayMessage(context.resources)
- binding.textViewDetails.isVisible = true
- binding.buttonCancel.isVisible = state.canRetry
- binding.buttonResume.isVisible = state.canRetry
- }
-
- is DownloadState.PostProcessing -> {
- binding.textViewStatus.setText(R.string.processing_)
- binding.progressBar.isIndeterminate = true
- binding.progressBar.isVisible = true
- binding.textViewPercent.isVisible = false
- binding.textViewDetails.isVisible = false
- binding.buttonCancel.isVisible = false
- binding.buttonResume.isVisible = false
- }
-
- is DownloadState.Preparing -> {
- binding.textViewStatus.setText(R.string.preparing_)
- binding.progressBar.isIndeterminate = true
- binding.progressBar.isVisible = true
- binding.textViewPercent.isVisible = false
- binding.textViewDetails.isVisible = false
- binding.buttonCancel.isVisible = true
- binding.buttonResume.isVisible = false
- }
-
- is DownloadState.Progress -> {
- binding.textViewStatus.setText(R.string.manga_downloading_)
- binding.progressBar.isIndeterminate = false
- binding.progressBar.isVisible = true
- binding.progressBar.max = state.max
- binding.progressBar.setProgressCompat(state.progress, true)
- binding.textViewPercent.text = percentPattern.format((state.percent * 100f).format(1))
- binding.textViewPercent.isVisible = true
- binding.textViewDetails.isVisible = false
- binding.buttonCancel.isVisible = true
- binding.buttonResume.isVisible = false
- }
-
- is DownloadState.Queued -> {
- binding.textViewStatus.setText(R.string.queued)
- binding.progressBar.isIndeterminate = false
- binding.progressBar.isVisible = false
- binding.textViewPercent.isVisible = false
- binding.textViewDetails.isVisible = false
- binding.buttonCancel.isVisible = true
- binding.buttonResume.isVisible = false
- }
- }
- }.launchIn(lifecycleOwner.lifecycleScope)
- }
-
- onViewRecycled {
- job?.cancel()
- job = null
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt
deleted file mode 100644
index 7b0872910..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.koitharu.kotatsu.download.ui
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import androidx.core.graphics.Insets
-import androidx.core.view.isVisible
-import androidx.core.view.updatePadding
-import coil.ImageLoader
-import dagger.hilt.android.AndroidEntryPoint
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.BaseActivity
-import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
-import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class DownloadsActivity : BaseActivity() {
-
- @Inject
- lateinit var coil: ImageLoader
-
- private lateinit var serviceConnection: DownloadsConnection
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(ActivityDownloadsBinding.inflate(layoutInflater))
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- val adapter = DownloadsAdapter(this, coil)
- val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
- binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
- binding.recyclerView.setHasFixedSize(true)
- binding.recyclerView.adapter = adapter
- serviceConnection = DownloadsConnection(this, this)
- serviceConnection.items.observe(this) { items ->
- adapter.items = items
- binding.textViewHolder.isVisible = items.isNullOrEmpty()
- }
- serviceConnection.bind()
- }
-
- override fun onWindowInsetsChanged(insets: Insets) {
- binding.recyclerView.updatePadding(
- left = insets.left,
- right = insets.right,
- bottom = insets.bottom,
- )
- binding.toolbar.updatePadding(
- left = insets.left,
- right = insets.right,
- )
- }
-
- companion object {
-
- fun newIntent(context: Context) = Intent(context, DownloadsActivity::class.java)
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsAdapter.kt
deleted file mode 100644
index 5962220c3..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsAdapter.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.koitharu.kotatsu.download.ui
-
-import androidx.lifecycle.LifecycleOwner
-import androidx.recyclerview.widget.DiffUtil
-import coil.ImageLoader
-import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
-import org.koitharu.kotatsu.download.domain.DownloadState
-import org.koitharu.kotatsu.utils.progress.PausingProgressJob
-
-typealias DownloadItem = PausingProgressJob
-
-class DownloadsAdapter(
- lifecycleOwner: LifecycleOwner,
- coil: ImageLoader,
-) : AsyncListDifferDelegationAdapter(DiffCallback()) {
-
- init {
- delegatesManager.addDelegate(downloadItemAD(lifecycleOwner, coil))
- setHasStableIds(true)
- }
-
- override fun getItemId(position: Int): Long {
- return items[position].progressValue.startId.toLong()
- }
-
- private class DiffCallback : DiffUtil.ItemCallback() {
-
- override fun areItemsTheSame(
- oldItem: DownloadItem,
- newItem: DownloadItem,
- ): Boolean {
- return oldItem.progressValue.startId == newItem.progressValue.startId
- }
-
- override fun areContentsTheSame(
- oldItem: DownloadItem,
- newItem: DownloadItem,
- ): Boolean {
- return oldItem.progressValue == newItem.progressValue && oldItem.isPaused == newItem.isPaused
- }
-
- override fun getChangePayload(oldItem: DownloadItem, newItem: DownloadItem): Any {
- return Unit
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsConnection.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsConnection.kt
deleted file mode 100644
index f2577ec26..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsConnection.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.koitharu.kotatsu.download.ui
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.os.IBinder
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.launch
-import org.koitharu.kotatsu.download.domain.DownloadState
-import org.koitharu.kotatsu.download.ui.service.DownloadService
-import org.koitharu.kotatsu.utils.asFlowLiveData
-import org.koitharu.kotatsu.utils.progress.PausingProgressJob
-
-class DownloadsConnection(
- private val context: Context,
- private val lifecycleOwner: LifecycleOwner,
-) : ServiceConnection {
-
- private var bindingObserver: BindingLifecycleObserver? = null
- private var collectJob: Job? = null
- private val itemsFlow = MutableStateFlow>>(emptyList())
-
- val items
- get() = itemsFlow.asFlowLiveData()
-
- override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
- collectJob?.cancel()
- val binder = (service as? DownloadService.DownloadBinder)
- collectJob = if (binder == null) {
- null
- } else {
- lifecycleOwner.lifecycleScope.launch {
- binder.downloads.collect {
- itemsFlow.value = it
- }
- }
- }
- }
-
- override fun onServiceDisconnected(name: ComponentName?) {
- collectJob?.cancel()
- collectJob = null
- itemsFlow.value = itemsFlow.value.filter { it.progressValue.isTerminal }
- }
-
- fun bind() {
- if (bindingObserver != null) {
- return
- }
- bindingObserver = BindingLifecycleObserver().also {
- lifecycleOwner.lifecycle.addObserver(it)
- }
- context.bindService(Intent(context, DownloadService::class.java), this, 0)
- }
-
- fun unbind() {
- bindingObserver?.let {
- lifecycleOwner.lifecycle.removeObserver(it)
- }
- bindingObserver = null
- context.unbindService(this)
- }
-
- private inner class BindingLifecycleObserver : DefaultLifecycleObserver {
-
- override fun onDestroy(owner: LifecycleOwner) {
- super.onDestroy(owner)
- unbind()
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt
deleted file mode 100644
index 103f3621d..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt
+++ /dev/null
@@ -1,356 +0,0 @@
-package org.koitharu.kotatsu.download.ui.service
-
-import android.app.Notification
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.content.Context
-import android.os.Build
-import android.text.format.DateUtils
-import android.util.SparseArray
-import androidx.core.app.NotificationCompat
-import androidx.core.app.NotificationManagerCompat
-import androidx.core.app.PendingIntentCompat
-import androidx.core.content.ContextCompat
-import androidx.core.graphics.drawable.toBitmap
-import androidx.core.text.HtmlCompat
-import androidx.core.text.htmlEncode
-import androidx.core.text.parseAsHtml
-import androidx.core.util.forEach
-import androidx.core.util.isNotEmpty
-import androidx.core.util.size
-import com.google.android.material.R as materialR
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.details.ui.DetailsActivity
-import org.koitharu.kotatsu.download.domain.DownloadState
-import org.koitharu.kotatsu.download.ui.DownloadsActivity
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.util.ellipsize
-import org.koitharu.kotatsu.parsers.util.format
-import org.koitharu.kotatsu.search.ui.MangaListActivity
-import org.koitharu.kotatsu.utils.ext.getDisplayMessage
-
-class DownloadNotification(private val context: Context) {
-
- private val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- private val states = SparseArray()
- private val groupBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
-
- private val queueIntent = PendingIntentCompat.getActivity(
- context,
- REQUEST_QUEUE,
- DownloadsActivity.newIntent(context),
- 0,
- false,
- )
-
- private val localListIntent = PendingIntentCompat.getActivity(
- context,
- REQUEST_LIST_LOCAL,
- MangaListActivity.newIntent(context, MangaSource.LOCAL),
- 0,
- false,
- )
-
- init {
- groupBuilder.setOnlyAlertOnce(true)
- groupBuilder.setDefaults(0)
- groupBuilder.color = ContextCompat.getColor(context, R.color.blue_primary)
- groupBuilder.foregroundServiceBehavior = NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
- groupBuilder.setSilent(true)
- groupBuilder.setGroup(GROUP_ID)
- groupBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
- groupBuilder.setGroupSummary(true)
- groupBuilder.setContentTitle(context.getString(R.string.downloading_manga))
- }
-
- fun buildGroupNotification(): Notification {
- val style = NotificationCompat.InboxStyle(groupBuilder)
- var progress = 0f
- var isAllDone = true
- var isInProgress = false
- groupBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- states.forEach { _, state ->
- if (state.manga.isNsfw) {
- groupBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
- }
- val summary = when (state) {
- is DownloadState.Cancelled -> {
- progress++
- context.getString(R.string.cancelling_)
- }
-
- is DownloadState.Done -> {
- progress++
- context.getString(R.string.download_complete)
- }
-
- is DownloadState.Error -> {
- isAllDone = false
- context.getString(R.string.error)
- }
-
- is DownloadState.PostProcessing -> {
- progress++
- isInProgress = true
- isAllDone = false
- context.getString(R.string.processing_)
- }
-
- is DownloadState.Preparing -> {
- isAllDone = false
- isInProgress = true
- context.getString(R.string.preparing_)
- }
-
- is DownloadState.Progress -> {
- isAllDone = false
- isInProgress = true
- progress += state.percent
- context.getString(R.string.percent_string_pattern, (state.percent * 100).format())
- }
-
- is DownloadState.Queued -> {
- isAllDone = false
- isInProgress = true
- context.getString(R.string.queued)
- }
- }
- style.addLine(
- context.getString(
- R.string.download_summary_pattern,
- state.manga.title.ellipsize(16).htmlEncode(),
- summary.htmlEncode(),
- ).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY),
- )
- }
- progress = if (isInProgress) {
- progress / states.size.toFloat()
- } else {
- 1f
- }
- style.setBigContentTitle(
- context.getString(if (isAllDone) R.string.download_complete else R.string.downloading_manga),
- )
- groupBuilder.setContentText(context.resources.getQuantityString(R.plurals.items, states.size, states.size()))
- groupBuilder.setNumber(states.size)
- groupBuilder.setSmallIcon(
- if (isInProgress) android.R.drawable.stat_sys_download else android.R.drawable.stat_sys_download_done,
- )
- groupBuilder.setContentIntent(if (isAllDone) localListIntent else queueIntent)
- groupBuilder.setAutoCancel(isAllDone)
- when (progress) {
- 1f -> groupBuilder.setProgress(0, 0, false)
- 0f -> groupBuilder.setProgress(1, 0, true)
- else -> groupBuilder.setProgress(100, (progress * 100f).toInt(), false)
- }
- return groupBuilder.build()
- }
-
- fun detach() {
- if (states.isNotEmpty()) {
- val notification = buildGroupNotification()
- manager.notify(ID_GROUP_DETACHED, notification)
- }
- manager.cancel(ID_GROUP)
- }
-
- fun newItem(startId: Int) = Item(startId)
-
- inner class Item(
- private val startId: Int,
- ) {
-
- private val builder = NotificationCompat.Builder(context, CHANNEL_ID)
- private val cancelAction = NotificationCompat.Action(
- materialR.drawable.material_ic_clear_black_24dp,
- context.getString(android.R.string.cancel),
- PendingIntentCompat.getBroadcast(
- context,
- startId * 2,
- DownloadService.getCancelIntent(startId),
- PendingIntent.FLAG_CANCEL_CURRENT,
- false,
- ),
- )
- private val retryAction = NotificationCompat.Action(
- R.drawable.ic_restart_black,
- context.getString(R.string.try_again),
- PendingIntentCompat.getBroadcast(
- context,
- startId * 2 + 1,
- DownloadService.getResumeIntent(startId),
- PendingIntent.FLAG_CANCEL_CURRENT,
- false,
- ),
- )
-
- init {
- builder.setOnlyAlertOnce(true)
- builder.setDefaults(0)
- builder.color = ContextCompat.getColor(context, R.color.blue_primary)
- builder.foregroundServiceBehavior = NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
- builder.setSilent(true)
- builder.setGroup(GROUP_ID)
- builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
- }
-
- fun notify(state: DownloadState, timeLeft: Long) {
- builder.setContentTitle(state.manga.title)
- builder.setContentText(context.getString(R.string.manga_downloading_))
- builder.setProgress(1, 0, true)
- builder.setSmallIcon(android.R.drawable.stat_sys_download)
- builder.setContentIntent(queueIntent)
- builder.setStyle(null)
- builder.setLargeIcon(state.cover?.toBitmap())
- builder.clearActions()
- builder.setSubText(null)
- builder.setShowWhen(false)
- builder.setVisibility(
- if (state.manga.isNsfw) {
- NotificationCompat.VISIBILITY_PRIVATE
- } else {
- NotificationCompat.VISIBILITY_PUBLIC
- },
- )
- when (state) {
- is DownloadState.Cancelled -> {
- builder.setProgress(1, 0, true)
- builder.setContentText(context.getString(R.string.cancelling_))
- builder.setContentIntent(null)
- builder.setStyle(null)
- builder.setOngoing(true)
- builder.priority = NotificationCompat.PRIORITY_DEFAULT
- }
-
- is DownloadState.Done -> {
- builder.setProgress(0, 0, false)
- builder.setContentText(context.getString(R.string.download_complete))
- builder.setContentIntent(createMangaIntent(context, state.localManga))
- builder.setAutoCancel(true)
- builder.setSmallIcon(android.R.drawable.stat_sys_download_done)
- builder.setCategory(null)
- builder.setStyle(null)
- builder.setOngoing(false)
- builder.setShowWhen(true)
- builder.setWhen(System.currentTimeMillis())
- builder.priority = NotificationCompat.PRIORITY_DEFAULT
- }
-
- is DownloadState.Error -> {
- val message = state.error.getDisplayMessage(context.resources)
- builder.setProgress(0, 0, false)
- builder.setSmallIcon(android.R.drawable.stat_notify_error)
- builder.setSubText(context.getString(R.string.error))
- builder.setContentText(message)
- builder.setAutoCancel(!state.canRetry)
- builder.setOngoing(state.canRetry)
- builder.setCategory(NotificationCompat.CATEGORY_ERROR)
- builder.setShowWhen(true)
- builder.setWhen(System.currentTimeMillis())
- builder.setStyle(NotificationCompat.BigTextStyle().bigText(message))
- if (state.canRetry) {
- builder.addAction(cancelAction)
- builder.addAction(retryAction)
- }
- builder.priority = NotificationCompat.PRIORITY_DEFAULT
- }
-
- is DownloadState.PostProcessing -> {
- builder.setProgress(1, 0, true)
- builder.setContentText(context.getString(R.string.processing_))
- builder.setStyle(null)
- builder.setOngoing(true)
- builder.priority = NotificationCompat.PRIORITY_DEFAULT
- }
-
- is DownloadState.Queued -> {
- builder.setProgress(0, 0, false)
- builder.setContentText(context.getString(R.string.queued))
- builder.setStyle(null)
- builder.setOngoing(true)
- builder.addAction(cancelAction)
- builder.priority = NotificationCompat.PRIORITY_LOW
- }
-
- is DownloadState.Preparing -> {
- builder.setProgress(1, 0, true)
- builder.setContentText(context.getString(R.string.preparing_))
- builder.setStyle(null)
- builder.setOngoing(true)
- builder.addAction(cancelAction)
- builder.priority = NotificationCompat.PRIORITY_DEFAULT
- }
-
- is DownloadState.Progress -> {
- builder.setProgress(state.max, state.progress, false)
- val percent = context.getString(R.string.percent_string_pattern, (state.percent * 100).format())
- if (timeLeft > 0L) {
- val eta = DateUtils.getRelativeTimeSpanString(timeLeft, 0L, DateUtils.SECOND_IN_MILLIS)
- builder.setContentText(eta)
- builder.setSubText(percent)
- } else {
- builder.setContentText(percent)
- }
- builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
- builder.setStyle(null)
- builder.setOngoing(true)
- builder.addAction(cancelAction)
- builder.priority = NotificationCompat.PRIORITY_DEFAULT
- }
- }
- val notification = builder.build()
- states.append(startId, state)
- updateGroupNotification()
- manager.notify(TAG, startId, notification)
- }
-
- fun dismiss() {
- manager.cancel(TAG, startId)
- states.remove(startId)
- updateGroupNotification()
- }
- }
-
- private fun updateGroupNotification() {
- val notification = buildGroupNotification()
- manager.notify(ID_GROUP, notification)
- }
-
- private fun createMangaIntent(context: Context, manga: Manga) = PendingIntentCompat.getActivity(
- context,
- manga.hashCode(),
- DetailsActivity.newIntent(context, manga),
- PendingIntent.FLAG_CANCEL_CURRENT,
- false,
- )
-
- companion object {
-
- private const val TAG = "download"
- private const val CHANNEL_ID = "download"
- private const val GROUP_ID = "downloads"
- private const val REQUEST_QUEUE = 6
- private const val REQUEST_LIST_LOCAL = 7
- const val ID_GROUP = 9999
- private const val ID_GROUP_DETACHED = 9998
-
- fun createChannel(context: Context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val manager = NotificationManagerCompat.from(context)
- if (manager.getNotificationChannel(CHANNEL_ID) == null) {
- val channel = NotificationChannel(
- CHANNEL_ID,
- context.getString(R.string.downloads),
- NotificationManager.IMPORTANCE_LOW,
- )
- channel.enableVibration(false)
- channel.enableLights(false)
- channel.setSound(null, null)
- manager.createNotificationChannel(channel)
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt
deleted file mode 100644
index 8dd780d45..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt
+++ /dev/null
@@ -1,262 +0,0 @@
-package org.koitharu.kotatsu.download.ui.service
-
-import android.app.DownloadManager.ACTION_DOWNLOAD_COMPLETE
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.Binder
-import android.os.IBinder
-import android.os.PowerManager
-import android.view.View
-import androidx.annotation.MainThread
-import androidx.core.app.ServiceCompat
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.transformWhile
-import kotlinx.coroutines.launch
-import org.koitharu.kotatsu.BuildConfig
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.BaseService
-import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
-import org.koitharu.kotatsu.download.domain.DownloadManager
-import org.koitharu.kotatsu.download.domain.DownloadState
-import org.koitharu.kotatsu.download.ui.DownloadsActivity
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
-import org.koitharu.kotatsu.utils.ext.throttle
-import org.koitharu.kotatsu.utils.progress.PausingProgressJob
-import org.koitharu.kotatsu.utils.progress.ProgressJob
-import org.koitharu.kotatsu.utils.progress.TimeLeftEstimator
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-import kotlin.collections.set
-
-@AndroidEntryPoint
-class DownloadService : BaseService() {
-
- private lateinit var downloadNotification: DownloadNotification
- private lateinit var wakeLock: PowerManager.WakeLock
-
- @Inject
- lateinit var downloadManager: DownloadManager
-
- private val jobs = LinkedHashMap>()
- private val jobCount = MutableStateFlow(0)
- private val controlReceiver = ControlReceiver()
-
- override fun onCreate() {
- super.onCreate()
- downloadNotification = DownloadNotification(this)
- wakeLock = (applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
- .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading")
- wakeLock.acquire(TimeUnit.HOURS.toMillis(8))
- DownloadNotification.createChannel(this)
- startForeground(DownloadNotification.ID_GROUP, downloadNotification.buildGroupNotification())
- val intentFilter = IntentFilter()
- intentFilter.addAction(ACTION_DOWNLOAD_CANCEL)
- intentFilter.addAction(ACTION_DOWNLOAD_RESUME)
- ContextCompat.registerReceiver(this, controlReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED)
- }
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- super.onStartCommand(intent, flags, startId)
- val manga = intent?.getParcelableExtraCompat(EXTRA_MANGA)?.manga
- val chapters = intent?.getLongArrayExtra(EXTRA_CHAPTERS_IDS)
- return if (manga != null) {
- jobs[startId] = downloadManga(startId, manga, chapters)
- jobCount.value = jobs.size
- START_REDELIVER_INTENT
- } else {
- stopSelfIfIdle()
- START_NOT_STICKY
- }
- }
-
- override fun onBind(intent: Intent): IBinder {
- super.onBind(intent)
- return DownloadBinder(this)
- }
-
- override fun onDestroy() {
- unregisterReceiver(controlReceiver)
- if (wakeLock.isHeld) {
- wakeLock.release()
- }
- super.onDestroy()
- }
-
- private fun downloadManga(
- startId: Int,
- manga: Manga,
- chaptersIds: LongArray?,
- ): PausingProgressJob {
- val job = downloadManager.downloadManga(manga, chaptersIds, startId)
- listenJob(job)
- return job
- }
-
- private fun listenJob(job: ProgressJob) {
- lifecycleScope.launch {
- val startId = job.progressValue.startId
- val notificationItem = downloadNotification.newItem(startId)
- try {
- val timeLeftEstimator = TimeLeftEstimator()
- notificationItem.notify(job.progressValue, -1L)
- job.progressAsFlow()
- .onEach { state ->
- if (state is DownloadState.Progress) {
- timeLeftEstimator.tick(value = state.progress, total = state.max)
- } else {
- timeLeftEstimator.emptyTick()
- }
- }
- .throttle { state -> if (state is DownloadState.Progress) 400L else 0L }
- .whileActive()
- .collect { state ->
- val timeLeft = timeLeftEstimator.getEstimatedTimeLeft()
- notificationItem.notify(state, timeLeft)
- }
- job.join()
- } finally {
- (job.progressValue as? DownloadState.Done)?.let {
- sendBroadcast(
- Intent(ACTION_DOWNLOAD_COMPLETE)
- .putExtra(EXTRA_MANGA, ParcelableManga(it.localManga, withChapters = false)),
- )
- }
- if (job.isCancelled) {
- notificationItem.dismiss()
- if (jobs.remove(startId) != null) {
- jobCount.value = jobs.size
- }
- } else {
- notificationItem.notify(job.progressValue, -1L)
- }
- }
- }.invokeOnCompletion {
- stopSelfIfIdle()
- }
- }
-
- private fun Flow.whileActive(): Flow = transformWhile { state ->
- emit(state)
- !state.isTerminal
- }
-
- @MainThread
- private fun stopSelfIfIdle() {
- if (jobs.any { (_, job) -> job.isActive }) {
- return
- }
- downloadNotification.detach()
- ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
- stopSelf()
- }
-
- inner class ControlReceiver : BroadcastReceiver() {
-
- override fun onReceive(context: Context, intent: Intent?) {
- when (intent?.action) {
- ACTION_DOWNLOAD_CANCEL -> {
- val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
- jobs[cancelId]?.cancel()
- }
-
- ACTION_DOWNLOAD_RESUME -> {
- val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
- jobs[cancelId]?.resume()
- }
- }
- }
- }
-
- class DownloadBinder(service: DownloadService) : Binder(), DefaultLifecycleObserver {
-
- private var downloadsStateFlow = MutableStateFlow>>(emptyList())
-
- init {
- service.lifecycle.addObserver(this)
- service.jobCount.onEach {
- downloadsStateFlow.value = service.jobs.values.toList()
- }.launchIn(service.lifecycleScope)
- }
-
- override fun onDestroy(owner: LifecycleOwner) {
- owner.lifecycle.removeObserver(this)
- downloadsStateFlow.value = emptyList()
- super.onDestroy(owner)
- }
-
- val downloads
- get() = downloadsStateFlow.asStateFlow()
- }
-
- companion object {
-
- private const val ACTION_DOWNLOAD_CANCEL = "${BuildConfig.APPLICATION_ID}.action.ACTION_DOWNLOAD_CANCEL"
- private const val ACTION_DOWNLOAD_RESUME = "${BuildConfig.APPLICATION_ID}.action.ACTION_DOWNLOAD_RESUME"
-
- const val EXTRA_MANGA = "manga"
- private const val EXTRA_CHAPTERS_IDS = "chapters_ids"
- private const val EXTRA_CANCEL_ID = "cancel_id"
-
- fun start(view: View, manga: Manga, chaptersIds: Collection? = null) {
- if (chaptersIds?.isEmpty() == true) {
- return
- }
- val intent = Intent(view.context, DownloadService::class.java)
- intent.putExtra(EXTRA_MANGA, ParcelableManga(manga, withChapters = false))
- if (chaptersIds != null) {
- intent.putExtra(EXTRA_CHAPTERS_IDS, chaptersIds.toLongArray())
- }
- ContextCompat.startForegroundService(view.context, intent)
- showStartedSnackbar(view)
- }
-
- fun start(view: View, manga: Collection) {
- if (manga.isEmpty()) {
- return
- }
- for (item in manga) {
- val intent = Intent(view.context, DownloadService::class.java)
- intent.putExtra(EXTRA_MANGA, ParcelableManga(item, withChapters = false))
- ContextCompat.startForegroundService(view.context, intent)
- }
- showStartedSnackbar(view)
- }
-
- fun confirmAndStart(view: View, items: Set) {
- MaterialAlertDialogBuilder(view.context)
- .setTitle(R.string.save_manga)
- .setMessage(R.string.batch_manga_save_confirm)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(R.string.save) { _, _ ->
- start(view, items)
- }.show()
- }
-
- fun getCancelIntent(startId: Int) = Intent(ACTION_DOWNLOAD_CANCEL)
- .putExtra(EXTRA_CANCEL_ID, startId)
-
- fun getResumeIntent(startId: Int) = Intent(ACTION_DOWNLOAD_RESUME)
- .putExtra(EXTRA_CANCEL_ID, startId)
-
- private fun showStartedSnackbar(view: View) {
- Snackbar.make(view, R.string.download_started, Snackbar.LENGTH_LONG)
- .setAction(R.string.details) {
- it.context.startActivity(DownloadsActivity.newIntent(it.context))
- }.show()
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/explore/domain/ExploreRepository.kt b/app/src/main/java/org/koitharu/kotatsu/explore/domain/ExploreRepository.kt
deleted file mode 100644
index f8bc28e98..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/explore/domain/ExploreRepository.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.koitharu.kotatsu.explore.domain
-
-import javax.inject.Inject
-import org.koitharu.kotatsu.core.parser.MangaRepository
-import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.history.domain.HistoryRepository
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.SortOrder
-
-class ExploreRepository @Inject constructor(
- private val settings: AppSettings,
- private val historyRepository: HistoryRepository,
- private val mangaRepositoryFactory: MangaRepository.Factory,
-) {
-
- suspend fun findRandomManga(tagsLimit: Int): Manga {
- val blacklistTagRegex = settings.getSuggestionsTagsBlacklistRegex()
- val allTags = historyRepository.getPopularTags(tagsLimit).filterNot {
- blacklistTagRegex?.containsMatchIn(it.title) ?: false
- }
- val tag = allTags.randomOrNull()
- val source = checkNotNull(tag?.source ?: settings.getMangaSources(includeHidden = false).randomOrNull()) {
- "No sources found"
- }
- val repo = mangaRepositoryFactory.create(source)
- val list = repo.getList(
- offset = 0,
- sortOrder = if (SortOrder.UPDATED in repo.sortOrders) SortOrder.UPDATED else null,
- tags = setOfNotNull(tag),
- ).shuffled()
- for (item in list) {
- if (settings.isSuggestionsExcludeNsfw && item.isNsfw) {
- continue
- }
- if (blacklistTagRegex != null && item.tags.any { x -> blacklistTagRegex.containsMatchIn(x.title) }) {
- continue
- }
- return item
- }
- return list.random()
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt
deleted file mode 100644
index 03ec95536..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.koitharu.kotatsu.list.ui
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.plus
-import org.koitharu.kotatsu.base.ui.BaseViewModel
-import org.koitharu.kotatsu.base.ui.util.ReversibleAction
-import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.core.prefs.observeAsFlow
-import org.koitharu.kotatsu.core.prefs.observeAsLiveData
-import org.koitharu.kotatsu.list.ui.model.ListModel
-import org.koitharu.kotatsu.parsers.model.MangaTag
-import org.koitharu.kotatsu.utils.SingleLiveEvent
-import org.koitharu.kotatsu.utils.asFlowLiveData
-
-abstract class MangaListViewModel(
- private val settings: AppSettings,
-) : BaseViewModel() {
-
- abstract val content: LiveData>
- protected val listModeFlow = settings.observeAsFlow(AppSettings.KEY_LIST_MODE) { listMode }
- .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, settings.listMode)
- val listMode = listModeFlow.asFlowLiveData(viewModelScope.coroutineContext)
- val onActionDone = SingleLiveEvent()
- val gridScale = settings.observeAsLiveData(
- context = viewModelScope.coroutineContext + Dispatchers.Default,
- key = AppSettings.KEY_GRID_SIZE,
- valueProducer = { gridSize / 100f },
- )
-
- open fun onUpdateFilter(tags: Set) = Unit
-
- abstract fun onRefresh()
-
- abstract fun onRetry()
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/LoadingFooter.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/LoadingFooter.kt
deleted file mode 100644
index 7a754dcc6..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/LoadingFooter.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.koitharu.kotatsu.list.ui.model
-
-object LoadingFooter : ListModel {
-
- override fun equals(other: Any?): Boolean = other === LoadingFooter
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/output/LocalMangaOutput.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/output/LocalMangaOutput.kt
deleted file mode 100644
index e3445f387..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/local/data/output/LocalMangaOutput.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.koitharu.kotatsu.local.data.output
-
-import okio.Closeable
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaChapter
-import org.koitharu.kotatsu.parsers.util.toFileNameSafe
-import java.io.File
-
-sealed class LocalMangaOutput(
- val rootFile: File,
-) : Closeable {
-
- abstract suspend fun mergeWithExisting()
-
- abstract suspend fun addCover(file: File, ext: String)
-
- abstract suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String)
-
- abstract suspend fun flushChapter(chapter: MangaChapter): Boolean
-
- abstract suspend fun finish()
-
- abstract suspend fun cleanup()
-
- companion object {
-
- const val ENTRY_NAME_INDEX = "index.json"
- const val SUFFIX_TMP = ".tmp"
-
- fun getOrCreate(root: File, manga: Manga): LocalMangaOutput {
- return checkNotNull(getImpl(root, manga, onlyIfExists = false))
- }
-
- fun get(root: File, manga: Manga): LocalMangaOutput? {
- return getImpl(root, manga, onlyIfExists = true)
- }
-
- private fun getImpl(root: File, manga: Manga, onlyIfExists: Boolean): LocalMangaOutput? {
- val fileName = manga.title.toFileNameSafe()
- val dir = File(root, fileName)
- val zip = File(root, "$fileName.cbz")
- return when {
- dir.isDirectory -> LocalMangaDirOutput(dir, manga)
- zip.isFile -> LocalMangaZipOutput(zip, manga)
- !onlyIfExists -> LocalMangaDirOutput(dir, manga)
- else -> null
- }
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/OnPageSelectListener.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/OnPageSelectListener.kt
deleted file mode 100644
index 38db30b5e..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/OnPageSelectListener.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.koitharu.kotatsu.reader.ui.thumbnails
-
-import org.koitharu.kotatsu.parsers.model.MangaPage
-
-fun interface OnPageSelectListener {
-
- fun onPageSelected(page: MangaPage)
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnail.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnail.kt
deleted file mode 100644
index 22c5ddad5..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnail.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.koitharu.kotatsu.reader.ui.thumbnails
-
-import org.koitharu.kotatsu.core.parser.MangaRepository
-import org.koitharu.kotatsu.parsers.model.MangaPage
-
-data class PageThumbnail(
- val number: Int,
- val isCurrent: Boolean,
- val repository: MangaRepository,
- val page: MangaPage
-)
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt
deleted file mode 100644
index 0d9ca8b22..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-package org.koitharu.kotatsu.reader.ui.thumbnails
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.FragmentManager
-import androidx.recyclerview.widget.GridLayoutManager
-import coil.ImageLoader
-import dagger.hilt.android.AndroidEntryPoint
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.BaseBottomSheet
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
-import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
-import org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar
-import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPages
-import org.koitharu.kotatsu.core.parser.MangaRepository
-import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.databinding.SheetPagesBinding
-import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
-import org.koitharu.kotatsu.parsers.model.MangaPage
-import org.koitharu.kotatsu.reader.domain.PageLoader
-import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
-import org.koitharu.kotatsu.utils.ext.getParcelableCompat
-import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
-import org.koitharu.kotatsu.utils.ext.withArgs
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class PagesThumbnailsSheet :
- BaseBottomSheet(),
- OnListItemClickListener,
- BottomSheetHeaderBar.OnExpansionChangeListener {
-
- @Inject
- lateinit var mangaRepositoryFactory: MangaRepository.Factory
-
- @Inject
- lateinit var pageLoader: PageLoader
-
- @Inject
- lateinit var coil: ImageLoader
-
- @Inject
- lateinit var settings: AppSettings
-
- private lateinit var thumbnails: List
- private var spanResolver: MangaListSpanResolver? = null
- private var currentPageIndex = -1
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val pages = arguments?.getParcelableCompat(ARG_PAGES)?.pages
- if (pages.isNullOrEmpty()) {
- dismissAllowingStateLoss()
- return
- }
- currentPageIndex = requireArguments().getInt(ARG_CURRENT, currentPageIndex)
- val repository = mangaRepositoryFactory.create(pages.first().source)
- thumbnails = pages.mapIndexed { i, x ->
- PageThumbnail(
- number = i + 1,
- isCurrent = i == currentPageIndex,
- repository = repository,
- page = x,
- )
- }
- }
-
- override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetPagesBinding {
- return SheetPagesBinding.inflate(inflater, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- spanResolver = MangaListSpanResolver(view.resources)
- with(binding.headerBar) {
- title = arguments?.getString(ARG_TITLE)
- subtitle = null
- addOnExpansionChangeListener(this@PagesThumbnailsSheet)
- }
-
- with(binding.recyclerView) {
- addItemDecoration(
- SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)),
- )
- adapter = PageThumbnailAdapter(
- dataSet = thumbnails,
- coil = coil,
- scope = viewLifecycleScope,
- loader = pageLoader,
- clickListener = this@PagesThumbnailsSheet,
- )
- addOnLayoutChangeListener(spanResolver)
- spanResolver?.setGridSize(settings.gridSize / 100f, this)
- if (currentPageIndex > 0) {
- val offset = resources.getDimensionPixelOffset(R.dimen.preferred_grid_width)
- (layoutManager as GridLayoutManager).scrollToPositionWithOffset(currentPageIndex, offset)
- }
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- spanResolver = null
- }
-
- override fun onItemClick(item: MangaPage, view: View) {
- (
- (parentFragment as? OnPageSelectListener)
- ?: (activity as? OnPageSelectListener)
- )?.run {
- onPageSelected(item)
- dismiss()
- }
- }
-
- override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) {
- if (isExpanded) {
- headerBar.subtitle = resources.getQuantityString(
- R.plurals.pages,
- thumbnails.size,
- thumbnails.size,
- )
- } else {
- headerBar.subtitle = null
- }
- }
-
- companion object {
-
- private const val ARG_PAGES = "pages"
- private const val ARG_TITLE = "title"
- private const val ARG_CURRENT = "current"
-
- private const val TAG = "PagesThumbnailsSheet"
-
- fun show(fm: FragmentManager, pages: List, title: String, currentPage: Int) =
- PagesThumbnailsSheet().withArgs(3) {
- putParcelable(ARG_PAGES, ParcelableMangaPages(pages))
- putString(ARG_TITLE, title)
- putInt(ARG_CURRENT, currentPage)
- }.show(fm, TAG)
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt
deleted file mode 100644
index 7d2c6c3bd..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.koitharu.kotatsu.reader.ui.thumbnails.adapter
-
-import android.graphics.drawable.Drawable
-import coil.ImageLoader
-import coil.request.ImageRequest
-import coil.size.Scale
-import coil.size.Size
-import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
-import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
-import org.koitharu.kotatsu.parsers.model.MangaPage
-import org.koitharu.kotatsu.reader.domain.PageLoader
-import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
-import org.koitharu.kotatsu.utils.ext.decodeRegion
-import org.koitharu.kotatsu.utils.ext.isLowRamDevice
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
-import org.koitharu.kotatsu.utils.ext.setTextColorAttr
-import com.google.android.material.R as materialR
-
-fun pageThumbnailAD(
- coil: ImageLoader,
- scope: CoroutineScope,
- loader: PageLoader,
- clickListener: OnListItemClickListener,
-) = adapterDelegateViewBinding(
- { inflater, parent -> ItemPageThumbBinding.inflate(inflater, parent, false) },
-) {
- var job: Job? = null
- val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width)
- val thumbSize = Size(
- width = gridWidth,
- height = (gridWidth / 13f * 18f).toInt(),
- )
-
- suspend fun loadPageThumbnail(item: PageThumbnail): Drawable? = withContext(Dispatchers.Default) {
- item.page.preview?.let { url ->
- coil.execute(
- ImageRequest.Builder(context)
- .data(url)
- .tag(item.page.source)
- .size(thumbSize)
- .scale(Scale.FILL)
- .allowRgb565(true)
- .build(),
- ).drawable
- }?.let { drawable ->
- return@withContext drawable
- }
- val file = loader.loadPage(item.page, force = false)
- coil.execute(
- ImageRequest.Builder(context)
- .data(file)
- .size(thumbSize)
- .decodeRegion(0)
- .allowRgb565(isLowRamDevice(context))
- .build(),
- ).drawable
- }
-
- binding.root.setOnClickListener {
- clickListener.onItemClick(item.page, itemView)
- }
-
- bind {
- job?.cancel()
- binding.imageViewThumb.setImageDrawable(null)
- with(binding.textViewNumber) {
- setBackgroundResource(if (item.isCurrent) R.drawable.bg_badge_accent else R.drawable.bg_badge_empty)
- setTextColorAttr(if (item.isCurrent) materialR.attr.colorOnTertiary else android.R.attr.textColorPrimary)
- text = (item.number).toString()
- }
- job = scope.launch {
- val drawable = runCatchingCancellable {
- loadPageThumbnail(item)
- }.getOrNull()
- binding.imageViewThumb.setImageDrawable(drawable)
- }
- }
-
- onViewRecycled {
- job?.cancel()
- job = null
- binding.imageViewThumb.setImageDrawable(null)
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt
deleted file mode 100644
index b293d2865..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.koitharu.kotatsu.reader.ui.thumbnails.adapter
-
-import coil.ImageLoader
-import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
-import kotlinx.coroutines.CoroutineScope
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
-import org.koitharu.kotatsu.parsers.model.MangaPage
-import org.koitharu.kotatsu.reader.domain.PageLoader
-import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
-
-class PageThumbnailAdapter(
- dataSet: List,
- coil: ImageLoader,
- scope: CoroutineScope,
- loader: PageLoader,
- clickListener: OnListItemClickListener
-) : ListDelegationAdapter>() {
-
- init {
- delegatesManager.addDelegate(pageThumbnailAD(coil, scope, loader, clickListener))
- setItems(dataSet)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/domain/ShelfSection.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/domain/ShelfSection.kt
deleted file mode 100644
index de24a9583..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/shelf/domain/ShelfSection.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.koitharu.kotatsu.shelf.domain
-
-enum class ShelfSection {
-
- HISTORY, LOCAL, UPDATED, FAVORITES;
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt
deleted file mode 100644
index 813208aa4..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.koitharu.kotatsu.suggestions.ui
-
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.content.Context
-import android.os.Build
-import androidx.annotation.FloatRange
-import androidx.core.app.NotificationCompat
-import androidx.core.content.ContextCompat
-import androidx.hilt.work.HiltWorker
-import androidx.work.*
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.core.parser.MangaRepository
-import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.history.domain.HistoryRepository
-import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.model.MangaTag
-import org.koitharu.kotatsu.parsers.model.SortOrder
-import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion
-import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
-import org.koitharu.kotatsu.utils.ext.asArrayList
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
-import org.koitharu.kotatsu.utils.ext.trySetForeground
-import java.util.concurrent.TimeUnit
-import kotlin.math.pow
-
-@HiltWorker
-class SuggestionsWorker @AssistedInject constructor(
- @Assisted appContext: Context,
- @Assisted params: WorkerParameters,
- private val suggestionRepository: SuggestionRepository,
- private val historyRepository: HistoryRepository,
- private val appSettings: AppSettings,
- private val mangaRepositoryFactory: MangaRepository.Factory,
-) : CoroutineWorker(appContext, params) {
-
- override suspend fun doWork(): Result {
- val count = doWorkImpl()
- val outputData = workDataOf(DATA_COUNT to count)
- return Result.success(outputData)
- }
-
- override suspend fun getForegroundInfo(): ForegroundInfo {
- val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- val title = applicationContext.getString(R.string.suggestions_updating)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val channel = NotificationChannel(
- WORKER_CHANNEL_ID,
- title,
- NotificationManager.IMPORTANCE_LOW,
- )
- channel.setShowBadge(false)
- channel.enableVibration(false)
- channel.setSound(null, null)
- channel.enableLights(false)
- manager.createNotificationChannel(channel)
- }
-
- val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID)
- .setContentTitle(title)
- .setPriority(NotificationCompat.PRIORITY_MIN)
- .setDefaults(0)
- .setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark))
- .setSilent(true)
- .setProgress(0, 0, true)
- .setSmallIcon(android.R.drawable.stat_notify_sync)
- .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
- .setOngoing(true)
- .build()
-
- return ForegroundInfo(WORKER_NOTIFICATION_ID, notification)
- }
-
- private suspend fun doWorkImpl(): Int {
- if (!appSettings.isSuggestionsEnabled) {
- suggestionRepository.clear()
- return 0
- }
- val blacklistTagRegex = appSettings.getSuggestionsTagsBlacklistRegex()
- val allTags = historyRepository.getPopularTags(TAGS_LIMIT).filterNot {
- blacklistTagRegex?.containsMatchIn(it.title) ?: false
- }
- if (allTags.isEmpty()) {
- return 0
- }
- if (TAG in tags) { // not expedited
- trySetForeground()
- }
- val tagsBySources = allTags.groupBy { x -> x.source }
- val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
- val rawResults = coroutineScope {
- tagsBySources.flatMap { (source, tags) ->
- val repo = mangaRepositoryFactory.tryCreate(source) ?: return@flatMap emptyList()
- tags.map { tag ->
- async(dispatcher) {
- repo.getListSafe(tag)
- }
- }
- }.awaitAll().flatten().asArrayList()
- }
- if (appSettings.isSuggestionsExcludeNsfw) {
- rawResults.removeAll { it.isNsfw }
- }
- if (blacklistTagRegex != null) {
- rawResults.removeAll {
- it.tags.any { x -> blacklistTagRegex.containsMatchIn(x.title) }
- }
- }
- if (rawResults.isEmpty()) {
- return 0
- }
- val suggestions = rawResults.distinctBy { manga ->
- manga.id
- }.map { manga ->
- MangaSuggestion(
- manga = manga,
- relevance = computeRelevance(manga.tags, allTags),
- )
- }.sortedBy { it.relevance }.take(LIMIT)
- suggestionRepository.replace(suggestions)
- return suggestions.size
- }
-
- @FloatRange(from = 0.0, to = 1.0)
- private fun computeRelevance(mangaTags: Set, allTags: List): Float {
- val maxWeight = (allTags.size + allTags.size + 1 - mangaTags.size) * mangaTags.size / 2.0
- val weight = mangaTags.sumOf { tag ->
- val index = allTags.indexOf(tag)
- if (index < 0) 0 else allTags.size - index
- }
- return (weight / maxWeight).pow(2.0).toFloat()
- }
-
- private suspend fun MangaRepository.getListSafe(tag: MangaTag) = runCatchingCancellable {
- getList(offset = 0, sortOrder = SortOrder.UPDATED, tags = setOf(tag))
- }.onFailure { error ->
- error.printStackTraceDebug()
- }.getOrDefault(emptyList())
-
- private fun MangaRepository.Factory.tryCreate(source: MangaSource) = runCatching {
- create(source)
- }.onFailure { error ->
- error.printStackTraceDebug()
- }.getOrNull()
-
- companion object {
-
- private const val TAG = "suggestions"
- private const val TAG_ONESHOT = "suggestions_oneshot"
- private const val LIMIT = 140
- private const val TAGS_LIMIT = 20
- private const val MAX_PARALLELISM = 4
- private const val DATA_COUNT = "count"
- private const val WORKER_CHANNEL_ID = "suggestion_worker"
- private const val WORKER_NOTIFICATION_ID = 36
-
- fun setup(context: Context) {
- val constraints = Constraints.Builder()
- .setRequiredNetworkType(NetworkType.UNMETERED)
- .setRequiresBatteryNotLow(true)
- .build()
- val request = PeriodicWorkRequestBuilder(6, TimeUnit.HOURS)
- .setConstraints(constraints)
- .addTag(TAG)
- .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
- .build()
- WorkManager.getInstance(context)
- .enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.KEEP, request)
- }
-
- fun startNow(context: Context) {
- val constraints = Constraints.Builder()
- .setRequiredNetworkType(NetworkType.CONNECTED)
- .build()
- val request = OneTimeWorkRequestBuilder()
- .setConstraints(constraints)
- .addTag(TAG_ONESHOT)
- .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
- .build()
- WorkManager.getInstance(context)
- .enqueue(request)
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt
deleted file mode 100644
index a5343ade5..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.koitharu.kotatsu.sync.ui
-
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.Dispatchers
-import org.koitharu.kotatsu.base.ui.BaseViewModel
-import org.koitharu.kotatsu.sync.data.SyncAuthApi
-import org.koitharu.kotatsu.sync.domain.SyncAuthResult
-import org.koitharu.kotatsu.utils.SingleLiveEvent
-import javax.inject.Inject
-
-@HiltViewModel
-class SyncAuthViewModel @Inject constructor(
- private val api: SyncAuthApi,
-) : BaseViewModel() {
-
- val onTokenObtained = SingleLiveEvent()
-
- fun obtainToken(email: String, password: String) {
- launchLoadingJob(Dispatchers.Default) {
- val token = api.authenticate(email, password)
- val result = SyncAuthResult(email, password, token)
- onTokenObtained.emitCall(result)
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/FlowLiveData.kt b/app/src/main/java/org/koitharu/kotatsu/utils/FlowLiveData.kt
deleted file mode 100644
index 797893609..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/FlowLiveData.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.koitharu.kotatsu.utils
-
-import androidx.lifecycle.LiveData
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.FlowCollector
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-
-private const val DEFAULT_TIMEOUT = 5_000L
-
-/**
- * Similar to a CoroutineLiveData but optimized for using within infinite flows
- */
-class FlowLiveData(
- private val flow: Flow,
- defaultValue: T,
- context: CoroutineContext = EmptyCoroutineContext,
- private val timeoutInMs: Long = DEFAULT_TIMEOUT,
-) : LiveData(defaultValue) {
-
- private val scope = CoroutineScope(Dispatchers.Main.immediate + context + SupervisorJob(context[Job]))
- private var job: Job? = null
- private var cancellationJob: Job? = null
-
- override fun onActive() {
- super.onActive()
- cancellationJob?.cancel()
- cancellationJob = null
- if (job?.isActive == true) {
- return
- }
- job = scope.launch {
- flow.collect(Collector())
- }
- }
-
- override fun onInactive() {
- super.onInactive()
- cancellationJob?.cancel()
- cancellationJob = scope.launch(Dispatchers.Main.immediate) {
- delay(timeoutInMs)
- if (!hasActiveObservers()) {
- job?.cancel()
- job = null
- }
- }
- }
-
- private inner class Collector : FlowCollector {
-
- private var previousValue: Any? = value
- private val dispatcher = Dispatchers.Main.immediate
-
- override suspend fun emit(value: T) {
- if (previousValue != value) {
- previousValue = value
- if (dispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
- withContext(dispatcher) {
- setValue(value)
- }
- } else {
- setValue(value)
- }
- }
- }
- }
-}
-
-fun Flow.asFlowLiveData(
- context: CoroutineContext = EmptyCoroutineContext,
- defaultValue: T,
- timeoutInMs: Long = DEFAULT_TIMEOUT,
-): LiveData = FlowLiveData(this, defaultValue, context, timeoutInMs)
-
-fun StateFlow.asFlowLiveData(
- context: CoroutineContext = EmptyCoroutineContext,
- timeoutInMs: Long = DEFAULT_TIMEOUT,
-): LiveData = FlowLiveData(this, value, context, timeoutInMs)
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/GZipInterceptor.kt b/app/src/main/java/org/koitharu/kotatsu/utils/GZipInterceptor.kt
deleted file mode 100644
index 5da93ae8a..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/GZipInterceptor.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.koitharu.kotatsu.utils
-
-import okhttp3.Interceptor
-import okhttp3.Response
-import org.koitharu.kotatsu.core.network.CommonHeaders.CONTENT_ENCODING
-
-class GZipInterceptor : Interceptor {
-
- override fun intercept(chain: Interceptor.Chain): Response {
- val newRequest = chain.request().newBuilder()
- newRequest.addHeader(CONTENT_ENCODING, "gzip")
- return chain.proceed(newRequest.build())
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/PreferenceIconTarget.kt b/app/src/main/java/org/koitharu/kotatsu/utils/PreferenceIconTarget.kt
deleted file mode 100644
index edece17d7..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/PreferenceIconTarget.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.koitharu.kotatsu.utils
-
-import android.graphics.drawable.Drawable
-import androidx.preference.Preference
-import coil.target.Target
-
-class PreferenceIconTarget(
- private val preference: Preference,
-) : Target {
-
- override fun onError(error: Drawable?) {
- preference.icon = error
- }
-
- override fun onStart(placeholder: Drawable?) {
- preference.icon = placeholder
- }
-
- override fun onSuccess(result: Drawable) {
- preference.icon = result
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/SingleLiveEvent.kt b/app/src/main/java/org/koitharu/kotatsu/utils/SingleLiveEvent.kt
deleted file mode 100644
index cbc89d96b..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/SingleLiveEvent.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.koitharu.kotatsu.utils
-
-import androidx.annotation.AnyThread
-import androidx.annotation.MainThread
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.Observer
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.coroutines.EmptyCoroutineContext
-
-class SingleLiveEvent : LiveData() {
-
- private val pending = AtomicBoolean(false)
-
- override fun observe(owner: LifecycleOwner, observer: Observer) {
- super.observe(owner) {
- if (pending.compareAndSet(true, false)) {
- observer.onChanged(it)
- }
- }
- }
-
- override fun setValue(value: T) {
- pending.set(true)
- super.setValue(value)
- }
-
- @MainThread
- fun call(newValue: T) {
- setValue(newValue)
- }
-
- @AnyThread
- fun postCall(newValue: T) {
- postValue(newValue)
- }
-
- suspend fun emitCall(newValue: T) {
- val dispatcher = Dispatchers.Main.immediate
- if (dispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
- withContext(dispatcher) {
- setValue(newValue)
- }
- } else {
- setValue(newValue)
- }
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/IO.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/IO.kt
deleted file mode 100644
index 1b05eddc3..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/IO.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.koitharu.kotatsu.utils.ext
-
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.currentCoroutineContext
-import kotlinx.coroutines.ensureActive
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.withContext
-import okhttp3.ResponseBody
-import org.koitharu.kotatsu.utils.progress.ProgressResponseBody
-import java.io.InputStream
-import java.io.OutputStream
-
-suspend fun InputStream.copyToSuspending(
- out: OutputStream,
- bufferSize: Int = DEFAULT_BUFFER_SIZE,
- progressState: MutableStateFlow? = null,
-): Long = withContext(Dispatchers.IO) {
- val job = currentCoroutineContext()[Job]
- val total = available()
- var bytesCopied: Long = 0
- val buffer = ByteArray(bufferSize)
- var bytes = read(buffer)
- while (bytes >= 0) {
- out.write(buffer, 0, bytes)
- bytesCopied += bytes
- job?.ensureActive()
- bytes = read(buffer)
- job?.ensureActive()
- if (progressState != null && total > 0) {
- progressState.value = bytesCopied / total.toFloat()
- }
- }
- bytesCopied
-}
-
-fun ResponseBody.withProgress(progressState: MutableStateFlow): ResponseBody {
- return ProgressResponseBody(this, progressState)
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt
deleted file mode 100644
index 0f0ac0180..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.koitharu.kotatsu.utils.ext
-
-import android.view.View
-import androidx.core.graphics.Insets
-
-fun Insets.end(view: View): Int {
- return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) left else right
-}
-
-fun Insets.start(view: View): Int {
- return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) right else left
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LayoutManagerExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LayoutManagerExt.kt
deleted file mode 100644
index 57e9c4a8f..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LayoutManagerExt.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.koitharu.kotatsu.utils.ext
-
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.StaggeredGridLayoutManager
-
-internal val RecyclerView.LayoutManager?.firstVisibleItemPosition
- get() = when (this) {
- is LinearLayoutManager -> findFirstVisibleItemPosition()
- is StaggeredGridLayoutManager -> findFirstVisibleItemPositions(null)[0]
- else -> 0
- }
-
-internal val RecyclerView.LayoutManager?.isLayoutReversed
- get() = when (this) {
- is LinearLayoutManager -> reverseLayout
- is StaggeredGridLayoutManager -> reverseLayout
- else -> false
- }
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt
deleted file mode 100644
index 7f23b9487..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.koitharu.kotatsu.utils.ext
-
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import org.koitharu.kotatsu.utils.BufferedObserver
-import kotlin.coroutines.EmptyCoroutineContext
-
-fun LiveData.requireValue(): T = checkNotNull(value) {
- "LiveData value is null"
-}
-
-fun LiveData.observeWithPrevious(owner: LifecycleOwner, observer: BufferedObserver) {
- var previous: T? = null
- this.observe(owner) {
- observer.onChanged(it, previous)
- previous = it
- }
-}
-
-suspend fun MutableLiveData.emitValue(newValue: T) {
- val dispatcher = Dispatchers.Main.immediate
- if (dispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
- withContext(dispatcher) {
- value = newValue
- }
- } else {
- value = newValue
- }
-}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt
deleted file mode 100644
index badf5ae7c..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.koitharu.kotatsu.utils.ext
-
-inline fun String?.ifNullOrEmpty(defaultValue: () -> String): String {
- return if (this.isNullOrEmpty()) defaultValue() else this
-}
-
-fun String.longHashCode(): Long {
- var h = 1125899906842597L
- val len: Int = this.length
- for (i in 0 until len) {
- h = 31 * h + this[i].code
- }
- return h
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/kotlin/org/koitharu/kotatsu/KotatsuApp.kt
similarity index 94%
rename from app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/KotatsuApp.kt
index 57ce0686c..164095e81 100644
--- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/KotatsuApp.kt
@@ -20,11 +20,12 @@ import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.prefs.AppSettings
+import org.koitharu.kotatsu.core.util.WorkServiceStopHelper
+import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
+import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.local.data.PagesCache
-import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.reader.domain.PageLoader
-import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import javax.inject.Inject
@HiltAndroidApp
@@ -56,6 +57,7 @@ class KotatsuApp : Application(), Configuration.Provider {
processLifecycleScope.launch(Dispatchers.Default) {
setupDatabaseObservers()
}
+ WorkServiceStopHelper(applicationContext).setup()
}
override fun attachBaseContext(base: Context?) {
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/BookmarksDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/BookmarksDao.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/data/BookmarksDao.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/BookmarksDao.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt
index feeac4519..0c439a6bc 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt
@@ -5,7 +5,6 @@ import androidx.room.withTransaction
import dagger.Reusable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import org.koitharu.kotatsu.base.domain.ReversibleHandle
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.bookmarks.data.toBookmark
import org.koitharu.kotatsu.bookmarks.data.toBookmarks
@@ -14,9 +13,10 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toEntities
import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toManga
+import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
+import org.koitharu.kotatsu.core.util.ext.mapItems
import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.utils.ext.mapItems
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import javax.inject.Inject
@Reusable
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksActivity.kt
similarity index 87%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksActivity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksActivity.kt
index 1bcc16d2e..5f2f952bd 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksActivity.kt
@@ -3,16 +3,14 @@ package org.koitharu.kotatsu.bookmarks.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
-import android.view.ViewGroup
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets
-import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import com.google.android.material.appbar.AppBarLayout
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.BaseActivity
+import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.main.ui.owners.SnackbarOwner
@@ -24,10 +22,10 @@ class BookmarksActivity :
SnackbarOwner {
override val appBar: AppBarLayout
- get() = binding.appbar
+ get() = viewBinding.appbar
override val snackbarHost: CoordinatorLayout
- get() = binding.root
+ get() = viewBinding.root
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -43,7 +41,7 @@ class BookmarksActivity :
}
override fun onWindowInsetsChanged(insets: Insets) {
- binding.root.updatePadding(
+ viewBinding.root.updatePadding(
left = insets.left,
right = insets.right,
)
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt
similarity index 79%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt
index 06b3dbb36..f2db6cb49 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt
@@ -16,19 +16,23 @@ import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.domain.reverseAsync
-import org.koitharu.kotatsu.base.ui.BaseFragment
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
-import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
-import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
-import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
-import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller
-import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.bookmarks.data.ids
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksGroupAdapter
import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
+import org.koitharu.kotatsu.core.ui.BaseFragment
+import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
+import org.koitharu.kotatsu.core.ui.list.SectionedSelectionController
+import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
+import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
+import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
+import org.koitharu.kotatsu.core.ui.util.ReversibleAction
+import org.koitharu.kotatsu.core.ui.util.reverseAsync
+import org.koitharu.kotatsu.core.util.ext.invalidateNestedItemDecorations
+import org.koitharu.kotatsu.core.util.ext.observe
+import org.koitharu.kotatsu.core.util.ext.observeEvent
+import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.databinding.FragmentListSimpleBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
@@ -37,8 +41,6 @@ import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.main.ui.owners.SnackbarOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity
-import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
-import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
import javax.inject.Inject
@AndroidEntryPoint
@@ -56,12 +58,12 @@ class BookmarksFragment :
private var adapter: BookmarksGroupAdapter? = null
private var selectionController: SectionedSelectionController? = null
- override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): FragmentListSimpleBinding {
+ override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentListSimpleBinding {
return FragmentListSimpleBinding.inflate(inflater, container, false)
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ override fun onViewBindingCreated(binding: FragmentListSimpleBinding, savedInstanceState: Bundle?) {
+ super.onViewBindingCreated(binding, savedInstanceState)
selectionController = SectionedSelectionController(
activity = requireActivity(),
owner = this,
@@ -77,12 +79,12 @@ class BookmarksFragment :
)
binding.recyclerView.adapter = adapter
binding.recyclerView.setHasFixedSize(true)
- val spacingDecoration = SpacingItemDecoration(view.resources.getDimensionPixelOffset(R.dimen.grid_spacing))
+ val spacingDecoration = SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing))
binding.recyclerView.addItemDecoration(spacingDecoration)
viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
- viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
- viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone)
+ viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
+ viewModel.onActionDone.observeEvent(viewLifecycleOwner, ::onActionDone)
}
override fun onDestroyView() {
@@ -114,7 +116,7 @@ class BookmarksFragment :
override fun onFastScrollStop(fastScroller: FastScroller) = Unit
override fun onSelectionChanged(controller: SectionedSelectionController, count: Int) {
- binding.recyclerView.invalidateNestedItemDecorations()
+ requireViewBinding().recyclerView.invalidateNestedItemDecorations()
}
override fun onCreateActionMode(
@@ -149,10 +151,10 @@ class BookmarksFragment :
): AbstractSelectionItemDecoration = BookmarksSelectionDecoration(requireContext())
override fun onWindowInsetsChanged(insets: Insets) {
- binding.recyclerView.updatePadding(
+ requireViewBinding().recyclerView.updatePadding(
bottom = insets.bottom,
)
- binding.recyclerView.fastScroller.updateLayoutParams {
+ requireViewBinding().recyclerView.fastScroller.updateLayoutParams {
bottomMargin = insets.bottom
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksSelectionDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksSelectionDecoration.kt
similarity index 92%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksSelectionDecoration.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksSelectionDecoration.kt
index 025acb882..85886eed8 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksSelectionDecoration.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksSelectionDecoration.kt
@@ -4,8 +4,8 @@ import android.content.Context
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
+import org.koitharu.kotatsu.core.util.ext.getItem
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
-import org.koitharu.kotatsu.utils.ext.getItem
class BookmarksSelectionDecoration(context: Context) : MangaSelectionDecoration(context) {
@@ -14,5 +14,4 @@ class BookmarksSelectionDecoration(context: Context) : MangaSelectionDecoration(
val item = holder.getItem(Bookmark::class.java) ?: return RecyclerView.NO_ID
return item.pageId
}
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksViewModel.kt
similarity index 67%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksViewModel.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksViewModel.kt
index ced4e03cc..d08ce07b4 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksViewModel.kt
@@ -1,23 +1,26 @@
package org.koitharu.kotatsu.bookmarks.ui
-import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.BaseViewModel
-import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup
+import org.koitharu.kotatsu.core.ui.BaseViewModel
+import org.koitharu.kotatsu.core.ui.util.ReversibleAction
+import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
+import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.utils.SingleLiveEvent
-import org.koitharu.kotatsu.utils.asFlowLiveData
import javax.inject.Inject
@HiltViewModel
@@ -25,9 +28,9 @@ class BookmarksViewModel @Inject constructor(
private val repository: BookmarksRepository,
) : BaseViewModel() {
- val onActionDone = SingleLiveEvent()
+ val onActionDone = MutableEventFlow()
- val content: LiveData> = repository.observeBookmarks()
+ val content: StateFlow> = repository.observeBookmarks()
.map { list ->
if (list.isEmpty()) {
listOf(
@@ -43,12 +46,12 @@ class BookmarksViewModel @Inject constructor(
}
}
.catch { e -> emit(listOf(e.toErrorState(canRetry = false))) }
- .asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
+ .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
fun removeBookmarks(ids: Map>) {
launchJob(Dispatchers.Default) {
val handle = repository.removeBookmarks(ids)
- onActionDone.emitCall(ReversibleAction(R.string.bookmarks_removed, handle))
+ onActionDone.call(ReversibleAction(R.string.bookmarks_removed, handle))
}
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkListAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkListAD.kt
similarity index 72%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkListAD.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkListAD.kt
index 1ffa3bac3..886aba926 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkListAD.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkListAD.kt
@@ -4,16 +4,16 @@ import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
+import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
+import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
+import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
+import org.koitharu.kotatsu.core.util.ext.decodeRegion
+import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
+import org.koitharu.kotatsu.core.util.ext.enqueueWith
+import org.koitharu.kotatsu.core.util.ext.newImageRequest
+import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemBookmarkBinding
-import org.koitharu.kotatsu.utils.ext.decodeRegion
-import org.koitharu.kotatsu.utils.ext.disposeImageRequest
-import org.koitharu.kotatsu.utils.ext.enqueueWith
-import org.koitharu.kotatsu.utils.ext.newImageRequest
-import org.koitharu.kotatsu.utils.ext.source
-import org.koitharu.kotatsu.utils.image.CoverSizeResolver
fun bookmarkListAD(
coil: ImageLoader,
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt
similarity index 87%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt
index 2f3022b8e..d47bbb785 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt
@@ -4,8 +4,8 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
+import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
class BookmarksAdapter(
coil: ImageLoader,
@@ -13,7 +13,7 @@ class BookmarksAdapter(
clickListener: OnListItemClickListener,
) : AsyncListDifferDelegationAdapter(
DiffCallback(),
- bookmarkListAD(coil, lifecycleOwner, clickListener)
+ bookmarkListAD(coil, lifecycleOwner, clickListener),
) {
private class DiffCallback : DiffUtil.ItemCallback() {
@@ -27,4 +27,4 @@ class BookmarksAdapter(
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAD.kt
similarity index 82%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAD.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAD.kt
index a4d33d0eb..df737ff54 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAD.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAD.kt
@@ -6,20 +6,20 @@ import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
-import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
-import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup
+import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
+import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
+import org.koitharu.kotatsu.core.ui.list.SectionedSelectionController
+import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
+import org.koitharu.kotatsu.core.util.ext.clearItemDecorations
+import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
+import org.koitharu.kotatsu.core.util.ext.enqueueWith
+import org.koitharu.kotatsu.core.util.ext.newImageRequest
+import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemBookmarksGroupBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.utils.ext.clearItemDecorations
-import org.koitharu.kotatsu.utils.ext.disposeImageRequest
-import org.koitharu.kotatsu.utils.ext.enqueueWith
-import org.koitharu.kotatsu.utils.ext.newImageRequest
-import org.koitharu.kotatsu.utils.ext.source
-import org.koitharu.kotatsu.utils.image.CoverSizeResolver
fun bookmarksGroupAD(
coil: ImageLoader,
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt
index a73d0a0c1..31ab12fd7 100644
--- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksGroupAdapter.kt
@@ -5,16 +5,17 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
-import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
-import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup
+import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
+import org.koitharu.kotatsu.core.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
+import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.parsers.model.Manga
import kotlin.jvm.internal.Intrinsics
@@ -54,6 +55,10 @@ class BookmarksGroupAdapter(
oldItem.manga.id == newItem.manga.id
}
+ oldItem is LoadingFooter && newItem is LoadingFooter -> {
+ oldItem.key == newItem.key
+ }
+
else -> oldItem.javaClass == newItem.javaClass
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/model/BookmarksGroup.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/model/BookmarksGroup.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/model/BookmarksGroup.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/model/BookmarksGroup.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/BrowserActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserActivity.kt
similarity index 76%
rename from app/src/main/java/org/koitharu/kotatsu/browser/BrowserActivity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserActivity.kt
index 991a86d8c..f84ca4899 100644
--- a/app/src/main/java/org/koitharu/kotatsu/browser/BrowserActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserActivity.kt
@@ -12,8 +12,9 @@ import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
+import org.koitharu.kotatsu.core.ui.BaseActivity
+import org.koitharu.kotatsu.core.util.ext.catchingWebViewUnavailability
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import com.google.android.material.R as materialR
@@ -24,18 +25,20 @@ class BrowserActivity : BaseActivity(), BrowserCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(ActivityBrowserBinding.inflate(layoutInflater))
+ if (!catchingWebViewUnavailability { setContentView(ActivityBrowserBinding.inflate(layoutInflater)) }) {
+ return
+ }
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
}
- with(binding.webView.settings) {
+ with(viewBinding.webView.settings) {
javaScriptEnabled = true
userAgentString = CommonHeadersInterceptor.userAgentChrome
}
- binding.webView.webViewClient = BrowserClient(this)
- binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
- onBackPressedCallback = WebViewBackPressedCallback(binding.webView)
+ viewBinding.webView.webViewClient = BrowserClient(this)
+ viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
+ onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)
onBackPressedDispatcher.addCallback(onBackPressedCallback)
if (savedInstanceState != null) {
return
@@ -48,18 +51,18 @@ class BrowserActivity : BaseActivity(), BrowserCallback
intent?.getStringExtra(EXTRA_TITLE) ?: getString(R.string.loading_),
url,
)
- binding.webView.loadUrl(url)
+ viewBinding.webView.loadUrl(url)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
- binding.webView.saveState(outState)
+ viewBinding.webView.saveState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
- binding.webView.restoreState(savedInstanceState)
+ viewBinding.webView.restoreState(savedInstanceState)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -70,14 +73,14 @@ class BrowserActivity : BaseActivity(), BrowserCallback
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
android.R.id.home -> {
- binding.webView.stopLoading()
+ viewBinding.webView.stopLoading()
finishAfterTransition()
true
}
R.id.action_browser -> {
val intent = Intent(Intent.ACTION_VIEW)
- intent.data = Uri.parse(binding.webView.url)
+ intent.data = Uri.parse(viewBinding.webView.url)
try {
startActivity(Intent.createChooser(intent, item.title))
} catch (_: ActivityNotFoundException) {
@@ -89,22 +92,22 @@ class BrowserActivity : BaseActivity(), BrowserCallback
}
override fun onPause() {
- binding.webView.onPause()
+ viewBinding.webView.onPause()
super.onPause()
}
override fun onResume() {
super.onResume()
- binding.webView.onResume()
+ viewBinding.webView.onResume()
}
override fun onDestroy() {
super.onDestroy()
- binding.webView.destroy()
+ viewBinding.webView.destroy()
}
override fun onLoadingStateChanged(isLoading: Boolean) {
- binding.progressBar.isVisible = isLoading
+ viewBinding.progressBar.isVisible = isLoading
}
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {
@@ -117,10 +120,10 @@ class BrowserActivity : BaseActivity(), BrowserCallback
}
override fun onWindowInsetsChanged(insets: Insets) {
- binding.appbar.updatePadding(
+ viewBinding.appbar.updatePadding(
top = insets.top,
)
- binding.root.updatePadding(
+ viewBinding.root.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom,
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/BrowserCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserCallback.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/browser/BrowserCallback.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserCallback.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/BrowserClient.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserClient.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/browser/BrowserClient.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserClient.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/OnHistoryChangedListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/OnHistoryChangedListener.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/browser/OnHistoryChangedListener.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/OnHistoryChangedListener.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/ProgressChromeClient.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/ProgressChromeClient.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/browser/ProgressChromeClient.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/ProgressChromeClient.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/WebViewBackPressedCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/WebViewBackPressedCallback.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/browser/WebViewBackPressedCallback.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/WebViewBackPressedCallback.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareCallback.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareCallback.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareCallback.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt
similarity index 83%
rename from app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt
index 34f02003c..0a1284448 100644
--- a/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt
@@ -1,10 +1,8 @@
package org.koitharu.kotatsu.browser.cloudflare
-import android.annotation.SuppressLint
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.WebSettings
@@ -14,13 +12,13 @@ import androidx.fragment.app.setFragmentResult
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import okhttp3.Headers
-import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
+import org.koitharu.kotatsu.core.ui.AlertDialogFragment
+import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding
-import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
@AndroidEntryPoint
@@ -39,14 +37,13 @@ class CloudFlareDialog : AlertDialogFragment(), Cloud
url = requireArguments().getString(ARG_URL).orEmpty()
}
- override fun onInflateView(
+ override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
) = FragmentCloudflareBinding.inflate(inflater, container, false)
- @SuppressLint("SetJavaScriptEnabled")
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ override fun onViewBindingCreated(binding: FragmentCloudflareBinding, savedInstanceState: Bundle?) {
+ super.onViewBindingCreated(binding, savedInstanceState)
with(binding.webView.settings) {
javaScriptEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT
@@ -64,8 +61,8 @@ class CloudFlareDialog : AlertDialogFragment(), Cloud
}
override fun onDestroyView() {
- binding.webView.stopLoading()
- binding.webView.destroy()
+ requireViewBinding().webView.stopLoading()
+ requireViewBinding().webView.destroy()
onBackPressedCallback = null
super.onDestroyView()
}
@@ -76,18 +73,18 @@ class CloudFlareDialog : AlertDialogFragment(), Cloud
override fun onDialogCreated(dialog: AlertDialog) {
super.onDialogCreated(dialog)
- onBackPressedCallback = WebViewBackPressedCallback(binding.webView).also {
+ onBackPressedCallback = WebViewBackPressedCallback(requireViewBinding().webView).also {
dialog.onBackPressedDispatcher.addCallback(it)
}
}
override fun onResume() {
super.onResume()
- binding.webView.onResume()
+ requireViewBinding().webView.onResume()
}
override fun onPause() {
- binding.webView.onPause()
+ requireViewBinding().webView.onPause()
super.onPause()
}
@@ -97,7 +94,7 @@ class CloudFlareDialog : AlertDialogFragment(), Cloud
}
override fun onPageLoaded() {
- bindingOrNull()?.progressBar?.isInvisible = true
+ viewBinding?.progressBar?.isInvisible = true
}
override fun onCheckPassed() {
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/AppModule.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt
similarity index 68%
rename from app/src/main/java/org/koitharu/kotatsu/core/AppModule.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt
index 6797915b1..39b9b701b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/AppModule.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt
@@ -4,7 +4,6 @@ import android.app.Application
import android.content.Context
import android.provider.SearchRecentSuggestions
import android.text.Html
-import android.util.AndroidRuntimeException
import androidx.collection.arraySetOf
import androidx.room.InvalidationTracker
import coil.ComponentRegistry
@@ -23,50 +22,42 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
-import okhttp3.CookieJar
import okhttp3.OkHttpClient
import org.koitharu.kotatsu.BuildConfig
-import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.cache.MemoryContentCache
import org.koitharu.kotatsu.core.cache.StubContentCache
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.network.*
-import org.koitharu.kotatsu.core.network.cookies.AndroidCookieJar
-import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
-import org.koitharu.kotatsu.core.network.cookies.PreferencesCookieJar
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
-import org.koitharu.kotatsu.core.prefs.AppSettings
+import org.koitharu.kotatsu.core.ui.image.CoilImageGetter
+import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
+import org.koitharu.kotatsu.core.util.IncognitoModeIndicator
+import org.koitharu.kotatsu.core.util.ext.activityManager
+import org.koitharu.kotatsu.core.util.ext.connectivityManager
+import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.CbzFetcher
-import org.koitharu.kotatsu.local.data.LocalManga
import org.koitharu.kotatsu.local.data.LocalStorageChanges
-import org.koitharu.kotatsu.local.data.LocalStorageManager
+import org.koitharu.kotatsu.local.data.PagesCache
+import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.reader.ui.thumbnails.MangaPageFetcher
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
import org.koitharu.kotatsu.settings.backup.BackupObserver
import org.koitharu.kotatsu.sync.domain.SyncController
-import org.koitharu.kotatsu.utils.IncognitoModeIndicator
-import org.koitharu.kotatsu.utils.ext.activityManager
-import org.koitharu.kotatsu.utils.ext.connectivityManager
-import org.koitharu.kotatsu.utils.ext.isLowRamDevice
-import org.koitharu.kotatsu.utils.image.CoilImageGetter
import org.koitharu.kotatsu.widget.WidgetUpdater
-import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
interface AppModule {
- @Binds
- fun bindCookieJar(androidCookieJar: MutableCookieJar): CookieJar
-
@Binds
fun bindMangaLoaderContext(mangaLoaderContextImpl: MangaLoaderContextImpl): MangaLoaderContext
@@ -75,47 +66,6 @@ interface AppModule {
companion object {
- @Provides
- @Singleton
- fun provideCookieJar(
- @ApplicationContext context: Context
- ): MutableCookieJar = try {
- AndroidCookieJar()
- } catch (e: AndroidRuntimeException) {
- // WebView is not available
- PreferencesCookieJar(context)
- }
-
- @Provides
- @Singleton
- fun provideOkHttpClient(
- localStorageManager: LocalStorageManager,
- commonHeadersInterceptor: CommonHeadersInterceptor,
- mirrorSwitchInterceptor: MirrorSwitchInterceptor,
- cookieJar: CookieJar,
- settings: AppSettings,
- ): OkHttpClient {
- val cache = localStorageManager.createHttpCache()
- return OkHttpClient.Builder().apply {
- connectTimeout(20, TimeUnit.SECONDS)
- readTimeout(60, TimeUnit.SECONDS)
- writeTimeout(20, TimeUnit.SECONDS)
- cookieJar(cookieJar)
- dns(DoHManager(cache, settings))
- if (settings.isSSLBypassEnabled) {
- bypassSSLErrors()
- }
- cache(cache)
- addInterceptor(GZipInterceptor())
- addInterceptor(commonHeadersInterceptor)
- addInterceptor(CloudFlareInterceptor())
- addInterceptor(mirrorSwitchInterceptor)
- if (BuildConfig.DEBUG) {
- addInterceptor(CurlLoggingInterceptor())
- }
- }.build()
- }
-
@Provides
@Singleton
fun provideNetworkState(
@@ -134,14 +84,10 @@ interface AppModule {
@Singleton
fun provideCoil(
@ApplicationContext context: Context,
- okHttpClient: OkHttpClient,
+ @MangaHttpClient okHttpClient: OkHttpClient,
mangaRepositoryFactory: MangaRepository.Factory,
+ pagesCache: PagesCache,
): ImageLoader {
- val httpClientFactory = {
- okHttpClient.newBuilder()
- .cache(null)
- .build()
- }
val diskCacheFactory = {
val rootDir = context.externalCacheDir ?: context.cacheDir
DiskCache.Builder()
@@ -149,19 +95,20 @@ interface AppModule {
.build()
}
return ImageLoader.Builder(context)
- .okHttpClient(httpClientFactory)
+ .okHttpClient(okHttpClient.newBuilder().cache(null).build())
.interceptorDispatcher(Dispatchers.Default)
.fetcherDispatcher(Dispatchers.IO)
.decoderDispatcher(Dispatchers.Default)
.transformationDispatcher(Dispatchers.Default)
.diskCache(diskCacheFactory)
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
- .allowRgb565(isLowRamDevice(context))
+ .allowRgb565(context.isLowRamDevice())
.components(
ComponentRegistry.Builder()
.add(SvgDecoder.Factory())
.add(CbzFetcher.Factory())
.add(FaviconFetcher.Factory(context, okHttpClient, mangaRepositoryFactory))
+ .add(MangaPageFetcher.Factory(context, okHttpClient, pagesCache, mangaRepositoryFactory))
.build(),
).build()
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupEntry.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupEntry.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/backup/BackupEntry.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupEntry.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupRepository.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupRepository.kt
index 9d80b7af4..f02602cef 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupRepository.kt
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.parsers.util.json.JSONIterator
import org.koitharu.kotatsu.parsers.util.json.mapJSON
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
+import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import javax.inject.Inject
private const val PAGE_SIZE = 10
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipInput.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipInput.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipInput.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipInput.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt
similarity index 92%
rename from app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt
index 8a6217d04..c06f45a76 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt
@@ -5,10 +5,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okio.Closeable
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.util.ext.format
import org.koitharu.kotatsu.core.zip.ZipOutput
-import org.koitharu.kotatsu.utils.ext.format
import java.io.File
-import java.util.*
+import java.util.Date
+import java.util.Locale
import java.util.zip.Deflater
class BackupZipOutput(val file: File) : Closeable {
@@ -42,4 +43,4 @@ suspend fun BackupZipOutput(context: Context): BackupZipOutput = runInterruptibl
append(".bk.zip")
}
BackupZipOutput(File(dir, filename))
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/CompositeResult.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/CompositeResult.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/backup/CompositeResult.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/backup/CompositeResult.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/JsonSerializer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/backup/JsonSerializer.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/ContentCache.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ContentCache.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/cache/ContentCache.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ContentCache.kt
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ExpiringLruCache.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ExpiringLruCache.kt
new file mode 100644
index 000000000..34d46dfca
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ExpiringLruCache.kt
@@ -0,0 +1,33 @@
+package org.koitharu.kotatsu.core.cache
+
+import androidx.collection.LruCache
+import java.util.concurrent.TimeUnit
+
+class ExpiringLruCache(
+ val maxSize: Int,
+ private val lifetime: Long,
+ private val timeUnit: TimeUnit,
+) {
+
+ private val cache = LruCache>(maxSize)
+
+ operator fun get(key: ContentCache.Key): T? {
+ val value = cache.get(key) ?: return null
+ if (value.isExpired) {
+ cache.remove(key)
+ }
+ return value.get()
+ }
+
+ operator fun set(key: ContentCache.Key, value: T) {
+ cache.put(key, ExpiringValue(value, lifetime, timeUnit))
+ }
+
+ fun clear() {
+ cache.evictAll()
+ }
+
+ fun trimToSize(size: Int) {
+ cache.trimToSize(size)
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ExpiringValue.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ExpiringValue.kt
new file mode 100644
index 000000000..2d561bb0c
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ExpiringValue.kt
@@ -0,0 +1,34 @@
+package org.koitharu.kotatsu.core.cache
+
+import android.os.SystemClock
+import java.util.concurrent.TimeUnit
+
+class ExpiringValue(
+ private val value: T,
+ lifetime: Long,
+ timeUnit: TimeUnit,
+) {
+
+ private val expiresAt = SystemClock.elapsedRealtime() + timeUnit.toMillis(lifetime)
+
+ val isExpired: Boolean
+ get() = SystemClock.elapsedRealtime() >= expiresAt
+
+ fun get(): T? = if (isExpired) null else value
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as ExpiringValue<*>
+
+ if (value != other.value) return false
+ return expiresAt == other.expiresAt
+ }
+
+ override fun hashCode(): Int {
+ var result = value?.hashCode() ?: 0
+ result = 31 * result + expiresAt.hashCode()
+ return result
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt
similarity index 74%
rename from app/src/main/java/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt
index ffa9a904e..722b06d41 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt
@@ -6,6 +6,7 @@ import android.content.res.Configuration
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
+import java.util.concurrent.TimeUnit
class MemoryContentCache(application: Application) : ContentCache, ComponentCallbacks2 {
@@ -13,8 +14,8 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall
application.registerComponentCallbacks(this)
}
- private val detailsCache = DeferredLruCache(4)
- private val pagesCache = DeferredLruCache>(4)
+ private val detailsCache = ExpiringLruCache>(4, 5, TimeUnit.MINUTES)
+ private val pagesCache = ExpiringLruCache>>(4, 10, TimeUnit.MINUTES)
override val isCachingEnabled: Boolean = true
@@ -23,7 +24,7 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall
}
override fun putDetails(source: MangaSource, url: String, details: SafeDeferred) {
- detailsCache.put(ContentCache.Key(source, url), details)
+ detailsCache[ContentCache.Key(source, url)] = details
}
override suspend fun getPages(source: MangaSource, url: String): List? {
@@ -31,7 +32,7 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall
}
override fun putPages(source: MangaSource, url: String, pages: SafeDeferred>) {
- pagesCache.put(ContentCache.Key(source, url), pages)
+ pagesCache[ContentCache.Key(source, url)] = pages
}
override fun onConfigurationChanged(newConfig: Configuration) = Unit
@@ -43,17 +44,17 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall
trimCache(pagesCache, level)
}
- private fun trimCache(cache: DeferredLruCache<*>, level: Int) {
+ private fun trimCache(cache: ExpiringLruCache<*>, level: Int) {
when (level) {
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL,
ComponentCallbacks2.TRIM_MEMORY_COMPLETE,
- ComponentCallbacks2.TRIM_MEMORY_MODERATE -> cache.evictAll()
+ ComponentCallbacks2.TRIM_MEMORY_MODERATE -> cache.clear()
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> cache.trimToSize(1)
- else -> cache.trimToSize(cache.maxSize() / 2)
+ else -> cache.trimToSize(cache.maxSize / 2)
}
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/SafeDeferred.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/SafeDeferred.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/cache/SafeDeferred.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/cache/SafeDeferred.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/StubContentCache.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/cache/StubContentCache.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/cache/StubContentCache.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/cache/StubContentCache.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/DatabasePrePopulateCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/DatabasePrePopulateCallback.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/DatabasePrePopulateCallback.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/DatabasePrePopulateCallback.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt
index d390e80dd..5378e420e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt
@@ -33,6 +33,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration6To7
import org.koitharu.kotatsu.core.db.migrations.Migration7To8
import org.koitharu.kotatsu.core.db.migrations.Migration8To9
import org.koitharu.kotatsu.core.db.migrations.Migration9To10
+import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import org.koitharu.kotatsu.favourites.data.FavouriteCategoriesDao
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
@@ -46,7 +47,6 @@ import org.koitharu.kotatsu.suggestions.data.SuggestionEntity
import org.koitharu.kotatsu.tracker.data.TrackEntity
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
import org.koitharu.kotatsu.tracker.data.TracksDao
-import org.koitharu.kotatsu.utils.ext.processLifecycleScope
const val DATABASE_VERSION = 15
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/Tables.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/Tables.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/Tables.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/Tables.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/MangaDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/dao/MangaDao.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/PreferencesDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/PreferencesDao.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/dao/PreferencesDao.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/PreferencesDao.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TagsDao.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TagsDao.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TrackLogsDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TrackLogsDao.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/dao/TrackLogsDao.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TrackLogsDao.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt
similarity index 86%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt
index 467fcab9a..80bcb6045 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt
@@ -1,10 +1,13 @@
package org.koitharu.kotatsu.core.db.entity
import org.koitharu.kotatsu.core.model.MangaSource
-import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.core.util.ext.longHashCode
+import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaState
+import org.koitharu.kotatsu.parsers.model.MangaTag
+import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.toTitleCase
-import org.koitharu.kotatsu.utils.ext.longHashCode
// Entity to model
@@ -66,7 +69,6 @@ fun SortOrder(name: String, fallback: SortOrder): SortOrder = runCatching {
SortOrder.valueOf(name)
}.getOrDefault(fallback)
-@Suppress("FunctionName")
fun MangaState(name: String): MangaState? = runCatching {
MangaState.valueOf(name)
}.getOrNull()
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/TagEntity.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/TagEntity.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration10To11.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration10To11.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration10To11.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration10To11.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration11To12.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration11To12.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration11To12.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration11To12.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration12To13.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration12To13.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration12To13.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration12To13.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration13To14.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration13To14.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration13To14.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration13To14.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration14To15.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration14To15.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration14To15.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration14To15.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration1To2.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration1To2.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration1To2.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration1To2.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration2To3.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration2To3.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration2To3.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration2To3.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration3To4.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration3To4.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration3To4.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration3To4.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration4To5.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration4To5.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration4To5.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration4To5.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration5To6.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration5To6.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration5To6.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration5To6.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration6To7.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration6To7.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration6To7.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration6To7.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration7To8.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration7To8.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration7To8.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration7To8.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration8To9.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration8To9.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration8To9.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration8To9.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration9To10.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration9To10.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration9To10.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration9To10.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CaughtException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CaughtException.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/CaughtException.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CaughtException.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CompositeException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CompositeException.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/CompositeException.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CompositeException.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/EmptyHistoryException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/EmptyHistoryException.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/EmptyHistoryException.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/EmptyHistoryException.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/SyncApiException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/SyncApiException.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/SyncApiException.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/SyncApiException.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/UnsupportedFileException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/UnsupportedFileException.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/UnsupportedFileException.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/UnsupportedFileException.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/WrongPasswordException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/WrongPasswordException.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/WrongPasswordException.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/WrongPasswordException.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt
similarity index 90%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt
index c3bc2f893..1edf3b662 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt
@@ -6,9 +6,9 @@ import androidx.core.util.Consumer
import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.core.ui.ErrorDetailsDialog
+import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
+import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.parsers.exception.ParseException
-import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class DialogErrorObserver(
host: View,
@@ -22,10 +22,7 @@ class DialogErrorObserver(
fragment: Fragment?,
) : this(host, fragment, null, null)
- override fun onChanged(value: Throwable?) {
- if (value == null) {
- return
- }
+ override suspend fun emit(value: Throwable) {
val listener = DialogListener(value)
val dialogBuilder = MaterialAlertDialogBuilder(activity ?: host.context)
.setMessage(value.getDisplayMessage(host.context.resources))
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt
similarity index 87%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt
index e41b65955..a64fb9edf 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt
@@ -7,19 +7,19 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.Observer
import androidx.lifecycle.coroutineScope
+import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
-import org.koitharu.kotatsu.utils.ext.findActivity
-import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
+import org.koitharu.kotatsu.core.util.ext.findActivity
+import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
abstract class ErrorObserver(
protected val host: View,
protected val fragment: Fragment?,
private val resolver: ExceptionResolver?,
private val onResolved: Consumer?,
-) : Observer {
+) : FlowCollector {
protected val activity = host.context.findActivity()
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt
index 75d466bbb..b5e267dcb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt
@@ -12,13 +12,13 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
-import org.koitharu.kotatsu.core.ui.ErrorDetailsDialog
+import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
+import org.koitharu.kotatsu.core.util.TaggedActivityResult
+import org.koitharu.kotatsu.core.util.isSuccess
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
-import org.koitharu.kotatsu.utils.TaggedActivityResult
-import org.koitharu.kotatsu.utils.isSuccess
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt
similarity index 85%
rename from app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt
index fb3cea7d9..e39897cfc 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt
@@ -5,10 +5,10 @@ import androidx.core.util.Consumer
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.core.ui.ErrorDetailsDialog
+import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
+import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.exception.ParseException
-import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class SnackbarErrorObserver(
host: View,
@@ -22,10 +22,7 @@ class SnackbarErrorObserver(
fragment: Fragment?,
) : this(host, fragment, null, null)
- override fun onChanged(value: Throwable?) {
- if (value == null) {
- return
- }
+ override suspend fun emit(value: Throwable) {
val snackbar = Snackbar.make(host, value.getDisplayMessage(host.context.resources), Snackbar.LENGTH_SHORT)
if (activity is BottomNavOwner) {
snackbar.anchorView = activity.bottomNav
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt
index 70598cb0a..03047a807 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt
@@ -13,14 +13,15 @@ import okhttp3.Request
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.BuildConfig
+import org.koitharu.kotatsu.core.network.BaseHttpClient
import org.koitharu.kotatsu.core.prefs.AppSettings
+import org.koitharu.kotatsu.core.util.ext.asArrayList
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
import org.koitharu.kotatsu.parsers.util.parseJsonArray
-import org.koitharu.kotatsu.utils.ext.asArrayList
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
+import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
@@ -36,7 +37,7 @@ private const val CONTENT_TYPE_APK = "application/vnd.android.package-archive"
class AppUpdateRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val settings: AppSettings,
- private val okHttp: OkHttpClient,
+ @BaseHttpClient private val okHttp: OkHttpClient,
) {
private val availableUpdate = MutableStateFlow(null)
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/github/AppVersion.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/github/AppVersion.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/github/AppVersion.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/github/AppVersion.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/github/VersionId.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/github/VersionId.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/github/VersionId.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/github/VersionId.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/logs/FileLogger.kt
similarity index 78%
rename from app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/logs/FileLogger.kt
index 4ade0b3a6..d63c2e3bb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/logs/FileLogger.kt
@@ -4,17 +4,20 @@ import android.content.Context
import androidx.annotation.WorkerThread
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
+import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import org.koitharu.kotatsu.utils.ext.processLifecycleScope
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
-import org.koitharu.kotatsu.utils.ext.subdir
+import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
+import org.koitharu.kotatsu.core.util.ext.subdir
+import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
@@ -82,6 +85,15 @@ class FileLogger(
flushImpl()
}
+ @WorkerThread
+ fun flushBlocking() {
+ if (!isEnabled) {
+ return
+ }
+ runBlockingSafe { flushJob?.cancelAndJoin() }
+ runBlockingSafe { flushImpl() }
+ }
+
private fun postFlush() {
if (flushJob?.isActive == true) {
return
@@ -96,10 +108,10 @@ class FileLogger(
}
}
- private suspend fun flushImpl() {
+ private suspend fun flushImpl() = withContext(NonCancellable) {
mutex.withLock {
if (buffer.isEmpty()) {
- return
+ return@withContext
}
runInterruptible(Dispatchers.IO) {
if (file.length() > MAX_SIZE_BYTES) {
@@ -131,4 +143,9 @@ class FileLogger(
}
bakFile.delete()
}
+
+ private inline fun runBlockingSafe(crossinline block: suspend () -> Unit) = try {
+ runBlocking(NonCancellable) { block() }
+ } catch (_: InterruptedException) {
+ }
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/logs/Loggers.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/logs/Loggers.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/logs/Loggers.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/logs/Loggers.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/logs/LoggersModule.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/logs/LoggersModule.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/logs/LoggersModule.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/logs/LoggersModule.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/FavouriteCategory.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/FavouriteCategory.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/FavouriteCategory.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/FavouriteCategory.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
similarity index 50%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
index d1c3f849e..ce0b3e7f1 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
@@ -1,14 +1,17 @@
package org.koitharu.kotatsu.core.model
import androidx.core.os.LocaleListCompat
+import org.koitharu.kotatsu.core.util.ext.iterator
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaChapter
+import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapToSet
-import org.koitharu.kotatsu.parsers.util.toTitleCase
-import org.koitharu.kotatsu.utils.ext.iterator
fun Collection.ids() = mapToSet { it.id }
+fun Collection.distinctById() = distinctBy { it.id }
+
fun Collection.countChaptersByBranch(): Int {
if (size <= 1) {
return size
@@ -21,6 +24,10 @@ fun Collection.countChaptersByBranch(): Int {
return acc.values.max()
}
+fun Manga.findChapter(id: Long): MangaChapter? {
+ return chapters?.find { it.id == id }
+}
+
fun Manga.getPreferredBranch(history: MangaHistory?): String? {
val ch = chapters
if (ch.isNullOrEmpty()) {
@@ -33,15 +40,25 @@ fun Manga.getPreferredBranch(history: MangaHistory?): String? {
}
}
val groups = ch.groupBy { it.branch }
+ if (groups.size == 1) {
+ return groups.keys.first()
+ }
+ val candidates = HashMap>(groups.size)
for (locale in LocaleListCompat.getAdjustedDefault()) {
- var language = locale.getDisplayLanguage(locale).toTitleCase(locale)
- if (groups.containsKey(language)) {
- return language
- }
- language = locale.getDisplayName(locale).toTitleCase(locale)
- if (groups.containsKey(language)) {
- return language
+ val displayLanguage = locale.getDisplayLanguage(locale)
+ val displayName = locale.getDisplayName(locale)
+ for (branch in groups.keys) {
+ if (branch != null && (
+ branch.contains(displayLanguage, ignoreCase = true) ||
+ branch.contains(displayName, ignoreCase = true)
+ )
+ ) {
+ candidates[branch] = groups[branch] ?: continue
+ }
}
}
- return groups.maxByOrNull { it.value.size }?.key
+ return candidates.ifEmpty { groups }.maxByOrNull { it.value.size }?.key
}
+
+val Manga.isLocal: Boolean
+ get() = source == MangaSource.LOCAL
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaHistory.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaHistory.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSource.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSource.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/ZoomMode.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/ZoomMode.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/ZoomMode.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/ZoomMode.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt
index 7ed9f638f..e774ce82e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt
@@ -2,12 +2,12 @@ package org.koitharu.kotatsu.core.model.parcelable
import android.os.Parcel
import androidx.core.os.ParcelCompat
+import org.koitharu.kotatsu.core.util.ext.readParcelableCompat
+import org.koitharu.kotatsu.core.util.ext.readSerializableCompat
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaTag
-import org.koitharu.kotatsu.utils.ext.readParcelableCompat
-import org.koitharu.kotatsu.utils.ext.readSerializableCompat
fun Manga.writeToParcel(out: Parcel, flags: Int, withChapters: Boolean) {
out.writeLong(id)
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaChapters.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaChapters.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaChapters.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaChapters.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPages.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPages.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPages.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPages.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt
similarity index 88%
rename from app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt
index bd5490e0a..7f6cf2f42 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt
@@ -2,15 +2,15 @@ package org.koitharu.kotatsu.core.model.parcelable
import android.os.Parcel
import android.os.Parcelable
+import org.koitharu.kotatsu.core.util.ext.Set
import org.koitharu.kotatsu.parsers.model.MangaTag
-import org.koitharu.kotatsu.utils.ext.Set
class ParcelableMangaTags(
val tags: Set,
) : Parcelable {
constructor(parcel: Parcel) : this(
- Set(parcel.readInt()) { parcel.readMangaTag() }
+ Set(parcel.readInt()) { parcel.readMangaTag() },
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
@@ -33,4 +33,4 @@ class ParcelableMangaTags(
return arrayOfNulls(size)
}
}
-}
\ No newline at end of file
+}
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
new file mode 100644
index 000000000..5beb6e42a
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/AppProxySelector.kt
@@ -0,0 +1,43 @@
+package org.koitharu.kotatsu.core.network
+
+import org.koitharu.kotatsu.core.prefs.AppSettings
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
+import java.io.IOException
+import java.net.InetSocketAddress
+import java.net.Proxy
+import java.net.ProxySelector
+import java.net.SocketAddress
+import java.net.URI
+
+class AppProxySelector(
+ private val settings: AppSettings,
+) : ProxySelector() {
+
+ private var cachedProxy: Proxy? = null
+
+ override fun select(uri: URI?): List {
+ return listOf(getProxy())
+ }
+
+ override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {
+ ioe?.printStackTraceDebug()
+ }
+
+ private fun getProxy(): Proxy {
+ val type = settings.proxyType
+ val address = settings.proxyAddress
+ val port = settings.proxyPort
+ if (type == Proxy.Type.DIRECT || address.isNullOrEmpty() || port == 0) {
+ return Proxy.NO_PROXY
+ }
+ cachedProxy?.let {
+ val addr = it.address() as? InetSocketAddress
+ if (addr != null && it.type() == type && addr.port == port && addr.hostString == address) {
+ return it
+ }
+ }
+ val proxy = Proxy(type, InetSocketAddress(address, port))
+ cachedProxy = proxy
+ return proxy
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CacheLimitInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CacheLimitInterceptor.kt
new file mode 100644
index 000000000..52710c57b
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CacheLimitInterceptor.kt
@@ -0,0 +1,26 @@
+package org.koitharu.kotatsu.core.network
+
+import okhttp3.CacheControl
+import okhttp3.Interceptor
+import okhttp3.Response
+import java.util.concurrent.TimeUnit
+
+class CacheLimitInterceptor : Interceptor {
+
+ private val defaultMaxAge = TimeUnit.HOURS.toSeconds(1)
+ private val defaultCacheControl = CacheControl.Builder()
+ .maxAge(defaultMaxAge.toInt(), TimeUnit.SECONDS)
+ .build()
+ .toString()
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val response = chain.proceed(chain.request())
+ val responseCacheControl = CacheControl.parse(response.headers)
+ if (responseCacheControl.noStore || responseCacheControl.maxAgeSeconds <= defaultMaxAge) {
+ return response
+ }
+ return response.newBuilder()
+ .header(CommonHeaders.CACHE_CONTROL, defaultCacheControl)
+ .build()
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/CommonHeaders.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CommonHeaders.kt
similarity index 92%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/CommonHeaders.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/CommonHeaders.kt
index 943e08f2e..f8976acd6 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/network/CommonHeaders.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CommonHeaders.kt
@@ -13,6 +13,7 @@ object CommonHeaders {
const val CONTENT_ENCODING = "Content-Encoding"
const val ACCEPT_ENCODING = "Accept-Encoding"
const val AUTHORIZATION = "Authorization"
+ const val CACHE_CONTROL = "Cache-Control"
val CACHE_CONTROL_NO_STORE: CacheControl
get() = CacheControl.Builder().noStore().build()
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/CommonHeadersInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CommonHeadersInterceptor.kt
similarity index 92%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/CommonHeadersInterceptor.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/CommonHeadersInterceptor.kt
index 6896cc4ab..ce873fbc6 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/network/CommonHeadersInterceptor.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CommonHeadersInterceptor.kt
@@ -12,7 +12,8 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mergeWith
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
+import java.net.IDN
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
@@ -41,7 +42,8 @@ class CommonHeadersInterceptor @Inject constructor(
headersBuilder[CommonHeaders.USER_AGENT] = userAgentFallback
}
if (headersBuilder[CommonHeaders.REFERER] == null && repository != null) {
- headersBuilder.trySet(CommonHeaders.REFERER, "https://${repository.domain}/")
+ val idn = IDN.toASCII(repository.domain)
+ headersBuilder.trySet(CommonHeaders.REFERER, "https://$idn/")
}
val newRequest = request.newBuilder().headers(headersBuilder.build()).build()
return repository?.intercept(ProxyChain(chain, newRequest)) ?: chain.proceed(newRequest)
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/DoHManager.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/DoHManager.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/DoHManager.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/DoHManager.kt
index f32717aad..9547b4da5 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/network/DoHManager.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/DoHManager.kt
@@ -6,7 +6,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps
import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import java.net.InetAddress
import java.net.UnknownHostException
@@ -52,8 +52,9 @@ class DoHManager(
tryGetByIp("8.8.8.8"),
tryGetByIp("2001:4860:4860::8888"),
tryGetByIp("2001:4860:4860::8844"),
- )
+ ),
).build()
+
DoHProvider.CLOUDFLARE -> DnsOverHttps.Builder().client(bootstrapClient)
.url("https://cloudflare-dns.com/dns-query".toHttpUrl())
.resolvePrivateAddresses(true)
@@ -68,8 +69,9 @@ class DoHManager(
tryGetByIp("2606:4700:4700::1001"),
tryGetByIp("2606:4700:4700::0064"),
tryGetByIp("2606:4700:4700::6400"),
- )
+ ),
).build()
+
DoHProvider.ADGUARD -> DnsOverHttps.Builder().client(bootstrapClient)
.url("https://dns-unfiltered.adguard.com/dns-query".toHttpUrl())
.resolvePrivateAddresses(true)
@@ -79,7 +81,7 @@ class DoHManager(
tryGetByIp("94.140.14.141"),
tryGetByIp("2a10:50c0::1:ff"),
tryGetByIp("2a10:50c0::2:ff"),
- )
+ ),
).build()
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/DoHProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/DoHProvider.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/DoHProvider.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/DoHProvider.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/GZipInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/GZipInterceptor.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/GZipInterceptor.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/GZipInterceptor.kt
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/HttpClients.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/HttpClients.kt
new file mode 100644
index 000000000..fb69fe291
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/HttpClients.kt
@@ -0,0 +1,11 @@
+package org.koitharu.kotatsu.core.network
+
+import javax.inject.Qualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class BaseHttpClient
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class MangaHttpClient
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/MirrorSwitchInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/MirrorSwitchInterceptor.kt
similarity index 94%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/MirrorSwitchInterceptor.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/MirrorSwitchInterceptor.kt
index 67da24e09..034a6513e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/network/MirrorSwitchInterceptor.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/MirrorSwitchInterceptor.kt
@@ -11,6 +11,7 @@ import okhttp3.internal.closeQuietly
import okhttp3.internal.publicsuffix.PublicSuffixDatabase
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
+import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.model.MangaSource
import javax.inject.Inject
import javax.inject.Singleton
@@ -18,10 +19,14 @@ import javax.inject.Singleton
@Singleton
class MirrorSwitchInterceptor @Inject constructor(
private val mangaRepositoryFactoryLazy: Lazy,
+ private val settings: AppSettings,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
+ if (!settings.isMirrorSwitchingAvailable) {
+ return chain.proceed(request)
+ }
return try {
val response = chain.proceed(request)
if (response.isFailed) {
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/NetworkModule.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/NetworkModule.kt
new file mode 100644
index 000000000..a60c5db64
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/NetworkModule.kt
@@ -0,0 +1,88 @@
+package org.koitharu.kotatsu.core.network
+
+import android.content.Context
+import android.util.AndroidRuntimeException
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import okhttp3.Cache
+import okhttp3.CookieJar
+import okhttp3.OkHttpClient
+import org.koitharu.kotatsu.BuildConfig
+import org.koitharu.kotatsu.core.network.cookies.AndroidCookieJar
+import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
+import org.koitharu.kotatsu.core.network.cookies.PreferencesCookieJar
+import org.koitharu.kotatsu.core.prefs.AppSettings
+import org.koitharu.kotatsu.local.data.LocalStorageManager
+import java.util.concurrent.TimeUnit
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+interface NetworkModule {
+
+ @Binds
+ fun bindCookieJar(androidCookieJar: MutableCookieJar): CookieJar
+
+ companion object {
+
+ @Provides
+ @Singleton
+ fun provideCookieJar(
+ @ApplicationContext context: Context
+ ): MutableCookieJar = try {
+ AndroidCookieJar()
+ } catch (e: AndroidRuntimeException) {
+ // WebView is not available
+ PreferencesCookieJar(context)
+ }
+
+ @Provides
+ @Singleton
+ fun provideHttpCache(
+ localStorageManager: LocalStorageManager,
+ ): Cache = localStorageManager.createHttpCache()
+
+ @Provides
+ @Singleton
+ @BaseHttpClient
+ fun provideBaseHttpClient(
+ cache: Cache,
+ cookieJar: CookieJar,
+ settings: AppSettings,
+ ): OkHttpClient = OkHttpClient.Builder().apply {
+ connectTimeout(20, TimeUnit.SECONDS)
+ readTimeout(60, TimeUnit.SECONDS)
+ writeTimeout(20, TimeUnit.SECONDS)
+ cookieJar(cookieJar)
+ proxySelector(AppProxySelector(settings))
+ dns(DoHManager(cache, settings))
+ if (settings.isSSLBypassEnabled) {
+ bypassSSLErrors()
+ }
+ cache(cache)
+ addInterceptor(GZipInterceptor())
+ addInterceptor(CloudFlareInterceptor())
+ if (BuildConfig.DEBUG) {
+ addInterceptor(CurlLoggingInterceptor())
+ }
+ }.build()
+
+ @Provides
+ @Singleton
+ @MangaHttpClient
+ fun provideMangaHttpClient(
+ @BaseHttpClient baseClient: OkHttpClient,
+ commonHeadersInterceptor: CommonHeadersInterceptor,
+ mirrorSwitchInterceptor: MirrorSwitchInterceptor,
+ ): OkHttpClient = baseClient.newBuilder().apply {
+ addNetworkInterceptor(CacheLimitInterceptor())
+ addInterceptor(commonHeadersInterceptor)
+ addInterceptor(mirrorSwitchInterceptor)
+ }.build()
+
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/SSLBypass.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLBypass.kt
similarity index 94%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/SSLBypass.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLBypass.kt
index ed1221613..a7dc8a04a 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/network/SSLBypass.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLBypass.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.core.network
import android.annotation.SuppressLint
import okhttp3.OkHttpClient
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/cookies/AndroidCookieJar.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/AndroidCookieJar.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/cookies/AndroidCookieJar.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/AndroidCookieJar.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/cookies/CookieWrapper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/CookieWrapper.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/cookies/CookieWrapper.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/CookieWrapper.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/cookies/MutableCookieJar.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/MutableCookieJar.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/cookies/MutableCookieJar.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/MutableCookieJar.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/cookies/PreferencesCookieJar.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/PreferencesCookieJar.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/core/network/cookies/PreferencesCookieJar.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/PreferencesCookieJar.kt
index cce51f827..4a709f38b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/network/cookies/PreferencesCookieJar.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/PreferencesCookieJar.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Cookie
import okhttp3.HttpUrl
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
private const val PREFS_NAME = "cookies"
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/os/NetworkState.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkState.kt
similarity index 91%
rename from app/src/main/java/org/koitharu/kotatsu/core/os/NetworkState.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkState.kt
index 0c3899bf6..63907bdde 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/os/NetworkState.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkState.kt
@@ -6,8 +6,8 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import kotlinx.coroutines.flow.first
-import org.koitharu.kotatsu.utils.MediatorStateFlow
-import org.koitharu.kotatsu.utils.ext.isOnline
+import org.koitharu.kotatsu.core.util.MediatorStateFlow
+import org.koitharu.kotatsu.core.util.ext.isOnline
class NetworkState(
private val connectivityManager: ConnectivityManager,
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt
similarity index 86%
rename from app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt
index 39708b1bd..4481c4038 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/ShortcutsUpdater.kt
@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
+import androidx.core.graphics.drawable.toBitmap
import androidx.room.InvalidationTracker
import coil.ImageLoader
import coil.request.ImageRequest
@@ -21,16 +22,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.history.domain.HistoryRepository
+import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
+import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
+import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.ui.ReaderActivity
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import org.koitharu.kotatsu.utils.ext.processLifecycleScope
-import org.koitharu.kotatsu.utils.ext.requireBitmap
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import javax.inject.Inject
import javax.inject.Singleton
@@ -92,6 +93,14 @@ class ShortcutsUpdater @Inject constructor(
return manager.maxShortcutCountPerActivity > 0
}
+ fun notifyMangaOpened(mangaId: Long) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
+ return
+ }
+ val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
+ manager.reportShortcutUsed(mangaId.toString())
+ }
+
@RequiresApi(Build.VERSION_CODES.N_MR1)
private suspend fun updateShortcutsImpl() = runCatchingCancellable {
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
@@ -122,7 +131,7 @@ class ShortcutsUpdater @Inject constructor(
.precision(Precision.EXACT)
.scale(Scale.FILL)
.build(),
- ).requireBitmap()
+ ).getDrawableOrThrow().toBitmap()
}.fold(
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/VoiceInputContract.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/VoiceInputContract.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/utils/VoiceInputContract.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/os/VoiceInputContract.kt
index ddb42ab45..15a6de48d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/VoiceInputContract.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/VoiceInputContract.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.os
import android.app.Activity
import android.content.Context
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt
similarity index 55%
rename from app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt
index 263253786..45c2e5df9 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt
@@ -1,43 +1,28 @@
-package org.koitharu.kotatsu.base.domain
+package org.koitharu.kotatsu.core.parser
-import android.graphics.BitmapFactory
-import android.net.Uri
-import android.util.Size
import androidx.room.withTransaction
import dagger.Reusable
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runInterruptible
import okhttp3.OkHttpClient
-import okhttp3.Request
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
import org.koitharu.kotatsu.core.db.entity.toEntities
import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
-import org.koitharu.kotatsu.core.network.CommonHeaders
-import org.koitharu.kotatsu.core.parser.MangaRepository
+import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
-import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
-import java.io.File
-import java.io.InputStream
-import java.util.zip.ZipFile
import javax.inject.Inject
-import kotlin.math.roundToInt
-
-private const val MIN_WEBTOON_RATIO = 2
@Reusable
class MangaDataRepository @Inject constructor(
- private val okHttpClient: OkHttpClient,
+ @MangaHttpClient private val okHttpClient: OkHttpClient,
private val db: MangaDatabase,
) {
@@ -106,65 +91,10 @@ class MangaDataRepository @Inject constructor(
}
}
- /**
- * Automatic determine type of manga by page size
- * @return ReaderMode.WEBTOON if page is wide
- */
- suspend fun determineMangaIsWebtoon(repository: MangaRepository, pages: List): Boolean {
- val pageIndex = (pages.size * 0.3).roundToInt()
- val page = requireNotNull(pages.getOrNull(pageIndex)) { "No pages" }
- val url = repository.getPageUrl(page)
- val uri = Uri.parse(url)
- val size = if (uri.scheme == "cbz") {
- runInterruptible(Dispatchers.IO) {
- val zip = ZipFile(uri.schemeSpecificPart)
- val entry = zip.getEntry(uri.fragment)
- zip.getInputStream(entry).use {
- getBitmapSize(it)
- }
- }
- } else {
- val request = Request.Builder()
- .url(url)
- .get()
- .tag(MangaSource::class.java, page.source)
- .cacheControl(CommonHeaders.CACHE_CONTROL_NO_STORE)
- .build()
- okHttpClient.newCall(request).await().use {
- runInterruptible(Dispatchers.IO) {
- getBitmapSize(it.body?.byteStream())
- }
- }
- }
- return size.width * MIN_WEBTOON_RATIO < size.height
- }
-
private fun newEntity(mangaId: Long) = MangaPrefsEntity(
mangaId = mangaId,
mode = -1,
cfBrightness = 0f,
cfContrast = 0f,
)
-
- companion object {
-
- suspend fun getImageMimeType(file: File): String? = runInterruptible(Dispatchers.IO) {
- val options = BitmapFactory.Options().apply {
- inJustDecodeBounds = true
- }
- BitmapFactory.decodeFile(file.path, options)?.recycle()
- options.outMimeType
- }
-
- private fun getBitmapSize(input: InputStream?): Size {
- val options = BitmapFactory.Options().apply {
- inJustDecodeBounds = true
- }
- BitmapFactory.decodeStream(input, null, options)?.recycle()
- val imageHeight: Int = options.outHeight
- val imageWidth: Int = options.outWidth
- check(imageHeight > 0 && imageWidth > 0)
- return Size(imageWidth, imageHeight)
- }
- }
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaIntent.kt
similarity index 63%
rename from app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaIntent.kt
index 55c34cb90..8ab582147 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaIntent.kt
@@ -1,39 +1,42 @@
-package org.koitharu.kotatsu.base.domain
+package org.koitharu.kotatsu.core.parser
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.lifecycle.SavedStateHandle
-import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
+import org.koitharu.kotatsu.core.ui.BaseActivity
+import org.koitharu.kotatsu.core.util.ext.getParcelableCompat
+import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.utils.ext.getParcelableCompat
-import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
class MangaIntent private constructor(
@JvmField val manga: Manga?,
- @JvmField val mangaId: Long,
+ @JvmField val id: Long,
@JvmField val uri: Uri?,
) {
constructor(intent: Intent?) : this(
manga = intent?.getParcelableExtraCompat(KEY_MANGA)?.manga,
- mangaId = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE,
+ id = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE,
uri = intent?.data,
)
constructor(savedStateHandle: SavedStateHandle) : this(
manga = savedStateHandle.get(KEY_MANGA)?.manga,
- mangaId = savedStateHandle[KEY_ID] ?: ID_NONE,
+ id = savedStateHandle[KEY_ID] ?: ID_NONE,
uri = savedStateHandle[BaseActivity.EXTRA_DATA],
)
constructor(args: Bundle?) : this(
manga = args?.getParcelableCompat(KEY_MANGA)?.manga,
- mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE,
+ id = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE,
uri = null,
)
+ val mangaId: Long
+ get() = if (id != ID_NONE) id else manga?.id ?: uri?.lastPathSegment?.toLongOrNull() ?: ID_NONE
+
companion object {
const val ID_NONE = 0L
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt
similarity index 92%
rename from app/src/main/java/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt
index 17f97c398..fe140190d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt
@@ -9,12 +9,13 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
+import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.prefs.SourceSettings
+import org.koitharu.kotatsu.core.util.ext.toList
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.utils.ext.toList
import java.lang.ref.WeakReference
import java.util.*
import javax.inject.Inject
@@ -24,7 +25,7 @@ import kotlin.coroutines.suspendCoroutine
@Singleton
class MangaLoaderContextImpl @Inject constructor(
- override val httpClient: OkHttpClient,
+ @MangaHttpClient override val httpClient: OkHttpClient,
override val cookieJar: MutableCookieJar,
@ApplicationContext private val androidContext: Context,
) : MangaLoaderContext() {
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaParser.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaParser.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/parser/MangaParser.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaParser.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaRepository.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaRepository.kt
index b90b10b52..36bbbc0c7 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaRepository.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.core.parser
import androidx.annotation.AnyThread
import org.koitharu.kotatsu.core.cache.ContentCache
-import org.koitharu.kotatsu.local.domain.LocalMangaRepository
+import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaTagHighlighter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaTagHighlighter.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/parser/MangaTagHighlighter.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaTagHighlighter.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt
index a99cd9acc..0f3d6d482 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt
@@ -14,6 +14,7 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.cache.SafeDeferred
import org.koitharu.kotatsu.core.prefs.SourceSettings
+import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -25,8 +26,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.domain
-import org.koitharu.kotatsu.utils.ext.processLifecycleScope
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
+import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
class RemoteMangaRepository(
private val parser: MangaParser,
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt
similarity index 90%
rename from app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt
index ab403a43c..be0d0dcfc 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt
@@ -19,10 +19,13 @@ import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.internal.closeQuietly
+import okio.Closeable
+import okio.buffer
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.local.data.CacheDir
+import org.koitharu.kotatsu.local.data.util.withExtraCloseable
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.await
import java.net.HttpURLConnection
@@ -54,7 +57,9 @@ class FaviconFetcher(
val icon = checkNotNull(favicons.find(sizePx)) { "No favicons found" }
val response = loadIcon(icon.url, mangaSource)
val responseBody = response.requireBody()
- val source = writeToDiskCache(responseBody)?.toImageSource() ?: responseBody.toImageSource()
+ val source = writeToDiskCache(responseBody)?.toImageSource()?.also {
+ response.closeQuietly()
+ } ?: responseBody.toImageSource(response)
return SourceResult(
source = source,
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(icon.type),
@@ -71,7 +76,7 @@ class FaviconFetcher(
options.tags.asMap().forEach { request.tag(it.key as Class, it.value) }
val response = okHttpClient.newCall(request.build()).await()
if (!response.isSuccessful && response.code != HttpURLConnection.HTTP_NOT_MODIFIED) {
- response.body?.closeQuietly()
+ response.closeQuietly()
throw HttpException(response)
}
return response
@@ -116,8 +121,12 @@ class FaviconFetcher(
return ImageSource(data, fileSystem, diskCacheKey, this)
}
- private fun ResponseBody.toImageSource(): ImageSource {
- return ImageSource(source(), options.context, FaviconMetadata(mangaSource))
+ private fun ResponseBody.toImageSource(response: Closeable): ImageSource {
+ return ImageSource(
+ source().withExtraCloseable(response).buffer(),
+ options.context,
+ FaviconMetadata(mangaSource),
+ )
}
private fun Response.toDataSource(): DataSource {
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconUri.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconUri.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconUri.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconUri.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt
similarity index 82%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt
index 471c3eece..e3a2e2b5c 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt
@@ -2,7 +2,9 @@ package org.koitharu.kotatsu.core.prefs
import android.content.Context
import android.content.SharedPreferences
+import android.net.ConnectivityManager
import android.net.Uri
+import android.os.Build
import android.provider.Settings
import androidx.annotation.FloatRange
import androidx.appcompat.app.AppCompatDelegate
@@ -14,16 +16,18 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.network.DoHProvider
+import org.koitharu.kotatsu.core.util.ext.connectivityManager
+import org.koitharu.kotatsu.core.util.ext.filterToSet
+import org.koitharu.kotatsu.core.util.ext.getEnumValue
+import org.koitharu.kotatsu.core.util.ext.observe
+import org.koitharu.kotatsu.core.util.ext.putEnumValue
+import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.SortOrder
-import org.koitharu.kotatsu.shelf.domain.ShelfSection
-import org.koitharu.kotatsu.utils.ext.connectivityManager
-import org.koitharu.kotatsu.utils.ext.filterToSet
-import org.koitharu.kotatsu.utils.ext.getEnumValue
-import org.koitharu.kotatsu.utils.ext.observe
-import org.koitharu.kotatsu.utils.ext.putEnumValue
-import org.koitharu.kotatsu.utils.ext.toUriOrNull
+import org.koitharu.kotatsu.parsers.util.mapToSet
+import org.koitharu.kotatsu.shelf.domain.model.ShelfSection
import java.io.File
+import java.net.Proxy
import java.util.Collections
import java.util.EnumSet
import java.util.Locale
@@ -165,6 +169,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getBoolean(KEY_PROTECT_APP_BIOMETRIC, true)
set(value) = prefs.edit { putBoolean(KEY_PROTECT_APP_BIOMETRIC, value) }
+ val isMirrorSwitchingAvailable: Boolean
+ get() = prefs.getBoolean(KEY_MIRROR_SWITCHING, true)
+
val isExitConfirmationEnabled: Boolean
get() = prefs.getBoolean(KEY_EXIT_CONFIRM, false)
@@ -174,10 +181,14 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isUnstableUpdatesAllowed: Boolean
get() = prefs.getBoolean(KEY_UPDATES_UNSTABLE, false)
- fun isContentPrefetchEnabled(): Boolean {
- val policy = NetworkPolicy.from(prefs.getString(KEY_PREFETCH_CONTENT, null), NetworkPolicy.NEVER)
- return policy.isNetworkAllowed(connectivityManager)
- }
+ val isContentPrefetchEnabled: Boolean
+ get() {
+ if (isBackgroundNetworkRestricted()) {
+ return false
+ }
+ val policy = NetworkPolicy.from(prefs.getString(KEY_PREFETCH_CONTENT, null), NetworkPolicy.NEVER)
+ return policy.isNetworkAllowed(connectivityManager)
+ }
var sourcesOrder: List
get() = prefs.getString(KEY_SOURCES_ORDER, null)
@@ -238,15 +249,28 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isDownloadsSlowdownEnabled: Boolean
get() = prefs.getBoolean(KEY_DOWNLOADS_SLOWDOWN, false)
- val downloadsParallelism: Int
- get() = prefs.getInt(KEY_DOWNLOADS_PARALLELISM, 2)
+ val isDownloadsWiFiOnly: Boolean
+ get() = prefs.getBoolean(KEY_DOWNLOADS_WIFI, false)
- val isSuggestionsEnabled: Boolean
+ var isSuggestionsEnabled: Boolean
get() = prefs.getBoolean(KEY_SUGGESTIONS, false)
+ set(value) = prefs.edit { putBoolean(KEY_SUGGESTIONS, value) }
val isSuggestionsExcludeNsfw: Boolean
get() = prefs.getBoolean(KEY_SUGGESTIONS_EXCLUDE_NSFW, false)
+ val isSuggestionsNotificationAvailable: Boolean
+ get() = prefs.getBoolean(KEY_SUGGESTIONS_NOTIFICATIONS, true)
+
+ val suggestionsTagsBlacklist: Set
+ get() {
+ val string = prefs.getString(KEY_SUGGESTIONS_EXCLUDE_TAGS, null)?.trimEnd(' ', ',')
+ if (string.isNullOrEmpty()) {
+ return emptySet()
+ }
+ return string.split(',').mapToSet { it.trim() }
+ }
+
val isReaderBarEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_BAR, true)
@@ -259,6 +283,18 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isSSLBypassEnabled: Boolean
get() = prefs.getBoolean(KEY_SSL_BYPASS, false)
+ val proxyType: Proxy.Type
+ get() {
+ val raw = prefs.getString(KEY_PROXY_TYPE, null) ?: return Proxy.Type.DIRECT
+ return enumValues().find { it.name == raw } ?: Proxy.Type.DIRECT
+ }
+
+ val proxyAddress: String?
+ get() = prefs.getString(KEY_PROXY_ADDRESS, null)
+
+ val proxyPort: Int
+ get() = prefs.getString(KEY_PROXY_PORT, null)?.toIntOrNull() ?: 0
+
var localListOrder: SortOrder
get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
@@ -271,22 +307,14 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getFloat(KEY_READER_AUTOSCROLL_SPEED, 0f)
set(@FloatRange(from = 0.0, to = 1.0) value) = prefs.edit { putFloat(KEY_READER_AUTOSCROLL_SPEED, value) }
- fun isPagesPreloadEnabled(): Boolean {
- val policy = NetworkPolicy.from(prefs.getString(KEY_PAGES_PRELOAD, null), NetworkPolicy.NON_METERED)
- return policy.isNetworkAllowed(connectivityManager)
- }
-
- fun getSuggestionsTagsBlacklistRegex(): Regex? {
- val string = prefs.getString(KEY_SUGGESTIONS_EXCLUDE_TAGS, null)?.trimEnd(' ', ',')
- if (string.isNullOrEmpty()) {
- return null
+ val isPagesPreloadEnabled: Boolean
+ get() {
+ if (isBackgroundNetworkRestricted()) {
+ return false
+ }
+ val policy = NetworkPolicy.from(prefs.getString(KEY_PAGES_PRELOAD, null), NetworkPolicy.NON_METERED)
+ return policy.isNetworkAllowed(connectivityManager)
}
- val tags = string.split(',')
- val regex = tags.joinToString(prefix = "(", separator = "|", postfix = ")") { tag ->
- Regex.escape(tag.trim())
- }
- return Regex(regex, RegexOption.IGNORE_CASE)
- }
fun getMangaSources(includeHidden: Boolean): List {
val list = remoteSources.toMutableList()
@@ -324,6 +352,14 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
fun observe() = prefs.observe()
+ private fun isBackgroundNetworkRestricted(): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ connectivityManager.restrictBackgroundStatus == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED
+ } else {
+ false
+ }
+ }
+
companion object {
const val PAGE_SWITCH_TAPS = "taps"
@@ -340,6 +376,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_SOURCES_HIDDEN = "sources_hidden"
const val KEY_TRAFFIC_WARNING = "traffic_warning"
const val KEY_PAGES_CACHE_CLEAR = "pages_cache_clear"
+ const val KEY_HTTP_CACHE_CLEAR = "http_cache_clear"
const val KEY_COOKIES_CLEAR = "cookies_clear"
const val KEY_THUMBS_CACHE_CLEAR = "thumbs_cache_clear"
const val KEY_SEARCH_HISTORY_CLEAR = "search_history_clear"
@@ -378,17 +415,19 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_SUGGESTIONS = "suggestions"
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"
const val KEY_SUGGESTIONS_EXCLUDE_TAGS = "suggestions_exclude_tags"
+ const val KEY_SUGGESTIONS_NOTIFICATIONS = "suggestions_notifications"
const val KEY_SHIKIMORI = "shikimori"
const val KEY_ANILIST = "anilist"
const val KEY_MAL = "mal"
const val KEY_KITSU = "kitsu"
- const val KEY_DOWNLOADS_PARALLELISM = "downloads_parallelism"
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
+ const val KEY_DOWNLOADS_WIFI = "downloads_wifi"
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
const val KEY_DOH = "doh"
const val KEY_EXIT_CONFIRM = "exit_confirm"
const val KEY_INCOGNITO_MODE = "incognito"
const val KEY_SYNC = "sync"
+ const val KEY_SYNC_SETTINGS = "sync_settings"
const val KEY_READER_BAR = "reader_bar"
const val KEY_READER_SLIDER = "reader_slider"
const val KEY_SHORTCUTS = "dynamic_shortcuts"
@@ -405,6 +444,11 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_TIPS_CLOSED = "tips_closed"
const val KEY_SSL_BYPASS = "ssl_bypass"
const val KEY_READER_AUTOSCROLL_SPEED = "as_speed"
+ const val KEY_MIRROR_SWITCHING = "mirror_switching"
+ const val KEY_PROXY = "proxy"
+ const val KEY_PROXY_TYPE = "proxy_type"
+ const val KEY_PROXY_ADDRESS = "proxy_address"
+ const val KEY_PROXY_PORT = "proxy_port"
// About
const val KEY_APP_UPDATE = "app_update"
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt
similarity index 69%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt
index 606ae9d84..ed6d14f68 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt
@@ -1,13 +1,11 @@
package org.koitharu.kotatsu.core.prefs
-import androidx.lifecycle.liveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transform
-import kotlin.coroutines.CoroutineContext
fun AppSettings.observeAsFlow(key: String, valueProducer: AppSettings.() -> T) = flow {
var lastValue: T = valueProducer()
@@ -23,25 +21,9 @@ fun AppSettings.observeAsFlow(key: String, valueProducer: AppSettings.() ->
}
}
-fun AppSettings.observeAsLiveData(
- context: CoroutineContext,
- key: String,
- valueProducer: AppSettings.() -> T,
-) = liveData(context) {
- emit(valueProducer())
- observe().collect {
- if (it == key) {
- val value = valueProducer()
- if (value != latestValue) {
- emit(value)
- }
- }
- }
-}
-
fun AppSettings.observeAsStateFlow(
- key: String,
scope: CoroutineScope,
+ key: String,
valueProducer: AppSettings.() -> T,
): StateFlow = observe().transform {
if (it == key) {
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/ColorScheme.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ColorScheme.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/ColorScheme.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ColorScheme.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/ListMode.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ListMode.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/ListMode.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ListMode.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/NetworkPolicy.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/NetworkPolicy.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/NetworkPolicy.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/NetworkPolicy.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/ReaderMode.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderMode.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/ReaderMode.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderMode.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt
similarity index 100%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/SourceSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/core/prefs/SourceSettings.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt
index 1b3af7980..0080c0b1d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/SourceSettings.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt
@@ -2,13 +2,13 @@ package org.koitharu.kotatsu.core.prefs
import android.content.Context
import androidx.core.content.edit
+import org.koitharu.kotatsu.core.util.ext.getEnumValue
+import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
+import org.koitharu.kotatsu.core.util.ext.putEnumValue
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.SortOrder
-import org.koitharu.kotatsu.utils.ext.getEnumValue
-import org.koitharu.kotatsu.utils.ext.ifNullOrEmpty
-import org.koitharu.kotatsu.utils.ext.putEnumValue
private const val KEY_SORT_ORDER = "sort_order"
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/AlertDialogFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/AlertDialogFragment.kt
similarity index 54%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/AlertDialogFragment.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/AlertDialogFragment.kt
index a667fec32..40c091d4d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/AlertDialogFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/AlertDialogFragment.kt
@@ -1,8 +1,9 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.appcompat.app.AlertDialog
@@ -12,13 +13,15 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
abstract class AlertDialogFragment : DialogFragment() {
- private var viewBinding: B? = null
+ var viewBinding: B? = null
+ private set
+ @Deprecated("", ReplaceWith("requireViewBinding()"))
protected val binding: B
- get() = checkNotNull(viewBinding)
+ get() = requireViewBinding()
final override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val binding = onInflateView(layoutInflater, null)
+ val binding = onCreateViewBinding(layoutInflater, null)
viewBinding = binding
return MaterialAlertDialogBuilder(requireContext(), theme)
.setView(binding.root)
@@ -32,6 +35,11 @@ abstract class AlertDialogFragment : DialogFragment() {
savedInstanceState: Bundle?,
) = viewBinding?.root
+ final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ onViewBindingCreated(requireViewBinding(), savedInstanceState)
+ }
+
@CallSuper
override fun onDestroyView() {
viewBinding = null
@@ -42,7 +50,14 @@ abstract class AlertDialogFragment : DialogFragment() {
open fun onDialogCreated(dialog: AlertDialog) = Unit
- protected fun bindingOrNull(): B? = viewBinding
+ @Deprecated("", ReplaceWith("viewBinding"))
+ protected fun bindingOrNull() = viewBinding
- protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
+ fun requireViewBinding(): B = checkNotNull(viewBinding) {
+ "Fragment $this did not return a ViewBinding from onCreateView() or this was called before onCreateView()."
+ }
+
+ protected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B
+
+ protected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt
similarity index 81%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt
index 234b1192c..f547e527a 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt
@@ -1,7 +1,9 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
import android.content.Intent
import android.content.res.Configuration
+import android.graphics.Color
+import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.MenuItem
@@ -23,11 +25,11 @@ import androidx.viewbinding.ViewBinding
import dagger.hilt.android.EntryPointAccessors
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.util.ActionModeDelegate
-import org.koitharu.kotatsu.base.ui.util.BaseActivityEntryPoint
-import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
-import org.koitharu.kotatsu.utils.ext.getThemeColor
+import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate
+import org.koitharu.kotatsu.core.ui.util.BaseActivityEntryPoint
+import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
+import org.koitharu.kotatsu.core.util.ext.getThemeColor
@Suppress("LeakingThis")
abstract class BaseActivity :
@@ -36,7 +38,7 @@ abstract class BaseActivity :
private var isAmoledTheme = false
- protected lateinit var binding: B
+ lateinit var viewBinding: B
private set
@JvmField
@@ -48,6 +50,8 @@ abstract class BaseActivity :
@JvmField
val actionModeDelegate = ActionModeDelegate()
+ private var defaultStatusBarColor = Color.TRANSPARENT
+
override fun onCreate(savedInstanceState: Bundle?) {
val settings = EntryPointAccessors.fromApplication(this, BaseActivityEntryPoint::class.java).settings
isAmoledTheme = settings.isAmoledTheme
@@ -84,7 +88,7 @@ abstract class BaseActivity :
}
protected fun setContentView(binding: B) {
- this.binding = binding
+ this.viewBinding = binding
super.setContentView(binding.root)
val toolbar = (binding.root.findViewById(R.id.toolbar) as? Toolbar)
toolbar?.let(this::setSupportActionBar)
@@ -119,11 +123,15 @@ abstract class BaseActivity :
override fun onSupportActionModeStarted(mode: ActionMode) {
super.onSupportActionModeStarted(mode)
actionModeDelegate.onSupportActionModeStarted(mode)
- val actionModeColor = ColorUtils.compositeColors(
- ContextCompat.getColor(this, com.google.android.material.R.color.m3_appbar_overlay_color),
- getThemeColor(com.google.android.material.R.attr.colorSurface),
- )
- val insets = ViewCompat.getRootWindowInsets(binding.root)
+ val actionModeColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ ColorUtils.compositeColors(
+ ContextCompat.getColor(this, com.google.android.material.R.color.m3_appbar_overlay_color),
+ getThemeColor(com.google.android.material.R.attr.colorSurface),
+ )
+ } else {
+ ContextCompat.getColor(this, R.color.kotatsu_secondaryContainer)
+ }
+ val insets = ViewCompat.getRootWindowInsets(viewBinding.root)
?.getInsets(WindowInsetsCompat.Type.systemBars()) ?: return
findViewById(androidx.appcompat.R.id.action_mode_bar).apply {
setBackgroundColor(actionModeColor)
@@ -131,6 +139,7 @@ abstract class BaseActivity :
topMargin = insets.top
}
}
+ defaultStatusBarColor = window.statusBarColor
window.statusBarColor = actionModeColor
}
@@ -138,7 +147,7 @@ abstract class BaseActivity :
override fun onSupportActionModeFinished(mode: ActionMode) {
super.onSupportActionModeFinished(mode)
actionModeDelegate.onSupportActionModeFinished(mode)
- window.statusBarColor = getThemeColor(android.R.attr.statusBarColor)
+ window.statusBarColor = defaultStatusBarColor
}
private fun putDataToExtras(intent: Intent?) {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseBottomSheet.kt
similarity index 73%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseBottomSheet.kt
index 6207ce83f..9c81104a1 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseBottomSheet.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
import android.app.Dialog
import android.os.Bundle
@@ -13,17 +13,19 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.dialog.AppBottomSheetDialog
-import org.koitharu.kotatsu.utils.ext.findActivity
-import org.koitharu.kotatsu.utils.ext.getDisplaySize
+import org.koitharu.kotatsu.core.ui.dialog.AppBottomSheetDialog
+import org.koitharu.kotatsu.core.util.ext.findActivity
+import org.koitharu.kotatsu.core.util.ext.getDisplaySize
import com.google.android.material.R as materialR
abstract class BaseBottomSheet : BottomSheetDialogFragment() {
- private var viewBinding: B? = null
+ var viewBinding: B? = null
+ private set
+ @Deprecated("", ReplaceWith("requireViewBinding()"))
protected val binding: B
- get() = checkNotNull(viewBinding)
+ get() = requireViewBinding()
protected val behavior: BottomSheetBehavior<*>?
get() = (dialog as? BottomSheetDialog)?.behavior
@@ -39,13 +41,14 @@ abstract class BaseBottomSheet : BottomSheetDialogFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
- val binding = onInflateView(inflater, container)
+ val binding = onCreateViewBinding(inflater, container)
viewBinding = binding
return binding.root
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ val binding = requireViewBinding()
// Enforce max width for tablets
val width = resources.getDimensionPixelSize(R.dimen.bottom_sheet_width)
if (width > 0) {
@@ -55,6 +58,7 @@ abstract class BaseBottomSheet : BottomSheetDialogFragment() {
binding.root.context.findActivity()?.getDisplaySize()?.let {
behavior?.peekHeight = (it.height() * 0.4).toInt()
}
+ onViewBindingCreated(binding, savedInstanceState)
}
override fun onDestroyView() {
@@ -75,7 +79,9 @@ abstract class BaseBottomSheet : BottomSheetDialogFragment() {
}
}
- protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
+ protected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B
+
+ protected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit
protected fun setExpanded(isExpanded: Boolean, isLocked: Boolean) {
val b = behavior ?: return
@@ -89,4 +95,8 @@ abstract class BaseBottomSheet : BottomSheetDialogFragment() {
}
b.isDraggable = !isLocked
}
+
+ fun requireViewBinding(): B = checkNotNull(viewBinding) {
+ "Fragment $this did not return a ViewBinding from onCreateView() or this was called before onCreateView()."
+ }
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFragment.kt
similarity index 53%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFragment.kt
index 697016c9a..6dfdadf1d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFragment.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
import android.os.Bundle
import android.view.LayoutInflater
@@ -6,19 +6,21 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
-import org.koitharu.kotatsu.base.ui.util.ActionModeDelegate
-import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
+import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate
+import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
@Suppress("LeakingThis")
abstract class BaseFragment :
Fragment(),
WindowInsetsDelegate.WindowInsetsListener {
- private var viewBinding: B? = null
+ var viewBinding: B? = null
+ private set
+ @Deprecated("", ReplaceWith("requireViewBinding()"))
protected val binding: B
- get() = checkNotNull(viewBinding)
+ get() = requireViewBinding()
@JvmField
protected val exceptionResolver = ExceptionResolver(this)
@@ -29,19 +31,20 @@ abstract class BaseFragment :
protected val actionModeDelegate: ActionModeDelegate
get() = (requireActivity() as BaseActivity<*>).actionModeDelegate
- override fun onCreateView(
+ final override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
- ): View? {
- val binding = onInflateView(inflater, container)
+ ): View {
+ val binding = onCreateViewBinding(inflater, container)
viewBinding = binding
return binding.root
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
insetsDelegate.onViewCreated(view)
+ onViewBindingCreated(requireViewBinding(), savedInstanceState)
}
override fun onDestroyView() {
@@ -50,7 +53,14 @@ abstract class BaseFragment :
super.onDestroyView()
}
+ fun requireViewBinding(): B = checkNotNull(viewBinding) {
+ "Fragment $this did not return a ViewBinding from onCreateView() or this was called before onCreateView()."
+ }
+
+ @Deprecated("", ReplaceWith("viewBinding"))
protected fun bindingOrNull() = viewBinding
- protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
+ protected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B
+
+ protected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFullscreenActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFullscreenActivity.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt
index e43ca8877..96a240e55 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFullscreenActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
import android.graphics.Color
import android.os.Build
@@ -56,4 +56,4 @@ abstract class BaseFullscreenActivity :
}
protected open fun onSystemUiVisibilityChanged(isVisible: Boolean) = Unit
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BasePreferenceFragment.kt
similarity index 88%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BasePreferenceFragment.kt
index 809944e8c..bfffb7ab3 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BasePreferenceFragment.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
import android.os.Bundle
import android.view.View
@@ -9,9 +9,9 @@ import androidx.core.view.updatePadding
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
-import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
-import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.prefs.AppSettings
+import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
+import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.settings.SettingsHeadersFragment
import javax.inject.Inject
@@ -56,6 +56,7 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
)
}
+ @Suppress("UsePropertyAccessSyntax")
protected fun setTitle(title: CharSequence) {
(parentFragment as? SettingsHeadersFragment)?.setTitle(title)
?: activity?.setTitle(title)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseService.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseService.kt
new file mode 100644
index 000000000..7a8f1463c
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseService.kt
@@ -0,0 +1,5 @@
+package org.koitharu.kotatsu.core.ui
+
+import androidx.lifecycle.LifecycleService
+
+abstract class BaseService : LifecycleService()
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseViewModel.kt
similarity index 57%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseViewModel.kt
index ac5f78b09..74948fcdb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseViewModel.kt
@@ -1,6 +1,5 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
-import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CancellationException
@@ -8,26 +7,34 @@ import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-import org.koitharu.kotatsu.base.ui.util.CountedBooleanLiveData
-import org.koitharu.kotatsu.utils.SingleLiveEvent
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
+import org.koitharu.kotatsu.core.util.ext.EventFlow
+import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
+import org.koitharu.kotatsu.core.util.ext.call
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
abstract class BaseViewModel : ViewModel() {
@JvmField
- protected val loadingCounter = CountedBooleanLiveData()
+ protected val loadingCounter = MutableStateFlow(0)
@JvmField
- protected val errorEvent = SingleLiveEvent()
+ protected val errorEvent = MutableEventFlow()
- val onError: LiveData
+ val onError: EventFlow
get() = errorEvent
- val isLoading: LiveData
- get() = loadingCounter
+ val isLoading: StateFlow
+ get() = loadingCounter.map { it > 0 }
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
protected fun launchJob(
context: CoroutineContext = EmptyCoroutineContext,
@@ -51,7 +58,11 @@ abstract class BaseViewModel : ViewModel() {
private fun createErrorHandler() = CoroutineExceptionHandler { _, throwable ->
throwable.printStackTraceDebug()
if (throwable !is CancellationException) {
- errorEvent.postCall(throwable)
+ errorEvent.call(throwable)
}
}
+
+ protected fun MutableStateFlow.increment() = update { it + 1 }
+
+ protected fun MutableStateFlow.decrement() = update { it - 1 }
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/CoroutineIntentService.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/CoroutineIntentService.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/CoroutineIntentService.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/CoroutineIntentService.kt
index 1fd56bd94..dc1c59bd4 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/CoroutineIntentService.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/CoroutineIntentService.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
import android.content.Intent
import androidx.lifecycle.lifecycleScope
@@ -9,7 +9,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
abstract class CoroutineIntentService : BaseService() {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/DefaultActivityLifecycleCallbacks.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/DefaultActivityLifecycleCallbacks.kt
similarity index 94%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/DefaultActivityLifecycleCallbacks.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/DefaultActivityLifecycleCallbacks.kt
index dd83e4dc7..f97db54ae 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/DefaultActivityLifecycleCallbacks.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/DefaultActivityLifecycleCallbacks.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui
+package org.koitharu.kotatsu.core.ui
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/AppBottomSheetDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/AppBottomSheetDialog.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/AppBottomSheetDialog.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/AppBottomSheetDialog.kt
index 8b6da8d3d..f76e27d11 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/AppBottomSheetDialog.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/AppBottomSheetDialog.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.dialog
+package org.koitharu.kotatsu.core.ui.dialog
import android.content.Context
import android.graphics.Color
@@ -26,4 +26,4 @@ class AppBottomSheetDialog(context: Context, theme: Int) : BottomSheetDialog(con
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/CheckBoxAlertDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/CheckBoxAlertDialog.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/CheckBoxAlertDialog.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/CheckBoxAlertDialog.kt
index c452bd1ce..f246aba42 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/CheckBoxAlertDialog.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/CheckBoxAlertDialog.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.dialog
+package org.koitharu.kotatsu.core.ui.dialog
import android.content.Context
import android.content.DialogInterface
@@ -77,4 +77,4 @@ class CheckBoxAlertDialog private constructor(private val delegate: AlertDialog)
fun create() = CheckBoxAlertDialog(delegate.create())
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/ErrorDetailsDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/ErrorDetailsDialog.kt
similarity index 80%
rename from app/src/main/java/org/koitharu/kotatsu/core/ui/ErrorDetailsDialog.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/ErrorDetailsDialog.kt
index a9bb5eb8a..6f9d0f12c 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/ui/ErrorDetailsDialog.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/ErrorDetailsDialog.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.core.ui
+package org.koitharu.kotatsu.core.ui.dialog
import android.content.ClipData
import android.content.ClipboardManager
@@ -6,7 +6,6 @@ import android.content.Context
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.core.text.HtmlCompat
import androidx.core.text.htmlEncode
@@ -14,12 +13,12 @@ import androidx.core.text.parseAsHtml
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.AlertDialogFragment
+import org.koitharu.kotatsu.core.ui.AlertDialogFragment
+import org.koitharu.kotatsu.core.util.ext.isReportable
+import org.koitharu.kotatsu.core.util.ext.report
+import org.koitharu.kotatsu.core.util.ext.requireSerializable
+import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.DialogErrorDetailsBinding
-import org.koitharu.kotatsu.utils.ext.isReportable
-import org.koitharu.kotatsu.utils.ext.report
-import org.koitharu.kotatsu.utils.ext.requireSerializable
-import org.koitharu.kotatsu.utils.ext.withArgs
class ErrorDetailsDialog : AlertDialogFragment() {
@@ -31,12 +30,12 @@ class ErrorDetailsDialog : AlertDialogFragment() {
exception = args.requireSerializable(ARG_ERROR)
}
- override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): DialogErrorDetailsBinding {
+ override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogErrorDetailsBinding {
return DialogErrorDetailsBinding.inflate(inflater, container, false)
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ override fun onViewBindingCreated(binding: DialogErrorDetailsBinding, savedInstanceState: Bundle?) {
+ super.onViewBindingCreated(binding, savedInstanceState)
with(binding.textViewMessage) {
movementMethod = LinkMovementMethod.getInstance()
text = context.getString(
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/RecyclerViewAlertDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/RecyclerViewAlertDialog.kt
new file mode 100644
index 000000000..3199138e4
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/RecyclerViewAlertDialog.kt
@@ -0,0 +1,93 @@
+package org.koitharu.kotatsu.core.ui.dialog
+
+import android.content.Context
+import android.content.DialogInterface
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
+import androidx.core.view.updatePadding
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
+import com.hannesdorfmann.adapterdelegates4.AdapterDelegatesManager
+import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
+import org.koitharu.kotatsu.R
+
+class RecyclerViewAlertDialog private constructor(
+ private val delegate: AlertDialog
+) : DialogInterface by delegate {
+
+ fun show() = delegate.show()
+
+ class Builder(context: Context) {
+
+ private val recyclerView = RecyclerView(context)
+ private val delegatesManager = AdapterDelegatesManager>()
+ private var items: List? = null
+
+ private val delegate = MaterialAlertDialogBuilder(context)
+ .setView(recyclerView)
+
+ init {
+ recyclerView.layoutManager = LinearLayoutManager(context)
+ recyclerView.updatePadding(
+ top = context.resources.getDimensionPixelOffset(R.dimen.list_spacing),
+ )
+ recyclerView.clipToPadding = false
+ }
+
+ fun setTitle(@StringRes titleResId: Int): Builder {
+ delegate.setTitle(titleResId)
+ return this
+ }
+
+ fun setTitle(title: CharSequence): Builder {
+ delegate.setTitle(title)
+ return this
+ }
+
+ fun setIcon(@DrawableRes iconId: Int): Builder {
+ delegate.setIcon(iconId)
+ return this
+ }
+
+ fun setPositiveButton(
+ @StringRes textId: Int,
+ listener: DialogInterface.OnClickListener,
+ ): Builder {
+ delegate.setPositiveButton(textId, listener)
+ return this
+ }
+
+ fun setNegativeButton(
+ @StringRes textId: Int,
+ listener: DialogInterface.OnClickListener? = null
+ ): Builder {
+ delegate.setNegativeButton(textId, listener)
+ return this
+ }
+
+ fun setCancelable(isCancelable: Boolean): Builder {
+ delegate.setCancelable(isCancelable)
+ return this
+ }
+
+ fun addAdapterDelegate(subject: AdapterDelegate>): Builder {
+ delegatesManager.addDelegate(subject)
+ return this
+ }
+
+ fun setItems(list: List): Builder {
+ items = list
+ return this
+ }
+
+ fun create(): RecyclerViewAlertDialog {
+ recyclerView.adapter = ListDelegationAdapter(delegatesManager).also {
+ it.items = items
+ }
+ return RecyclerViewAlertDialog(delegate.create())
+ }
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/RememberSelectionDialogListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/RememberSelectionDialogListener.kt
similarity index 85%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/RememberSelectionDialogListener.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/RememberSelectionDialogListener.kt
index 7783a564b..e98e5d992 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/RememberSelectionDialogListener.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/RememberSelectionDialogListener.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.dialog
+package org.koitharu.kotatsu.core.ui.dialog
import android.content.DialogInterface
@@ -10,4 +10,4 @@ class RememberSelectionDialogListener(initialValue: Int) : DialogInterface.OnCli
override fun onClick(dialog: DialogInterface?, which: Int) {
selection = which
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/StorageSelectDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/StorageSelectDialog.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/StorageSelectDialog.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/StorageSelectDialog.kt
index 58e353ca8..efc47ffde 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/StorageSelectDialog.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/StorageSelectDialog.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.dialog
+package org.koitharu.kotatsu.core.ui.dialog
import android.content.Context
import android.content.DialogInterface
@@ -98,4 +98,4 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
fun onStorageSelected(file: File)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/TwoButtonsAlertDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/TwoButtonsAlertDialog.kt
new file mode 100644
index 000000000..4d15077e1
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/TwoButtonsAlertDialog.kt
@@ -0,0 +1,79 @@
+package org.koitharu.kotatsu.core.ui.dialog
+
+import android.content.Context
+import android.content.DialogInterface
+import android.view.LayoutInflater
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
+import androidx.core.view.isVisible
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.koitharu.kotatsu.databinding.DialogTwoButtonsBinding
+
+class TwoButtonsAlertDialog private constructor(
+ private val delegate: AlertDialog
+) : DialogInterface by delegate {
+
+ fun show() = delegate.show()
+
+ class Builder(context: Context) {
+
+ private val binding = DialogTwoButtonsBinding.inflate(LayoutInflater.from(context))
+
+ private val delegate = MaterialAlertDialogBuilder(context)
+ .setView(binding.root)
+
+ fun setTitle(@StringRes titleResId: Int): Builder {
+ binding.title.setText(titleResId)
+ return this
+ }
+
+ fun setTitle(title: CharSequence): Builder {
+ binding.title.text = title
+ return this
+ }
+
+ fun setIcon(@DrawableRes iconId: Int): Builder {
+ binding.icon.setImageResource(iconId)
+ return this
+ }
+
+ fun setPositiveButton(
+ @StringRes textId: Int,
+ listener: DialogInterface.OnClickListener,
+ ): Builder {
+ initButton(binding.button1, DialogInterface.BUTTON_POSITIVE, textId, listener)
+ return this
+ }
+
+ fun setNegativeButton(
+ @StringRes textId: Int,
+ listener: DialogInterface.OnClickListener? = null
+ ): Builder {
+ initButton(binding.button2, DialogInterface.BUTTON_NEGATIVE, textId, listener)
+ return this
+ }
+
+ fun create(): TwoButtonsAlertDialog {
+ val dialog = delegate.create()
+ binding.root.tag = dialog
+ return TwoButtonsAlertDialog(dialog)
+ }
+
+ private fun initButton(
+ button: MaterialButton,
+ which: Int,
+ @StringRes textId: Int,
+ listener: DialogInterface.OnClickListener?,
+ ) {
+ button.setText(textId)
+ button.isVisible = true
+ button.setOnClickListener {
+ val dialog = binding.root.tag as DialogInterface
+ listener?.onClick(dialog, which)
+ dialog.dismiss()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/CoilImageGetter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoilImageGetter.kt
similarity index 94%
rename from app/src/main/java/org/koitharu/kotatsu/utils/image/CoilImageGetter.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoilImageGetter.kt
index 381d71d9f..7c5a5467f 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/image/CoilImageGetter.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoilImageGetter.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.image
+package org.koitharu.kotatsu.core.ui.image
import android.content.Context
import android.graphics.drawable.Drawable
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/CoverSizeResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/utils/image/CoverSizeResolver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt
index 69f61133f..43d662759 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/image/CoverSizeResolver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.image
+package org.koitharu.kotatsu.core.ui.image
import android.view.View
import android.view.View.OnLayoutChangeListener
@@ -7,10 +7,10 @@ import android.widget.ImageView
import coil.size.Dimension
import coil.size.Size
import coil.size.SizeResolver
-import kotlin.coroutines.resume
-import kotlin.math.roundToInt
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+import kotlin.math.roundToInt
private const val ASPECT_RATIO_HEIGHT = 18f
private const val ASPECT_RATIO_WIDTH = 13f
@@ -80,4 +80,4 @@ class CoverSizeResolver(
continuation.resume(size)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/FaviconFallbackDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconFallbackDrawable.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/utils/image/FaviconFallbackDrawable.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconFallbackDrawable.kt
index f6fdaa7df..ab065e004 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/image/FaviconFallbackDrawable.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconFallbackDrawable.kt
@@ -1,7 +1,12 @@
-package org.koitharu.kotatsu.utils.image
+package org.koitharu.kotatsu.core.ui.image
import android.content.Context
-import android.graphics.*
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import androidx.core.graphics.ColorUtils
import com.google.android.material.color.MaterialColors
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/RegionBitmapDecoder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/RegionBitmapDecoder.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/utils/image/RegionBitmapDecoder.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/RegionBitmapDecoder.kt
index 9736f6776..47d5461cb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/image/RegionBitmapDecoder.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/RegionBitmapDecoder.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.image
+package org.koitharu.kotatsu.core.ui.image
import android.graphics.Bitmap
import android.graphics.BitmapFactory
@@ -13,7 +13,11 @@ import coil.decode.Decoder
import coil.decode.ImageSource
import coil.fetch.SourceResult
import coil.request.Options
-import coil.size.*
+import coil.size.Dimension
+import coil.size.Scale
+import coil.size.Size
+import coil.size.isOriginal
+import coil.size.pxOrElse
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/TrimTransformation.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TrimTransformation.kt
similarity index 91%
rename from app/src/main/java/org/koitharu/kotatsu/utils/image/TrimTransformation.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TrimTransformation.kt
index b44281f38..bc22724ed 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/image/TrimTransformation.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TrimTransformation.kt
@@ -1,8 +1,12 @@
-package org.koitharu.kotatsu.utils.image
+package org.koitharu.kotatsu.core.ui.image
import android.graphics.Bitmap
import androidx.annotation.ColorInt
-import androidx.core.graphics.*
+import androidx.core.graphics.alpha
+import androidx.core.graphics.blue
+import androidx.core.graphics.get
+import androidx.core.graphics.green
+import androidx.core.graphics.red
import coil.size.Size
import coil.transform.Transformation
import kotlin.math.abs
@@ -104,4 +108,4 @@ class TrimTransformation(
abs(a.blue - b.blue) <= tolerance &&
abs(a.alpha - b.alpha) <= tolerance
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/AdapterDelegateClickListenerAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/AdapterDelegateClickListenerAdapter.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/AdapterDelegateClickListenerAdapter.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/AdapterDelegateClickListenerAdapter.kt
index 19d1d5661..a9e6e13ea 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/AdapterDelegateClickListenerAdapter.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/AdapterDelegateClickListenerAdapter.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import android.view.View
import android.view.View.OnClickListener
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BoundsScrollListener.kt
similarity index 90%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BoundsScrollListener.kt
index f5019c152..f9d41fec8 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BoundsScrollListener.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -8,19 +8,22 @@ abstract class BoundsScrollListener(private val offsetTop: Int, private val offs
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
+ if (recyclerView.hasPendingAdapterUpdates()) {
+ return
+ }
val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
if (firstVisibleItemPosition == RecyclerView.NO_POSITION) {
return
}
- if (firstVisibleItemPosition <= offsetTop) {
- onScrolledToStart(recyclerView)
- }
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - offsetBottom) {
onScrolledToEnd(recyclerView)
}
+ if (firstVisibleItemPosition <= offsetTop) {
+ onScrolledToStart(recyclerView)
+ }
}
abstract fun onScrolledToStart(recyclerView: RecyclerView)
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/FitHeightGridLayoutManager.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/FitHeightGridLayoutManager.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/FitHeightGridLayoutManager.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/FitHeightGridLayoutManager.kt
index fc6564beb..ddb94ce34 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/FitHeightGridLayoutManager.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/FitHeightGridLayoutManager.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import android.content.Context
import android.util.AttributeSet
@@ -34,4 +34,4 @@ class FitHeightGridLayoutManager : GridLayoutManager {
super.layoutDecoratedWithMargins(child, left, top, right, bottom)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/FitHeightLinearLayoutManager.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/FitHeightLinearLayoutManager.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/FitHeightLinearLayoutManager.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/FitHeightLinearLayoutManager.kt
index 64e73198a..f4a36a227 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/FitHeightLinearLayoutManager.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/FitHeightLinearLayoutManager.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import android.content.Context
import android.util.AttributeSet
@@ -34,4 +34,4 @@ class FitHeightLinearLayoutManager : LinearLayoutManager {
super.layoutDecoratedWithMargins(child, left, top, right, bottom)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/ListSelectionController.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ListSelectionController.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/ListSelectionController.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ListSelectionController.kt
index 5cadc9c6f..e552e1098 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/ListSelectionController.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ListSelectionController.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import android.app.Activity
import android.os.Bundle
@@ -12,9 +12,9 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
-import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.Dispatchers
-import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
+import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
+import kotlin.coroutines.EmptyCoroutineContext
private const val KEY_SELECTION = "selection"
private const val PROVIDER_NAME = "selection_decoration"
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/NestedScrollStateHandle.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/NestedScrollStateHandle.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/NestedScrollStateHandle.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/NestedScrollStateHandle.kt
index 80d5310d3..b4946ccb0 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/NestedScrollStateHandle.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/NestedScrollStateHandle.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import android.os.Bundle
import android.os.Parcelable
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/OnListItemClickListener.kt
similarity index 57%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/OnListItemClickListener.kt
index f39b81d14..e394740b9 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/OnListItemClickListener.kt
@@ -1,10 +1,10 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import android.view.View
-interface OnListItemClickListener {
+fun interface OnListItemClickListener {
fun onItemClick(item: I, view: View)
fun onItemLongClick(item: I, view: View) = false
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnTipCloseListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/OnTipCloseListener.kt
similarity index 59%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnTipCloseListener.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/OnTipCloseListener.kt
index 9c9721eef..81078afee 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnTipCloseListener.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/OnTipCloseListener.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
interface OnTipCloseListener {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/PaginationScrollListener.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/PaginationScrollListener.kt
index 5681cae23..4f70dcd4d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/PaginationScrollListener.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import androidx.recyclerview.widget.RecyclerView
@@ -15,4 +15,4 @@ class PaginationScrollListener(offset: Int, private val callback: Callback) :
fun onScrolledToEnd()
}
-}
\ No newline at end of file
+}
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
new file mode 100644
index 000000000..5acc5862c
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ScrollListenerInvalidationObserver.kt
@@ -0,0 +1,30 @@
+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/java/org/koitharu/kotatsu/base/ui/list/SectionedSelectionController.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/SectionedSelectionController.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/SectionedSelectionController.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/SectionedSelectionController.kt
index d210c6991..066b4fa59 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/SectionedSelectionController.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/SectionedSelectionController.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list
+package org.koitharu.kotatsu.core.ui.list
import android.app.Activity
import android.os.Bundle
@@ -14,7 +14,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import kotlinx.coroutines.Dispatchers
-import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
+import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
import kotlin.coroutines.EmptyCoroutineContext
private const val PROVIDER_NAME = "selection_decoration_sectioned"
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/AbstractDividerItemDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractDividerItemDecoration.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/AbstractDividerItemDecoration.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractDividerItemDecoration.kt
index 2d91e71c7..ca4bbec76 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/AbstractDividerItemDecoration.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractDividerItemDecoration.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list.decor
+package org.koitharu.kotatsu.core.ui.list.decor
import android.annotation.SuppressLint
import android.content.Context
@@ -59,7 +59,7 @@ abstract class AbstractDividerItemDecoration(context: Context) : RecyclerView.It
left,
parent.paddingTop.toFloat(),
right,
- (parent.height - parent.paddingBottom).toFloat()
+ (parent.height - parent.paddingBottom).toFloat(),
)
} else {
left = 0f
@@ -84,4 +84,4 @@ abstract class AbstractDividerItemDecoration(context: Context) : RecyclerView.It
above: RecyclerView.ViewHolder,
below: RecyclerView.ViewHolder,
): Boolean
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/AbstractSelectionItemDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/AbstractSelectionItemDecoration.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt
index 1974f6a5d..20e3aef78 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/AbstractSelectionItemDecoration.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list.decor
+package org.koitharu.kotatsu.core.ui.list.decor
import android.graphics.Canvas
import android.graphics.Rect
@@ -67,7 +67,7 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() {
if (parent.clipToPadding) {
canvas.clipRect(
parent.paddingLeft, parent.paddingTop, parent.width - parent.paddingRight,
- parent.height - parent.paddingBottom
+ parent.height - parent.paddingBottom,
)
}
@@ -108,4 +108,4 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() {
bounds: RectF,
state: RecyclerView.State,
) = Unit
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/SpacingItemDecoration.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/SpacingItemDecoration.kt
index 5b9fbde29..88f3593ac 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/SpacingItemDecoration.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list.decor
+package org.koitharu.kotatsu.core.ui.list.decor
import android.graphics.Rect
import android.view.View
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/TypedSpacingItemDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/TypedSpacingItemDecoration.kt
similarity index 94%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/TypedSpacingItemDecoration.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/TypedSpacingItemDecoration.kt
index 5662f026a..244936dbf 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/TypedSpacingItemDecoration.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/TypedSpacingItemDecoration.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list.decor
+package org.koitharu.kotatsu.core.ui.list.decor
import android.graphics.Rect
import android.util.SparseIntArray
@@ -13,7 +13,7 @@ class TypedSpacingItemDecoration(
) : RecyclerView.ItemDecoration() {
private val mapping = SparseIntArray(spacingMapping.size)
-
+
init {
spacingMapping.forEach { (k, v) -> mapping[k] = v }
}
@@ -32,4 +32,4 @@ class TypedSpacingItemDecoration(
}
outRect.set(spacing, spacing, spacing, spacing)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/BubbleAnimator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/BubbleAnimator.kt
similarity index 92%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/BubbleAnimator.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/BubbleAnimator.kt
index 36b5e0e5f..359edfc05 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/BubbleAnimator.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/BubbleAnimator.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list.fastscroll
+package org.koitharu.kotatsu.core.ui.list.fastscroll
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -8,9 +8,9 @@ import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
+import org.koitharu.kotatsu.core.util.ext.animatorDurationScale
+import org.koitharu.kotatsu.core.util.ext.measureWidth
import kotlin.math.hypot
-import org.koitharu.kotatsu.utils.ext.animatorDurationScale
-import org.koitharu.kotatsu.utils.ext.measureWidth
class BubbleAnimator(
private val bubble: View,
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/FastScrollRecyclerView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/FastScrollRecyclerView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt
index 5a7c1274e..2b62a6d49 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/FastScrollRecyclerView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list.fastscroll
+package org.koitharu.kotatsu.core.ui.list.fastscroll
import android.content.Context
import android.util.AttributeSet
@@ -42,4 +42,4 @@ class FastScrollRecyclerView @JvmOverloads constructor(
fastScroller.detachRecyclerView()
super.onDetachedFromWindow()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/FastScroller.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScroller.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/FastScroller.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScroller.kt
index e5cb94dd4..d7eca512d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/FastScroller.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScroller.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list.fastscroll
+package org.koitharu.kotatsu.core.ui.list.fastscroll
import android.annotation.SuppressLint
import android.content.Context
@@ -22,9 +22,9 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.util.ext.getThemeColor
+import org.koitharu.kotatsu.core.util.ext.isLayoutReversed
import org.koitharu.kotatsu.databinding.FastScrollerBinding
-import org.koitharu.kotatsu.utils.ext.getThemeColor
-import org.koitharu.kotatsu.utils.ext.isLayoutReversed
import kotlin.math.roundToInt
import com.google.android.material.R as materialR
@@ -98,6 +98,7 @@ class FastScroller @JvmOverloads constructor(
showScrollbar()
if (showBubbleAlways && sectionIndexer != null) showBubble()
}
+
RecyclerView.SCROLL_STATE_IDLE -> if (hideScrollbar && !binding.thumb.isSelected) {
handler.postDelayed(scrollbarHider, SCROLLBAR_HIDE_DELAY)
}
@@ -176,10 +177,12 @@ class FastScroller @JvmOverloads constructor(
setYPositions()
return true
}
+
MotionEvent.ACTION_MOVE -> {
setYPositions()
return true
}
+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
requestDisallowInterceptTouchEvent(false)
setHandleSelected(false)
@@ -248,17 +251,20 @@ class FastScroller @JvmOverloads constructor(
setMargins(0, marginTop, 0, marginBottom)
}
}
+
is CoordinatorLayout -> layoutParams = (layoutParams as CoordinatorLayout.LayoutParams).apply {
height = LayoutParams.MATCH_PARENT
anchorGravity = GravityCompat.END
anchorId = recyclerViewId
setMargins(0, marginTop, 0, marginBottom)
}
+
is FrameLayout -> layoutParams = (layoutParams as FrameLayout.LayoutParams).apply {
height = LayoutParams.MATCH_PARENT
gravity = GravityCompat.END
setMargins(0, marginTop, 0, marginBottom)
}
+
is RelativeLayout -> layoutParams = (layoutParams as RelativeLayout.LayoutParams).apply {
height = 0
addRule(RelativeLayout.ALIGN_TOP, recyclerViewId)
@@ -266,6 +272,7 @@ class FastScroller @JvmOverloads constructor(
addRule(RelativeLayout.ALIGN_END, recyclerViewId)
setMargins(0, marginTop, 0, marginBottom)
}
+
else -> throw IllegalArgumentException("Parent ViewGroup must be a ConstraintLayout, CoordinatorLayout, FrameLayout, or RelativeLayout")
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/ScrollbarAnimator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/ScrollbarAnimator.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/ScrollbarAnimator.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/ScrollbarAnimator.kt
index 75298a802..1d9287b2d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/fastscroll/ScrollbarAnimator.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/ScrollbarAnimator.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.list.fastscroll
+package org.koitharu.kotatsu.core.ui.list.fastscroll
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -7,7 +7,7 @@ import android.view.ViewPropertyAnimator
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.utils.ext.animatorDurationScale
+import org.koitharu.kotatsu.core.util.ext.animatorDurationScale
class ScrollbarAnimator(
private val scrollbar: View,
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/DateTimeAgo.kt
similarity index 94%
rename from app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/DateTimeAgo.kt
index a11f56e01..8e468b5ad 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/DateTimeAgo.kt
@@ -1,10 +1,10 @@
-package org.koitharu.kotatsu.core.ui
+package org.koitharu.kotatsu.core.ui.model
import android.content.res.Resources
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.util.ext.daysDiff
+import org.koitharu.kotatsu.core.util.ext.format
import org.koitharu.kotatsu.list.ui.model.ListModel
-import org.koitharu.kotatsu.utils.ext.daysDiff
-import org.koitharu.kotatsu.utils.ext.format
import java.util.Date
sealed class DateTimeAgo : ListModel {
@@ -107,9 +107,7 @@ sealed class DateTimeAgo : ListModel {
other as Absolute
- if (day != other.day) return false
-
- return true
+ return day == other.day
}
override fun hashCode(): Int {
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/SortOrder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/SortOrder.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/core/ui/SortOrder.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/SortOrder.kt
index 92b9fd9ef..71e6034e6 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/ui/SortOrder.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/SortOrder.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.core.ui
+package org.koitharu.kotatsu.core.ui.model
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
@@ -12,4 +12,4 @@ val SortOrder.titleRes: Int
SortOrder.RATING -> R.string.by_rating
SortOrder.NEWEST -> R.string.newest
SortOrder.ALPHABETICAL -> R.string.by_name
- }
\ No newline at end of file
+ }
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActionModeDelegate.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActionModeDelegate.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActionModeDelegate.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActionModeDelegate.kt
index feed3fc6a..585f39e69 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActionModeDelegate.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActionModeDelegate.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.view.ActionMode
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActionModeListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActionModeListener.kt
similarity index 78%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActionModeListener.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActionModeListener.kt
index 0c87ff612..fde599ede 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActionModeListener.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActionModeListener.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import androidx.appcompat.view.ActionMode
@@ -7,4 +7,4 @@ interface ActionModeListener {
fun onActionModeStarted(mode: ActionMode)
fun onActionModeFinished(mode: ActionMode)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActivityRecreationHandle.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActivityRecreationHandle.kt
similarity index 86%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActivityRecreationHandle.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActivityRecreationHandle.kt
index 036515bbe..46c1d0f9e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ActivityRecreationHandle.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActivityRecreationHandle.kt
@@ -1,9 +1,9 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import android.app.Activity
import android.os.Bundle
import androidx.core.app.ActivityCompat
-import org.koitharu.kotatsu.base.ui.DefaultActivityLifecycleCallbacks
+import org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks
import java.util.WeakHashMap
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/BaseActivityEntryPoint.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/BaseActivityEntryPoint.kt
similarity index 86%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/BaseActivityEntryPoint.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/BaseActivityEntryPoint.kt
index 66b1a588c..309883319 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/BaseActivityEntryPoint.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/BaseActivityEntryPoint.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/CollapseActionViewCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/CollapseActionViewCallback.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/CollapseActionViewCallback.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/CollapseActionViewCallback.kt
index 5d9058de1..b417e40e3 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/CollapseActionViewCallback.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/CollapseActionViewCallback.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import android.view.MenuItem
import android.view.MenuItem.OnActionExpandListener
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/DefaultTextWatcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/DefaultTextWatcher.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/DefaultTextWatcher.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/DefaultTextWatcher.kt
index a382f488c..999dd6641 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/DefaultTextWatcher.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/DefaultTextWatcher.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import android.text.Editable
import android.text.TextWatcher
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/RecyclerViewOwner.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/RecyclerViewOwner.kt
similarity index 72%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/RecyclerViewOwner.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/RecyclerViewOwner.kt
index 9b0976d51..f34963f15 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/RecyclerViewOwner.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/RecyclerViewOwner.kt
@@ -1,8 +1,8 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import androidx.recyclerview.widget.RecyclerView
interface RecyclerViewOwner {
val recyclerView: RecyclerView
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleAction.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleAction.kt
similarity index 56%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleAction.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleAction.kt
index 57bb80a78..f9fea6652 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleAction.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleAction.kt
@@ -1,9 +1,8 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import androidx.annotation.StringRes
-import org.koitharu.kotatsu.base.domain.ReversibleHandle
class ReversibleAction(
@StringRes val stringResId: Int,
val handle: ReversibleHandle?,
-)
\ No newline at end of file
+)
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleActionObserver.kt
similarity index 50%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleActionObserver.kt
index 04a332cd2..b66e64cbb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleActionObserver.kt
@@ -1,30 +1,21 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import android.view.View
-import androidx.lifecycle.Observer
import com.google.android.material.snackbar.Snackbar
+import kotlinx.coroutines.flow.FlowCollector
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.domain.reverseAsync
-import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
-import org.koitharu.kotatsu.utils.ext.findActivity
class ReversibleActionObserver(
private val snackbarHost: View,
-) : Observer {
+) : FlowCollector {
- override fun onChanged(value: ReversibleAction?) {
- if (value == null) {
- return
- }
+ override suspend fun emit(value: ReversibleAction) {
val handle = value.handle
val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG
val snackbar = Snackbar.make(snackbarHost, value.stringResId, length)
if (handle != null) {
snackbar.setAction(R.string.undo) { handle.reverseAsync() }
}
- (snackbarHost.context.findActivity() as? BottomNavOwner)?.let {
- snackbar.anchorView = it.bottomNav
- }
snackbar.show()
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/ReversibleHandle.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleHandle.kt
similarity index 70%
rename from app/src/main/java/org/koitharu/kotatsu/base/domain/ReversibleHandle.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleHandle.kt
index f34c99e69..d3d6bc475 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/domain/ReversibleHandle.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleHandle.kt
@@ -1,12 +1,12 @@
-package org.koitharu.kotatsu.base.domain
+package org.koitharu.kotatsu.core.ui.util
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
-import org.koitharu.kotatsu.utils.ext.processLifecycleScope
-import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
+import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
+import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
fun interface ReversibleHandle {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ShrinkOnScrollBehavior.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ShrinkOnScrollBehavior.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/ShrinkOnScrollBehavior.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ShrinkOnScrollBehavior.kt
index 124c1dea1..8d648ec3a 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ShrinkOnScrollBehavior.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ShrinkOnScrollBehavior.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import android.content.Context
import android.util.AttributeSet
@@ -10,9 +10,7 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
open class ShrinkOnScrollBehavior : Behavior {
- @Suppress("unused")
constructor() : super()
- @Suppress("unused")
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
override fun onStartNestedScroll(
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/SpanSizeResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/SpanSizeResolver.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/SpanSizeResolver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/SpanSizeResolver.kt
index 71e5dc398..9c0e07f91 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/SpanSizeResolver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/SpanSizeResolver.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import android.view.View
import androidx.annotation.Px
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/StatusBarDimHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/StatusBarDimHelper.kt
similarity index 92%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/StatusBarDimHelper.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/StatusBarDimHelper.kt
index 7a8bf28d4..b58f36ae1 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/StatusBarDimHelper.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/StatusBarDimHelper.kt
@@ -1,11 +1,11 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import android.animation.ValueAnimator
import android.view.animation.AccelerateDecelerateInterpolator
-import com.google.android.material.R as materialR
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.shape.MaterialShapeDrawable
-import org.koitharu.kotatsu.utils.ext.getAnimationDuration
+import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
+import com.google.android.material.R as materialR
class StatusBarDimHelper : AppBarLayout.OnOffsetChangedListener {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/WindowInsetsDelegate.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/WindowInsetsDelegate.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/util/WindowInsetsDelegate.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/WindowInsetsDelegate.kt
index ff80acbbb..a85868857 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/WindowInsetsDelegate.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/WindowInsetsDelegate.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.util
+package org.koitharu.kotatsu.core.ui.util
import android.view.View
import androidx.core.graphics.Insets
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/BottomSheetHeaderBar.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/BottomSheetHeaderBar.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/BottomSheetHeaderBar.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/BottomSheetHeaderBar.kt
index 80b5749bf..354206ad4 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/BottomSheetHeaderBar.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/BottomSheetHeaderBar.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.animation.LayoutTransition
import android.content.Context
@@ -21,10 +21,10 @@ import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
+import org.koitharu.kotatsu.core.util.ext.getThemeDrawable
+import org.koitharu.kotatsu.core.util.ext.parents
import org.koitharu.kotatsu.databinding.LayoutSheetHeaderBinding
-import org.koitharu.kotatsu.utils.ext.getAnimationDuration
-import org.koitharu.kotatsu.utils.ext.getThemeDrawable
-import org.koitharu.kotatsu.utils.ext.parents
import java.util.*
import com.google.android.material.R as materialR
@@ -70,6 +70,9 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
binding.toolbar.subtitle = value
}
+ val isExpanded: Boolean
+ get() = binding.dragHandle.isGone
+
init {
setBackgroundResource(R.drawable.sheet_toolbar_background)
layoutTransition = LayoutTransition().apply {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CheckableImageView.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CheckableImageView.kt
index 2d18292cc..b872917c0 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CheckableImageView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.os.Parcel
@@ -101,4 +101,4 @@ class CheckableImageView @JvmOverloads constructor(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt
index 88398cbd0..3ad0838ad 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
@@ -13,7 +13,7 @@ import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.google.android.material.chip.ChipGroup
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.utils.ext.castOrNull
+import org.koitharu.kotatsu.core.util.ext.castOrNull
import com.google.android.material.R as materialR
class ChipsView @JvmOverloads constructor(
@@ -149,9 +149,7 @@ class ChipsView @JvmOverloads constructor(
if (title != other.title) return false
if (isCheckable != other.isCheckable) return false
if (isChecked != other.isChecked) return false
- if (data != other.data) return false
-
- return true
+ return data == other.data
}
override fun hashCode(): Int {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CoverImageView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CoverImageView.kt
similarity index 96%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CoverImageView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CoverImageView.kt
index 3a52eb237..9bcd4ca60 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CoverImageView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CoverImageView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.util.AttributeSet
@@ -40,4 +40,4 @@ class CoverImageView @JvmOverloads constructor(
}
setMeasuredDimension(desiredWidth, desiredHeight)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/HideBottomNavigationOnScrollBehavior.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/HideBottomNavigationOnScrollBehavior.kt
similarity index 94%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/HideBottomNavigationOnScrollBehavior.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/HideBottomNavigationOnScrollBehavior.kt
index b1742420a..629ffcd12 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/HideBottomNavigationOnScrollBehavior.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/HideBottomNavigationOnScrollBehavior.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.animation.ValueAnimator
import android.content.Context
@@ -10,8 +10,8 @@ import androidx.core.view.ViewCompat
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomnavigation.BottomNavigationView
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.utils.ext.getAnimationDuration
-import org.koitharu.kotatsu.utils.ext.measureHeight
+import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
+import org.koitharu.kotatsu.core.util.ext.measureHeight
class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
context: Context? = null,
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ListItemTextView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ListItemTextView.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ListItemTextView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ListItemTextView.kt
index e51509920..71d9314f7 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ListItemTextView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ListItemTextView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
@@ -19,7 +19,7 @@ import com.google.android.material.ripple.RippleUtils
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.utils.ext.resolveDp
+import org.koitharu.kotatsu.core.util.ext.resolveDp
@SuppressLint("RestrictedApi")
class ListItemTextView @JvmOverloads constructor(
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SegmentedBarView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SegmentedBarView.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SegmentedBarView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SegmentedBarView.kt
index 1125b7839..39591490a 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SegmentedBarView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SegmentedBarView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.animation.Animator
import android.animation.ValueAnimator
@@ -12,11 +12,11 @@ import android.view.ViewOutlineProvider
import android.view.animation.DecelerateInterpolator
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
+import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
+import org.koitharu.kotatsu.core.util.ext.getThemeColor
+import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
+import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.parsers.util.replaceWith
-import org.koitharu.kotatsu.utils.ext.getAnimationDuration
-import org.koitharu.kotatsu.utils.ext.getThemeColor
-import org.koitharu.kotatsu.utils.ext.isAnimationsEnabled
-import org.koitharu.kotatsu.utils.ext.resolveDp
import com.google.android.material.R as materialR
class SegmentedBarView @JvmOverloads constructor(
@@ -135,9 +135,7 @@ class SegmentedBarView @JvmOverloads constructor(
other as Segment
if (percent != other.percent) return false
- if (color != other.color) return false
-
- return true
+ return color == other.color
}
override fun hashCode(): Int {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SelectableTextView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SelectableTextView.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SelectableTextView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SelectableTextView.kt
index e931853f0..32cb29875 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SelectableTextView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SelectableTextView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.text.Selection
@@ -26,4 +26,4 @@ class SelectableTextView @JvmOverloads constructor(
Selection.setSelection(spannableText, text.length)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ShapeView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ShapeView.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ShapeView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ShapeView.kt
index 5ec934c1e..32b7f47dd 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ShapeView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ShapeView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SlidingBottomNavigationView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SlidingBottomNavigationView.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SlidingBottomNavigationView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SlidingBottomNavigationView.kt
index 3e9e7b55d..b34a958bf 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SlidingBottomNavigationView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SlidingBottomNavigationView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -14,10 +14,10 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.customview.view.AbsSavedState
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
-import com.google.android.material.R as materialR
import com.google.android.material.bottomnavigation.BottomNavigationView
-import org.koitharu.kotatsu.utils.ext.applySystemAnimatorScale
-import org.koitharu.kotatsu.utils.ext.measureHeight
+import org.koitharu.kotatsu.core.util.ext.applySystemAnimatorScale
+import org.koitharu.kotatsu.core.util.ext.measureHeight
+import com.google.android.material.R as materialR
private const val STATE_DOWN = 1
private const val STATE_UP = 2
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/TwoLinesItemView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/TwoLinesItemView.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/TwoLinesItemView.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/TwoLinesItemView.kt
index f7f8d44e1..37058bac2 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/TwoLinesItemView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/TwoLinesItemView.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
@@ -23,8 +23,8 @@ import com.google.android.material.ripple.RippleUtils
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.databinding.ViewTwoLinesItemBinding
-import org.koitharu.kotatsu.utils.ext.resolveDp
@SuppressLint("RestrictedApi")
class TwoLinesItemView @JvmOverloads constructor(
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/WindowInsetHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/WindowInsetHolder.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/WindowInsetHolder.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/WindowInsetHolder.kt
index 3279dfc06..57870cf19 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/WindowInsetHolder.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/WindowInsetHolder.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.base.ui.widgets
+package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.util.AttributeSet
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/AlphanumComparator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/AlphanumComparator.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/utils/AlphanumComparator.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/AlphanumComparator.kt
index cee0626c0..46867633e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/AlphanumComparator.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/AlphanumComparator.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
class AlphanumComparator : Comparator {
@@ -60,4 +60,4 @@ class AlphanumComparator : Comparator {
}
return chunk.toString()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/BufferedObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/BufferedObserver.kt
similarity index 64%
rename from app/src/main/java/org/koitharu/kotatsu/utils/BufferedObserver.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/BufferedObserver.kt
index bc806ec7a..ccebce668 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/BufferedObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/BufferedObserver.kt
@@ -1,6 +1,6 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
fun interface BufferedObserver {
fun onChanged(t: T, previous: T?)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CancellableSource.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CancellableSource.kt
new file mode 100644
index 000000000..d06daa6ea
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CancellableSource.kt
@@ -0,0 +1,18 @@
+package org.koitharu.kotatsu.core.util
+
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.ensureActive
+import okio.Buffer
+import okio.ForwardingSource
+import okio.Source
+
+class CancellableSource(
+ private val job: Job?,
+ delegate: Source,
+) : ForwardingSource(delegate) {
+
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ job?.ensureActive()
+ return super.read(sink, byteCount)
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex.kt
index 99f69e11a..9c9c9f0d3 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import androidx.collection.ArrayMap
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/EditTextValidator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/EditTextValidator.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/utils/EditTextValidator.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/EditTextValidator.kt
index 37ca77618..3554fc194 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/EditTextValidator.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/EditTextValidator.kt
@@ -1,11 +1,11 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.content.Context
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import androidx.annotation.CallSuper
-import org.koitharu.kotatsu.utils.ext.getDisplayMessage
+import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import java.lang.ref.WeakReference
abstract class EditTextValidator : TextWatcher {
@@ -51,4 +51,4 @@ abstract class EditTextValidator : TextWatcher {
class Failed(val message: CharSequence) : ValidationResult()
}
-}
\ No newline at end of file
+}
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
new file mode 100644
index 000000000..e14d2703c
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt
@@ -0,0 +1,36 @@
+package org.koitharu.kotatsu.core.util
+
+import kotlinx.coroutines.flow.FlowCollector
+
+class Event(
+ private val data: T,
+) {
+ private var isConsumed = false
+
+ suspend fun consume(collector: FlowCollector) {
+ if (isConsumed) {
+ collector.emit(data)
+ isConsumed = true
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Event<*>
+
+ if (data != other.data) return false
+ return isConsumed == other.isConsumed
+ }
+
+ override fun hashCode(): Int {
+ var result = data?.hashCode() ?: 0
+ result = 31 * result + isConsumed.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "Event(data=$data, isConsumed=$isConsumed)"
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/FileSize.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/FileSize.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/utils/FileSize.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/FileSize.kt
index cb558edfe..6325c3dec 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/FileSize.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/FileSize.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.content.Context
import org.koitharu.kotatsu.R
@@ -22,8 +22,8 @@ enum class FileSize(private val multiplier: Int) {
return buildString {
append(
DecimalFormat("#,##0.#").format(
- bytes / 1024.0.pow(digitGroups.toDouble())
- )
+ bytes / 1024.0.pow(digitGroups.toDouble()),
+ ),
)
val unit = units.getOrNull(digitGroups)
if (unit != null) {
@@ -32,4 +32,4 @@ enum class FileSize(private val multiplier: Int) {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/GoneOnInvisibleListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GoneOnInvisibleListener.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/utils/GoneOnInvisibleListener.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/GoneOnInvisibleListener.kt
index 46de769c6..25ab3717f 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/GoneOnInvisibleListener.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GoneOnInvisibleListener.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.view.View
import android.view.ViewTreeObserver
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/GridTouchHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GridTouchHelper.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/utils/GridTouchHelper.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/GridTouchHelper.kt
index 9605fb93b..6608c719e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/GridTouchHelper.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GridTouchHelper.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.content.Context
import android.view.GestureDetector
@@ -44,6 +44,7 @@ class GridTouchHelper(
else -> return false
}
}
+
2 -> AREA_RIGHT
else -> return false
},
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/IdlingDetector.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/IdlingDetector.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/utils/IdlingDetector.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/IdlingDetector.kt
index 0501a3da6..d9cbedfdc 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/IdlingDetector.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/IdlingDetector.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.os.Handler
import android.os.Looper
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/IncognitoModeIndicator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/IncognitoModeIndicator.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/utils/IncognitoModeIndicator.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/IncognitoModeIndicator.kt
index 8ca72f3eb..7dc2ee1ac 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/IncognitoModeIndicator.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/IncognitoModeIndicator.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.app.Activity
import android.os.Bundle
@@ -11,10 +11,10 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.base.ui.DefaultActivityLifecycleCallbacks
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
-import org.koitharu.kotatsu.utils.ext.getThemeColor
+import org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks
+import org.koitharu.kotatsu.core.util.ext.getThemeColor
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/MediatorStateFlow.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/MediatorStateFlow.kt
similarity index 95%
rename from app/src/main/java/org/koitharu/kotatsu/utils/MediatorStateFlow.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/MediatorStateFlow.kt
index 0f4fda663..7bee7ffc2 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/MediatorStateFlow.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/MediatorStateFlow.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/RecyclerViewScrollCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/RecyclerViewScrollCallback.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/utils/RecyclerViewScrollCallback.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/RecyclerViewScrollCallback.kt
index 075126db2..f8815ae6e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/RecyclerViewScrollCallback.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/RecyclerViewScrollCallback.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import androidx.annotation.Px
import androidx.recyclerview.widget.LinearLayoutManager
@@ -23,4 +23,4 @@ class RecyclerViewScrollCallback(
lm.scrollToPosition(position)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/RetainedLifecycleCoroutineScope.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/RetainedLifecycleCoroutineScope.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/utils/RetainedLifecycleCoroutineScope.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/RetainedLifecycleCoroutineScope.kt
index 66a232922..cf5645f1c 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/RetainedLifecycleCoroutineScope.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/RetainedLifecycleCoroutineScope.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import dagger.hilt.android.lifecycle.RetainedLifecycle
import kotlinx.coroutines.CoroutineScope
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ScreenOrientationHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ScreenOrientationHelper.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ScreenOrientationHelper.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ScreenOrientationHelper.kt
index 4cecbd2a8..8f062e247 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ScreenOrientationHelper.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ScreenOrientationHelper.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.app.Activity
import android.content.pm.ActivityInfo
@@ -18,7 +18,7 @@ class ScreenOrientationHelper(private val activity: Activity) {
get() = Settings.System.getInt(
activity.contentResolver,
Settings.System.ACCELEROMETER_ROTATION,
- 0
+ 0,
) == 1
var isLandscape: Boolean
@@ -42,7 +42,7 @@ class ScreenOrientationHelper(private val activity: Activity) {
}
}
activity.contentResolver.registerContentObserver(
- Settings.System.CONTENT_URI, true, observer
+ Settings.System.CONTENT_URI, true, observer,
)
awaitClose {
activity.contentResolver.unregisterContentObserver(observer)
@@ -50,4 +50,4 @@ class ScreenOrientationHelper(private val activity: Activity) {
}.onStart {
emit(isAutoRotationEnabled)
}.distinctUntilChanged()
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ShareHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ShareHelper.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt
index 8535fbed3..57d5e7c80 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ShareHelper.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt
@@ -1,7 +1,8 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.content.Context
import android.net.Uri
+import android.widget.Toast
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import org.koitharu.kotatsu.BuildConfig
@@ -84,6 +85,7 @@ class ShareHelper(private val context: Context) {
fun shareLogs(loggers: Collection) {
val intentBuilder = ShareCompat.IntentBuilder(context)
.setType(TYPE_TEXT)
+ var hasLogs = false
for (logger in loggers) {
val logFile = logger.file
if (!logFile.exists()) {
@@ -91,8 +93,13 @@ class ShareHelper(private val context: Context) {
}
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.files", logFile)
intentBuilder.addStream(uri)
+ hasLogs = true
+ }
+ if (hasLogs) {
+ intentBuilder.setChooserTitle(R.string.share_logs)
+ intentBuilder.startChooser()
+ } else {
+ Toast.makeText(context, R.string.nothing_here, Toast.LENGTH_SHORT).show()
}
- intentBuilder.setChooserTitle(R.string.share_logs)
- intentBuilder.startChooser()
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/TaggedActivityResult.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/TaggedActivityResult.kt
similarity index 63%
rename from app/src/main/java/org/koitharu/kotatsu/utils/TaggedActivityResult.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/TaggedActivityResult.kt
index ee84cffb2..8fba053eb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/TaggedActivityResult.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/TaggedActivityResult.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.app.Activity
@@ -8,4 +8,4 @@ class TaggedActivityResult(
)
val TaggedActivityResult.isSuccess: Boolean
- get() = this.result == Activity.RESULT_OK
\ No newline at end of file
+ get() = this.result == Activity.RESULT_OK
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Throttler.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Throttler.kt
new file mode 100644
index 000000000..5748c79bb
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Throttler.kt
@@ -0,0 +1,24 @@
+package org.koitharu.kotatsu.core.util
+
+import android.os.SystemClock
+
+class Throttler(
+ private val timeoutMs: Long,
+) {
+
+ private var lastTick = 0L
+
+ fun throttle(): Boolean {
+ val now = SystemClock.elapsedRealtime()
+ return if (lastTick + timeoutMs <= now) {
+ lastTick = now
+ true
+ } else {
+ false
+ }
+ }
+
+ fun reset() {
+ lastTick = 0L
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ViewBadge.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ViewBadge.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ViewBadge.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ViewBadge.kt
index 90f7a94d7..e8aa4263d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ViewBadge.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ViewBadge.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils
+package org.koitharu.kotatsu.core.util
import android.view.View
import androidx.annotation.OptIn
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/WorkManagerHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/WorkManagerHelper.kt
new file mode 100644
index 000000000..95e7aaa4e
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/WorkManagerHelper.kt
@@ -0,0 +1,67 @@
+package org.koitharu.kotatsu.core.util
+
+import android.annotation.SuppressLint
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkQuery
+import androidx.work.WorkRequest
+import androidx.work.await
+import androidx.work.impl.WorkManagerImpl
+import java.util.UUID
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+@SuppressLint("RestrictedApi")
+class WorkManagerHelper(
+ workManager: WorkManager,
+) {
+
+ private val workManagerImpl = workManager as WorkManagerImpl
+
+ suspend fun deleteWork(id: UUID) = suspendCoroutine { cont ->
+ workManagerImpl.workTaskExecutor.executeOnTaskThread {
+ try {
+ workManagerImpl.workDatabase.workSpecDao().delete(id.toString())
+ cont.resume(Unit)
+ } catch (e: Exception) {
+ cont.resumeWithException(e)
+ }
+ }
+ }
+
+ suspend fun deleteWorks(ids: Collection) = suspendCoroutine { cont ->
+ workManagerImpl.workTaskExecutor.executeOnTaskThread {
+ try {
+ val db = workManagerImpl.workDatabase
+ db.runInTransaction {
+ for (id in ids) {
+ db.workSpecDao().delete(id.toString())
+ }
+ }
+ cont.resume(Unit)
+ } catch (e: Exception) {
+ cont.resumeWithException(e)
+ }
+ }
+ }
+
+ suspend fun getWorkInfosByTag(tag: String): List {
+ return workManagerImpl.getWorkInfosByTag(tag).await()
+ }
+
+ suspend fun getFinishedWorkInfosByTag(tag: String): List {
+ val query = WorkQuery.Builder.fromTags(listOf(tag))
+ .addStates(listOf(WorkInfo.State.SUCCEEDED, WorkInfo.State.CANCELLED, WorkInfo.State.FAILED))
+ .build()
+ return workManagerImpl.getWorkInfos(query).await()
+ }
+
+ suspend fun getWorkInfoById(id: UUID): WorkInfo? {
+ return workManagerImpl.getWorkInfoById(id).await()
+ }
+
+ suspend fun updateWork(request: WorkRequest): WorkManager.UpdateResult {
+ return workManagerImpl.updateWork(request).await()
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/WorkServiceStopHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/WorkServiceStopHelper.kt
new file mode 100644
index 000000000..533c407a2
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/WorkServiceStopHelper.kt
@@ -0,0 +1,48 @@
+package org.koitharu.kotatsu.core.util
+
+import android.annotation.SuppressLint
+import android.content.Context
+import androidx.lifecycle.asFlow
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkQuery
+import androidx.work.impl.foreground.SystemForegroundService
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
+
+/**
+ * Workaround for issue
+ * https://issuetracker.google.com/issues/270245927
+ * https://issuetracker.google.com/issues/280504155
+ */
+class WorkServiceStopHelper(
+ private val context: Context,
+) {
+
+ fun setup() {
+ processLifecycleScope.launch(Dispatchers.Default) {
+ WorkManager.getInstance(context)
+ .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING))
+ .asFlow()
+ .map { it.isEmpty() }
+ .distinctUntilChanged()
+ .collectLatest {
+ if (it) {
+ delay(1_000)
+ stopWorkerService()
+ }
+ }
+ }
+ }
+
+ @SuppressLint("RestrictedApi")
+ private fun stopWorkerService() {
+ SystemForegroundService.getInstance()?.stop()
+ }
+}
+
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt
index 966338125..02c0f3ba0 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.app.Activity
import android.app.ActivityManager
@@ -19,6 +19,7 @@ import android.provider.Settings
import android.view.View
import android.view.ViewPropertyAnimator
import android.view.Window
+import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IntegerRes
import androidx.core.app.ActivityOptionsCompat
@@ -40,6 +41,8 @@ import org.json.JSONException
import org.jsoup.internal.StringUtil.StringJoiner
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
+import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import kotlin.math.roundToLong
@@ -133,8 +136,8 @@ fun Context.getAnimationDuration(@IntegerRes resId: Int): Long {
return (resources.getInteger(resId) * animatorDurationScale).roundToLong()
}
-fun isLowRamDevice(context: Context): Boolean {
- return context.activityManager?.isLowRamDevice ?: false
+fun Context.isLowRamDevice(): Boolean {
+ return activityManager?.isLowRamDevice ?: false
}
fun scaleUpActivityOptionsOf(view: View): ActivityOptions = ActivityOptions.makeScaleUpAnimation(
@@ -170,3 +173,18 @@ fun Context.findActivity(): Activity? = when (this) {
is ContextWrapper -> baseContext.findActivity()
else -> null
}
+
+inline fun Activity.catchingWebViewUnavailability(block: () -> Unit): Boolean {
+ return try {
+ block()
+ true
+ } catch (e: Exception) {
+ if (e.isWebViewUnavailable()) {
+ Toast.makeText(this, R.string.web_view_unavailable, Toast.LENGTH_LONG).show()
+ finishAfterTransition()
+ false
+ } else {
+ throw e
+ }
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/Bundle.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
similarity index 97%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/Bundle.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
index 1dcead8e2..d17233c25 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/Bundle.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
@@ -1,6 +1,6 @@
@file:Suppress("DEPRECATION")
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.content.Intent
import android.os.Build
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoilExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt
similarity index 82%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/CoilExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt
index f8a91c25e..d870f8d3b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoilExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.content.Context
import android.widget.ImageView
@@ -12,9 +12,9 @@ import coil.request.SuccessResult
import coil.util.CoilUtils
import com.google.android.material.progressindicator.BaseProgressIndicator
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.ui.image.RegionBitmapDecoder
+import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener
import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.utils.image.RegionBitmapDecoder
-import org.koitharu.kotatsu.utils.progress.ImageRequestIndicatorListener
fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): ImageRequest.Builder? {
val current = CoilUtils.result(this)
@@ -23,6 +23,7 @@ fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): Image
return null
}
}
+ disposeImageRequest()
return ImageRequest.Builder(context)
.data(data)
.lifecycle(lifecycleOwner)
@@ -37,11 +38,20 @@ fun ImageView.disposeImageRequest() {
fun ImageRequest.Builder.enqueueWith(loader: ImageLoader) = loader.enqueue(build())
-fun ImageResult.requireBitmap() = when (this) {
- is SuccessResult -> drawable.toBitmap()
+fun ImageResult.getDrawableOrThrow() = when (this) {
+ is SuccessResult -> drawable
is ErrorResult -> throw throwable
}
+@Deprecated(
+ "",
+ ReplaceWith(
+ "getDrawableOrThrow().toBitmap()",
+ "androidx.core.graphics.drawable.toBitmap",
+ ),
+)
+fun ImageResult.requireBitmap() = getDrawableOrThrow().toBitmap()
+
fun ImageResult.toBitmapOrNull() = when (this) {
is SuccessResult -> try {
drawable.toBitmap()
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt
similarity index 72%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt
index 6f6513707..9cb967878 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt
@@ -1,8 +1,10 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
+import androidx.collection.ArrayMap
import androidx.collection.ArraySet
import java.util.Collections
+@Deprecated("TODO: remove")
fun MutableList.move(sourceIndex: Int, targetIndex: Int) {
if (sourceIndex <= targetIndex) {
Collections.rotate(subList(sourceIndex, targetIndex + 1), -1)
@@ -45,3 +47,17 @@ inline fun Collection.filterToSet(predicate: (T) -> Boolean): Set {
fun Sequence.toListSorted(comparator: Comparator): List {
return toMutableList().apply { sortWith(comparator) }
}
+
+fun List.takeMostFrequent(limit: Int): List {
+ val map = ArrayMap(size)
+ for (item in this) {
+ map[item] = map.getOrDefault(item, 0) + 1
+ }
+ val entries = map.entries.sortedByDescending { it.value }
+ val count = minOf(limit, entries.size)
+ return buildList(count) {
+ repeat(count) { i ->
+ add(entries[i].key)
+ }
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coroutines.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coroutines.kt
index 3120f2f68..632030e09 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coroutines.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CursorExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Cursor.kt
similarity index 92%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/CursorExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Cursor.kt
index eeab153b0..3cec3da3b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CursorExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Cursor.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.content.ContentValues
import android.database.Cursor
@@ -36,4 +36,4 @@ fun JSONObject.toContentValues(): ContentValues {
return cv
}
-private fun String.escapeName() = "`$this`"
\ No newline at end of file
+private fun String.escapeName() = "`$this`"
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/DateExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Date.kt
similarity index 89%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/DateExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Date.kt
index 0a78f0341..e75842410 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/DateExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Date.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.annotation.SuppressLint
import android.text.format.DateUtils
@@ -10,7 +10,7 @@ import java.util.concurrent.TimeUnit
fun Date.format(pattern: String): String = SimpleDateFormat(pattern).format(this)
fun Date.formatRelative(minResolution: Long): CharSequence = DateUtils.getRelativeTimeSpanString(
- time, System.currentTimeMillis(), minResolution
+ time, System.currentTimeMillis(), minResolution,
)
fun Date.daysDiff(other: Long): Int {
@@ -27,4 +27,4 @@ fun Date.startOfDay(): Long {
calendar[Calendar.SECOND] = 0
calendar[Calendar.MILLISECOND] = 0
return calendar.timeInMillis
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/DisplayExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Display.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/DisplayExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Display.kt
index 6f917ac1e..b8ca902d4 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/DisplayExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Display.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.app.Activity
import android.graphics.Rect
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/EventFlow.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/EventFlow.kt
new file mode 100644
index 000000000..11fc25beb
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/EventFlow.kt
@@ -0,0 +1,18 @@
+package org.koitharu.kotatsu.core.util.ext
+
+import androidx.annotation.AnyThread
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.koitharu.kotatsu.core.util.Event
+
+@Suppress("FunctionName")
+fun MutableEventFlow() = MutableStateFlow?>(null)
+
+typealias EventFlow = StateFlow?>
+
+typealias MutableEventFlow = MutableStateFlow?>
+
+@AnyThread
+fun MutableEventFlow.call(data: T) {
+ value = Event(data)
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FileExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/File.kt
similarity index 98%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/FileExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/File.kt
index f2800f68c..ac98e2b48 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FileExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/File.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.content.ContentResolver
import android.content.Context
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Flow.kt
similarity index 72%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Flow.kt
index 4c08eec67..2aa0c1e62 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Flow.kt
@@ -1,13 +1,15 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.os.SystemClock
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transformLatest
+import org.koitharu.kotatsu.R
fun Flow.onFirst(action: suspend (T) -> Unit): Flow {
var isFirstCall = true
@@ -43,3 +45,20 @@ fun Flow.throttle(timeoutMillis: (T) -> Long): Flow {
fun StateFlow.requireValue(): T = checkNotNull(value) {
"StateFlow value is null"
}
+
+fun Flow>.flatten(): Flow = flow {
+ collect { value ->
+ for (item in value) {
+ emit(item)
+ }
+ }
+}
+
+fun Flow.zipWithPrevious(): Flow> = flow {
+ var previous: T? = null
+ collect { value ->
+ val result = previous to value
+ previous = value
+ emit(result)
+ }
+}
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
new file mode 100644
index 000000000..bfd2db25f
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt
@@ -0,0 +1,35 @@
+package org.koitharu.kotatsu.core.util.ext
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import kotlinx.coroutines.CoroutineStart
+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) {
+ collect(collector)
+ }
+ }
+}
+
+fun Flow?>.observeEvent(owner: LifecycleOwner, collector: FlowCollector) {
+ owner.lifecycleScope.launch {
+ owner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ collect {
+ it?.consume(collector)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Fragment.kt
similarity index 68%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Fragment.kt
index dec45bde0..d755911aa 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Fragment.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.os.Bundle
import androidx.annotation.MainThread
@@ -34,18 +34,21 @@ fun Fragment.addMenuProvider(provider: MenuProvider) {
}
@MainThread
-suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner = suspendCancellableCoroutine { cont ->
+suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner {
val liveData = viewLifecycleOwnerLiveData
- val observer = object : Observer {
- override fun onChanged(value: LifecycleOwner?) {
- if (value != null) {
- liveData.removeObserver(this)
- cont.resume(value)
+ liveData.value?.let { return it }
+ return suspendCancellableCoroutine { cont ->
+ val observer = object : Observer {
+ override fun onChanged(value: LifecycleOwner?) {
+ if (value != null) {
+ liveData.removeObserver(this)
+ cont.resume(value)
+ }
}
}
- }
- liveData.observeForever(observer)
- cont.invokeOnCancellation {
- liveData.removeObserver(observer)
+ liveData.observeForever(observer)
+ cont.invokeOnCancellation {
+ liveData.removeObserver(observer)
+ }
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/GraphicsExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Graphics.kt
similarity index 85%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/GraphicsExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Graphics.kt
index 94dc692a3..2e59b582f 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/GraphicsExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Graphics.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import android.graphics.Rect
import kotlin.math.roundToInt
@@ -10,4 +10,4 @@ fun Rect.scale(factor: Double) {
(width() - newWidth) / 2,
(height() - newHeight) / 2,
)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/HttpExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Http.kt
similarity index 93%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/HttpExt.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Http.kt
index a38596cb0..f5a23453e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/HttpExt.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Http.kt
@@ -1,4 +1,4 @@
-package org.koitharu.kotatsu.utils.ext
+package org.koitharu.kotatsu.core.util.ext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/IO.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/IO.kt
new file mode 100644
index 000000000..d41e0ba38
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/IO.kt
@@ -0,0 +1,25 @@
+package org.koitharu.kotatsu.core.util.ext
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.withContext
+import okhttp3.ResponseBody
+import okio.BufferedSink
+import okio.Source
+import org.koitharu.kotatsu.core.util.CancellableSource
+import org.koitharu.kotatsu.core.util.progress.ProgressResponseBody
+
+fun ResponseBody.withProgress(progressState: MutableStateFlow