Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d78c64350 | ||
|
|
321a9ecf62 | ||
|
|
439a01c43f | ||
|
|
3a9d0def7d |
@@ -17,8 +17,8 @@ android {
|
|||||||
//TODO: update as soon as sources becomes available
|
//TODO: update as soon as sources becomes available
|
||||||
//noinspection OldTargetApi
|
//noinspection OldTargetApi
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 562
|
versionCode 564
|
||||||
versionName '5.3.5'
|
versionName '5.3.7'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation('com.github.KotatsuApp:kotatsu-parsers:db96a1ff2e') {
|
implementation('com.github.KotatsuApp:kotatsu-parsers:69e0a531df') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,8 +127,8 @@ dependencies {
|
|||||||
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
|
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
|
||||||
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
|
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
|
||||||
|
|
||||||
implementation 'com.google.dagger:hilt-android:2.46.1'
|
implementation 'com.google.dagger:hilt-android:2.47'
|
||||||
kapt 'com.google.dagger:hilt-compiler:2.46.1'
|
kapt 'com.google.dagger:hilt-compiler:2.47'
|
||||||
implementation 'androidx.hilt:hilt-work:1.0.0'
|
implementation 'androidx.hilt:hilt-work:1.0.0'
|
||||||
kapt 'androidx.hilt:hilt-compiler:1.0.0'
|
kapt 'androidx.hilt:hilt-compiler:1.0.0'
|
||||||
|
|
||||||
@@ -157,6 +157,6 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.room:room-testing:2.5.2'
|
androidTestImplementation 'androidx.room:room-testing:2.5.2'
|
||||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
|
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
|
||||||
|
|
||||||
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.46.1'
|
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.47'
|
||||||
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.46.1'
|
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.47'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import java.util.EnumSet
|
|||||||
class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.DUMMY) {
|
class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.DUMMY) {
|
||||||
|
|
||||||
override val configKeyDomain: ConfigKey.Domain
|
override val configKeyDomain: ConfigKey.Domain
|
||||||
get() = ConfigKey.Domain()
|
get() = ConfigKey.Domain("")
|
||||||
|
|
||||||
override val sortOrders: Set<SortOrder>
|
override val sortOrders: Set<SortOrder>
|
||||||
get() = EnumSet.allOf(SortOrder::class.java)
|
get() = EnumSet.allOf(SortOrder::class.java)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.bookmarks.domain
|
package org.koitharu.kotatsu.bookmarks.domain
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.local.data.ImageFileFilter
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -26,7 +27,8 @@ class Bookmark(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun isImageUrlDirect(): Boolean {
|
private fun isImageUrlDirect(): Boolean {
|
||||||
return imageUrl.substringAfterLast('.').length in 2..4
|
val extension = imageUrl.substringAfterLast('.')
|
||||||
|
return extension.isNotEmpty() && ImageFileFilter().isExtensionValid(extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
|||||||
@@ -5,15 +5,19 @@ import coil.ImageLoader
|
|||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||||
|
import org.koitharu.kotatsu.core.ui.drawable.TextDrawable
|
||||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getThemeResId
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
import org.koitharu.kotatsu.databinding.ItemBookmarkBinding
|
import org.koitharu.kotatsu.databinding.ItemBookmarkBinding
|
||||||
|
import org.koitharu.kotatsu.parsers.util.format
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
fun bookmarkListAD(
|
fun bookmarkListAD(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package org.koitharu.kotatsu.core.ui.drawable
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.ColorFilter
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.PixelFormat
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextPaint
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.Px
|
||||||
|
import androidx.annotation.StyleRes
|
||||||
|
import androidx.core.graphics.withTranslation
|
||||||
|
import com.google.android.material.resources.TextAppearance
|
||||||
|
import com.google.android.material.resources.TextAppearanceFontCallback
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
|
|
||||||
|
class TextDrawable(
|
||||||
|
val text: CharSequence,
|
||||||
|
) : Drawable() {
|
||||||
|
|
||||||
|
private val paint = TextPaint(Paint.ANTI_ALIAS_FLAG)
|
||||||
|
private var cachedLayout: StaticLayout? = null
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
constructor(context: Context, text: CharSequence, @StyleRes textAppearanceId: Int) : this(text) {
|
||||||
|
val ta = TextAppearance(context, textAppearanceId)
|
||||||
|
paint.color = ta.textColor?.defaultColor ?: context.getThemeColor(android.R.attr.textColorPrimary, Color.BLACK)
|
||||||
|
paint.typeface = ta.fallbackFont
|
||||||
|
ta.getFontAsync(
|
||||||
|
context, paint,
|
||||||
|
object : TextAppearanceFontCallback() {
|
||||||
|
override fun onFontRetrieved(typeface: Typeface?, fontResolvedSynchronously: Boolean) = Unit
|
||||||
|
override fun onFontRetrievalFailed(reason: Int) = Unit
|
||||||
|
},
|
||||||
|
)
|
||||||
|
paint.letterSpacing = ta.letterSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
var alignment = Layout.Alignment.ALIGN_NORMAL
|
||||||
|
|
||||||
|
var lineSpacingMultiplier = 1f
|
||||||
|
|
||||||
|
@Px
|
||||||
|
var lineSpacingExtra = 0f
|
||||||
|
|
||||||
|
@get:ColorInt
|
||||||
|
var textColor: Int
|
||||||
|
get() = paint.color
|
||||||
|
set(@ColorInt value) {
|
||||||
|
paint.color = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
val b = bounds
|
||||||
|
if (b.isEmpty) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
canvas.withTranslation(x = b.left.toFloat(), y = b.top.toFloat()) {
|
||||||
|
obtainLayout().draw(canvas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAlpha(alpha: Int) {
|
||||||
|
paint.alpha = alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||||
|
paint.setColorFilter(colorFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||||
|
|
||||||
|
private fun obtainLayout(): StaticLayout {
|
||||||
|
val width = bounds.width()
|
||||||
|
cachedLayout?.let {
|
||||||
|
if (it.width == width) {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
StaticLayout.Builder.obtain(text, 0, text.length, paint, width)
|
||||||
|
.setAlignment(alignment)
|
||||||
|
.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)
|
||||||
|
.setIncludePad(true)
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
StaticLayout(text, paint, width, alignment, lineSpacingMultiplier, lineSpacingExtra, true)
|
||||||
|
}.also { cachedLayout = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,3 +60,10 @@ fun Context.getThemeColorStateList(
|
|||||||
) = obtainStyledAttributes(intArrayOf(resId)).use {
|
) = obtainStyledAttributes(intArrayOf(resId)).use {
|
||||||
it.getColorStateList(0)
|
it.getColorStateList(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.getThemeResId(
|
||||||
|
@AttrRes resId: Int,
|
||||||
|
fallback: Int
|
||||||
|
): Int = obtainStyledAttributes(intArrayOf(resId)).use {
|
||||||
|
it.getResourceId(0, fallback)
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class ImageFileFilter : FilenameFilter, FileFilter {
|
|||||||
return isExtensionValid(ext)
|
return isExtensionValid(ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isExtensionValid(ext: String): Boolean {
|
fun isExtensionValid(ext: String): Boolean {
|
||||||
return ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "webp"
|
return ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "webp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,34 +8,28 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.result.ActivityResultCallback
|
import androidx.activity.result.ActivityResultCallback
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.TwoStatePreference
|
import androidx.preference.TwoStatePreference
|
||||||
|
import androidx.preference.forEach
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runInterruptible
|
|
||||||
import okhttp3.Cache
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
|
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||||
import org.koitharu.kotatsu.core.util.FileSize
|
import org.koitharu.kotatsu.core.util.FileSize
|
||||||
import org.koitharu.kotatsu.core.util.ext.awaitStateAtLeast
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
|
||||||
import org.koitharu.kotatsu.local.data.CacheDir
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
|
||||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
|
||||||
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
|
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
|
||||||
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
|
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
|
||||||
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
|
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
|
||||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -43,24 +37,11 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|||||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||||
ActivityResultCallback<Uri?> {
|
ActivityResultCallback<Uri?> {
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var trackerRepo: TrackingRepository
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var searchRepository: MangaSearchRepository
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var storageManager: LocalStorageManager
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var cookieJar: MutableCookieJar
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var cache: Cache
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appShortcutManager: AppShortcutManager
|
lateinit var appShortcutManager: AppShortcutManager
|
||||||
|
|
||||||
|
private val viewModel: UserDataSettingsViewModel by viewModels()
|
||||||
|
|
||||||
private val backupSelectCall = registerForActivityResult(
|
private val backupSelectCall = registerForActivityResult(
|
||||||
ActivityResultContracts.OpenDocument(),
|
ActivityResultContracts.OpenDocument(),
|
||||||
this,
|
this,
|
||||||
@@ -76,23 +57,26 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.bindSummaryToCacheSize(CacheDir.PAGES)
|
findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.PAGES]))
|
||||||
findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.bindSummaryToCacheSize(CacheDir.THUMBS)
|
findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.THUMBS]))
|
||||||
findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR)?.bindSummaryToHttpCacheSize()
|
findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR)?.bindBytesSizeSummary(viewModel.httpCacheSize)
|
||||||
findPreference<Preference>(AppSettings.KEY_SEARCH_HISTORY_CLEAR)?.let { pref ->
|
findPreference<Preference>(AppSettings.KEY_SEARCH_HISTORY_CLEAR)?.let { pref ->
|
||||||
viewLifecycleScope.launch {
|
viewModel.searchHistoryCount.observe(viewLifecycleOwner) {
|
||||||
lifecycle.awaitStateAtLeast(Lifecycle.State.RESUMED)
|
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, it, it)
|
||||||
val items = searchRepository.getSearchHistoryCount()
|
|
||||||
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, items, items)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
findPreference<Preference>(AppSettings.KEY_UPDATES_FEED_CLEAR)?.let { pref ->
|
findPreference<Preference>(AppSettings.KEY_UPDATES_FEED_CLEAR)?.let { pref ->
|
||||||
viewLifecycleScope.launch {
|
viewModel.feedItemsCount.observe(viewLifecycleOwner) {
|
||||||
lifecycle.awaitStateAtLeast(Lifecycle.State.RESUMED)
|
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, it, it)
|
||||||
val items = trackerRepo.getLogsCount()
|
|
||||||
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, items, items)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModel.loadingKeys.observe(viewLifecycleOwner) { keys ->
|
||||||
|
preferenceScreen.forEach { pref ->
|
||||||
|
pref.isEnabled = pref.key !in keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))
|
||||||
|
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(listView))
|
||||||
settings.subscribe(this)
|
settings.subscribe(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,12 +88,12 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
return when (preference.key) {
|
return when (preference.key) {
|
||||||
AppSettings.KEY_PAGES_CACHE_CLEAR -> {
|
AppSettings.KEY_PAGES_CACHE_CLEAR -> {
|
||||||
clearCache(preference, CacheDir.PAGES)
|
viewModel.clearCache(preference.key, CacheDir.PAGES)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
|
AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
|
||||||
clearCache(preference, CacheDir.THUMBS)
|
viewModel.clearCache(preference.key, CacheDir.THUMBS)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,26 +103,17 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
|
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
|
||||||
clearSearchHistory(preference)
|
clearSearchHistory()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_HTTP_CACHE_CLEAR -> {
|
AppSettings.KEY_HTTP_CACHE_CLEAR -> {
|
||||||
clearHttpCache()
|
viewModel.clearHttpCache()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_UPDATES_FEED_CLEAR -> {
|
AppSettings.KEY_UPDATES_FEED_CLEAR -> {
|
||||||
viewLifecycleScope.launch {
|
viewModel.clearUpdatesFeed()
|
||||||
trackerRepo.clearLogs()
|
|
||||||
preference.summary = preference.context.resources
|
|
||||||
.getQuantityString(R.plurals.items, 0, 0)
|
|
||||||
Snackbar.make(
|
|
||||||
view ?: return@launch,
|
|
||||||
R.string.updates_feed_cleared,
|
|
||||||
Snackbar.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,71 +164,23 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearCache(preference: Preference, cache: CacheDir) {
|
private fun Preference.bindBytesSizeSummary(stateFlow: StateFlow<Long>) {
|
||||||
val ctx = preference.context.applicationContext
|
stateFlow.observe(viewLifecycleOwner) { size ->
|
||||||
viewLifecycleScope.launch {
|
summary = if (size < 0) {
|
||||||
try {
|
context.getString(R.string.computing_)
|
||||||
preference.isEnabled = false
|
} else {
|
||||||
storageManager.clearCache(cache)
|
FileSize.BYTES.format(context, size)
|
||||||
val size = storageManager.computeCacheSize(cache)
|
|
||||||
preference.summary = FileSize.BYTES.format(ctx, size)
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
throw e
|
|
||||||
} catch (e: Exception) {
|
|
||||||
preference.summary = e.getDisplayMessage(ctx.resources)
|
|
||||||
} finally {
|
|
||||||
preference.isEnabled = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Preference.bindSummaryToCacheSize(dir: CacheDir) = viewLifecycleScope.launch {
|
private fun clearSearchHistory() {
|
||||||
val size = storageManager.computeCacheSize(dir)
|
|
||||||
summary = FileSize.BYTES.format(context, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preference.bindSummaryToHttpCacheSize() = viewLifecycleScope.launch {
|
|
||||||
val size = runInterruptible(Dispatchers.IO) { cache.size() }
|
|
||||||
summary = FileSize.BYTES.format(context, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearHttpCache() {
|
|
||||||
val preference = findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR) ?: return
|
|
||||||
val ctx = preference.context.applicationContext
|
|
||||||
viewLifecycleScope.launch {
|
|
||||||
try {
|
|
||||||
preference.isEnabled = false
|
|
||||||
val size = runInterruptible(Dispatchers.IO) {
|
|
||||||
cache.evictAll()
|
|
||||||
cache.size()
|
|
||||||
}
|
|
||||||
preference.summary = FileSize.BYTES.format(ctx, size)
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
throw e
|
|
||||||
} catch (e: Exception) {
|
|
||||||
preference.summary = e.getDisplayMessage(ctx.resources)
|
|
||||||
} finally {
|
|
||||||
preference.isEnabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearSearchHistory(preference: Preference) {
|
|
||||||
MaterialAlertDialogBuilder(context ?: return)
|
MaterialAlertDialogBuilder(context ?: return)
|
||||||
.setTitle(R.string.clear_search_history)
|
.setTitle(R.string.clear_search_history)
|
||||||
.setMessage(R.string.text_clear_search_history_prompt)
|
.setMessage(R.string.text_clear_search_history_prompt)
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.setPositiveButton(R.string.clear) { _, _ ->
|
.setPositiveButton(R.string.clear) { _, _ ->
|
||||||
viewLifecycleScope.launch {
|
viewModel.clearSearchHistory()
|
||||||
searchRepository.clearSearchHistory()
|
|
||||||
preference.summary = preference.context.resources
|
|
||||||
.getQuantityString(R.plurals.items, 0, 0)
|
|
||||||
Snackbar.make(
|
|
||||||
view ?: return@launch,
|
|
||||||
R.string.search_history_cleared,
|
|
||||||
Snackbar.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,14 +190,7 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|||||||
.setMessage(R.string.text_clear_cookies_prompt)
|
.setMessage(R.string.text_clear_cookies_prompt)
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.setPositiveButton(R.string.clear) { _, _ ->
|
.setPositiveButton(R.string.clear) { _, _ ->
|
||||||
viewLifecycleScope.launch {
|
viewModel.clearCookies()
|
||||||
cookieJar.clear()
|
|
||||||
Snackbar.make(
|
|
||||||
listView ?: return@launch,
|
|
||||||
R.string.cookies_cleared,
|
|
||||||
Snackbar.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package org.koitharu.kotatsu.settings
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.runInterruptible
|
||||||
|
import okhttp3.Cache
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
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.local.data.CacheDir
|
||||||
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
|
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
|
import java.util.EnumMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class UserDataSettingsViewModel @Inject constructor(
|
||||||
|
private val storageManager: LocalStorageManager,
|
||||||
|
private val httpCache: Cache,
|
||||||
|
private val searchRepository: MangaSearchRepository,
|
||||||
|
private val trackingRepository: TrackingRepository,
|
||||||
|
private val cookieJar: MutableCookieJar,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val onActionDone = MutableEventFlow<ReversibleAction>()
|
||||||
|
val loadingKeys = MutableStateFlow(emptySet<String>())
|
||||||
|
|
||||||
|
val searchHistoryCount = MutableStateFlow(-1)
|
||||||
|
val feedItemsCount = MutableStateFlow(-1)
|
||||||
|
val httpCacheSize = MutableStateFlow(-1L)
|
||||||
|
val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)
|
||||||
|
|
||||||
|
init {
|
||||||
|
CacheDir.values().forEach {
|
||||||
|
cacheSizes[it] = MutableStateFlow(-1L)
|
||||||
|
}
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
searchHistoryCount.value = searchRepository.getSearchHistoryCount()
|
||||||
|
}
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
feedItemsCount.value = trackingRepository.getLogsCount()
|
||||||
|
}
|
||||||
|
CacheDir.values().forEach { cache ->
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
httpCacheSize.value = runInterruptible { httpCache.size() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCache(key: String, cache: CacheDir) {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
loadingKeys.update { it + key }
|
||||||
|
storageManager.clearCache(cache)
|
||||||
|
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
||||||
|
} finally {
|
||||||
|
loadingKeys.update { it - key }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearHttpCache() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
loadingKeys.update { it + AppSettings.KEY_HTTP_CACHE_CLEAR }
|
||||||
|
val size = runInterruptible(Dispatchers.IO) {
|
||||||
|
httpCache.evictAll()
|
||||||
|
httpCache.size()
|
||||||
|
}
|
||||||
|
httpCacheSize.value = size
|
||||||
|
} finally {
|
||||||
|
loadingKeys.update { it - AppSettings.KEY_HTTP_CACHE_CLEAR }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSearchHistory() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
searchRepository.clearSearchHistory()
|
||||||
|
searchHistoryCount.value = searchRepository.getSearchHistoryCount()
|
||||||
|
onActionDone.call(ReversibleAction(R.string.search_history_cleared, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCookies() {
|
||||||
|
launchJob {
|
||||||
|
cookieJar.clear()
|
||||||
|
onActionDone.call(ReversibleAction(R.string.cookies_cleared, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearUpdatesFeed() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
trackingRepository.clearLogs()
|
||||||
|
feedItemsCount.value = trackingRepository.getLogsCount()
|
||||||
|
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.0.2'
|
classpath 'com.android.tools.build:gradle:8.0.2'
|
||||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22'
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22'
|
||||||
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.46.1'
|
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.47'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user