Compare commits

...

12 Commits

Author SHA1 Message Date
d604ff3c24 Update ssiv 2022-09-22 16:28:25 +03:00
Sergio Varela
4f9eee7d46 Translated using Weblate (Spanish)
Currently translated at 100.0% (388 of 388 strings)

Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2022-09-22 16:24:06 +03:00
Zero O
fe673a94ed Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (388 of 388 strings)

Co-authored-by: Zero O <godarms2010@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2022-09-22 16:24:06 +03:00
bcd891d653 Upgrade plugins 2022-09-21 20:48:29 +03:00
1e75edf262 Fix coroutines cancellation 2022-09-21 18:50:20 +03:00
73478d6a81 Fix getParcelableCompat extensions 2022-09-19 10:06:43 +03:00
982080a930 Update ssiv 2022-09-18 13:26:25 +03:00
66cd5070dc Update FUNDING.yml 2022-09-17 10:53:46 +03:00
vianh
c05b0eaa59 Move fallback inset to dimens 2022-09-16 08:29:54 +03:00
vianh
29a073d844 Adjust reader info bar 2022-09-16 08:29:54 +03:00
a0e69428e4 Widget theme fix #225 2022-09-16 08:29:32 +03:00
3f6a103915 Rename Library to Shelf 2022-09-14 18:46:32 +03:00
75 changed files with 454 additions and 325 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1 +1,2 @@
custom: ["https://yoomoney.ru/to/410012543938752"]
ko_fi: koitharu

3
.idea/kotlinc.xml generated
View File

@@ -3,4 +3,7 @@
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="1.8" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.7.10" />
</component>
</project>

View File

@@ -15,8 +15,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 33
versionCode 497
versionName '4.0-beta1'
versionCode 498
versionName '4.0-beta2'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -124,7 +124,7 @@ dependencies {
implementation 'io.coil-kt:coil-base:2.2.1'
implementation 'io.coil-kt:coil-svg:2.2.1'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:a9ab74c8b4'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:0ff0278f0f'
implementation 'com.github.solkin:disk-lru-cache:1.4'
implementation 'ch.acra:acra-http:5.9.6'

View File

@@ -1,9 +1,12 @@
package org.koitharu.kotatsu.base.domain
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
fun interface ReversibleHandle {
@@ -11,8 +14,10 @@ fun interface ReversibleHandle {
}
fun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.Default) {
runCatching {
reverse()
runCatchingCancellable {
withContext(NonCancellable) {
reverse()
}
}.onFailure {
it.printStackTraceDebug()
}
@@ -21,4 +26,4 @@ fun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.D
operator fun ReversibleHandle.plus(other: ReversibleHandle) = ReversibleHandle {
this.reverse()
other.reverse()
}
}

View File

@@ -20,7 +20,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.viewbinding.ViewBinding
import dagger.hilt.android.EntryPointAccessors
import javax.inject.Inject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.util.ActionModeDelegate
@@ -30,6 +29,7 @@ import org.koitharu.kotatsu.base.ui.util.inject
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.utils.ext.getThemeColor
import javax.inject.Inject
abstract class BaseActivity<B : ViewBinding> :
AppCompatActivity(),
@@ -134,6 +134,7 @@ abstract class BaseActivity<B : ViewBinding> :
window.statusBarColor = getThemeColor(android.R.attr.statusBarColor)
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun onBackPressed() {
if ( // https://issuetracker.google.com/issues/139738913
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q &&

View File

@@ -1,13 +1,14 @@
package org.koitharu.kotatsu.core.backup
import androidx.room.withTransaction
import javax.inject.Inject
import org.json.JSONArray
import org.json.JSONObject
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 javax.inject.Inject
private const val PAGE_SIZE = 10
@@ -85,7 +86,7 @@ class BackupRepository @Inject constructor(private val db: MangaDatabase) {
JsonDeserializer(it).toTagEntity()
}
val history = JsonDeserializer(item).toHistoryEntity()
result += runCatching {
result += runCatchingCancellable {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)
@@ -100,7 +101,7 @@ class BackupRepository @Inject constructor(private val db: MangaDatabase) {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val category = JsonDeserializer(item).toFavouriteCategoryEntity()
result += runCatching {
result += runCatchingCancellable {
db.favouriteCategoriesDao.upsert(category)
}
}
@@ -116,7 +117,7 @@ class BackupRepository @Inject constructor(private val db: MangaDatabase) {
JsonDeserializer(it).toTagEntity()
}
val favourite = JsonDeserializer(item).toFavouriteEntity()
result += runCatching {
result += runCatchingCancellable {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)

View File

@@ -4,13 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -24,6 +17,14 @@ 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 java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.inject.Inject
import javax.inject.Singleton
private const val CERT_SHA1 = "2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE"
@@ -59,7 +60,7 @@ class AppUpdateRepository @Inject constructor(
if (!isUpdateSupported()) {
return@withContext null
}
runCatching {
runCatchingCancellable {
val currentVersion = VersionId(BuildConfig.VERSION_NAME)
val available = getAvailableVersions().asArrayList()
available.sortBy { it.versionId }

View File

@@ -2,7 +2,12 @@ package org.koitharu.kotatsu.core.model.parcelable
import android.os.Parcel
import androidx.core.os.ParcelCompat
import org.koitharu.kotatsu.parsers.model.*
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)
@@ -37,11 +42,11 @@ fun Parcel.readManga() = Manga(
coverUrl = requireNotNull(readString()),
largeCoverUrl = readString(),
description = readString(),
tags = requireNotNull(readParcelable<ParcelableMangaTags>(ParcelableMangaTags::class.java.classLoader)).tags,
state = readSerializable() as MangaState?,
tags = requireNotNull(readParcelableCompat<ParcelableMangaTags>()).tags,
state = readSerializableCompat(),
author = readString(),
chapters = readParcelable<ParcelableMangaChapters>(ParcelableMangaChapters::class.java.classLoader)?.chapters,
source = readSerializable() as MangaSource,
chapters = readParcelableCompat<ParcelableMangaChapters>()?.chapters,
source = checkNotNull(readSerializableCompat()),
)
fun MangaPage.writeToParcel(out: Parcel) {
@@ -57,7 +62,7 @@ fun Parcel.readMangaPage() = MangaPage(
url = requireNotNull(readString()),
referer = requireNotNull(readString()),
preview = readString(),
source = readSerializable() as MangaSource,
source = checkNotNull(readSerializableCompat()),
)
fun MangaChapter.writeToParcel(out: Parcel) {
@@ -79,7 +84,7 @@ fun Parcel.readMangaChapter() = MangaChapter(
scanlator = readString(),
uploadDate = readLong(),
branch = readString(),
source = readSerializable() as MangaSource,
source = checkNotNull(readSerializableCompat()),
)
fun MangaTag.writeToParcel(out: Parcel) {
@@ -91,5 +96,5 @@ fun MangaTag.writeToParcel(out: Parcel) {
fun Parcel.readMangaTag() = MangaTag(
title = requireNotNull(readString()),
key = requireNotNull(readString()),
source = readSerializable() as MangaSource,
)
source = checkNotNull(readSerializableCompat()),
)

View File

@@ -50,4 +50,4 @@ class ParcelableManga(
return arrayOfNulls(size)
}
}
}
}

View File

@@ -17,8 +17,6 @@ import coil.request.ImageRequest
import coil.size.Precision
import coil.size.Scale
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@@ -32,6 +30,9 @@ 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 javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ShortcutsUpdater @Inject constructor(
@@ -92,7 +93,7 @@ class ShortcutsUpdater @Inject constructor(
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
private suspend fun updateShortcutsImpl() = runCatching {
private suspend fun updateShortcutsImpl() = runCatchingCancellable {
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
val shortcuts = historyRepository.getList(0, manager.maxShortcutCountPerActivity)
.filter { x -> x.title.isNotEmpty() }
@@ -112,7 +113,7 @@ class ShortcutsUpdater @Inject constructor(
}
private suspend fun buildShortcutInfo(manga: Manga): ShortcutInfoCompat.Builder {
val icon = runCatching {
val icon = runCatchingCancellable {
coil.execute(
ImageRequest.Builder(context)
.data(manga.coverUrl)

View File

@@ -45,6 +45,7 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class DetailsViewModel @AssistedInject constructor(
@Assisted intent: MangaIntent,
@@ -189,7 +190,7 @@ class DetailsViewModel @AssistedInject constructor(
checkNotNull(manga) { "Cannot find saved manga for ${m.title}" }
val original = localMangaRepository.getRemoteManga(manga)
localMangaRepository.delete(manga) || throw IOException("Unable to delete file")
runCatching {
runCatchingCancellable {
historyRepository.deleteOrSwap(manga, original)
}
onMangaRemoved.postCall(manga)
@@ -228,7 +229,7 @@ class DetailsViewModel @AssistedInject constructor(
reload()
} else {
viewModelScope.launch(Dispatchers.Default) {
runCatching {
runCatchingCancellable {
localMangaRepository.getDetails(downloadedManga)
}.onSuccess {
delegate.relatedManga.value = it

View File

@@ -17,6 +17,7 @@ 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
class MangaDetailsDelegate(
private val intent: MangaIntent,
@@ -45,9 +46,9 @@ class MangaDetailsDelegate(
val hist = historyRepository.getOne(manga)
selectedBranch.value = manga.getPreferredBranch(hist)
mangaData.value = manga
relatedManga.value = runCatching {
relatedManga.value = runCatchingCancellable {
if (manga.source == MangaSource.LOCAL) {
val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatching null
val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatchingCancellable null
mangaRepositoryFactory.create(m.source).getDetails(m)
} else {
localMangaRepository.findSavedManga(manga)

View File

@@ -9,11 +9,18 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.internal.closeQuietly
@@ -32,7 +39,9 @@ import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.utils.ext.deleteAwait
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.progress.PausingProgressJob
import java.io.File
private const val MAX_FAILSAFE_ATTEMPTS = 2
private const val DOWNLOAD_ERROR_DELAY = 500L
@@ -231,7 +240,7 @@ class DownloadManager @AssistedInject constructor(
)
}
private suspend fun loadCover(manga: Manga) = runCatching {
private suspend fun loadCover(manga: Manga) = runCatchingCancellable {
imageLoader.execute(
ImageRequest.Builder(context)
.data(manga.coverUrl)

View File

@@ -16,9 +16,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.collections.set
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.BuildConfig
@@ -28,10 +25,14 @@ 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.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() {
@@ -65,7 +66,7 @@ class DownloadService : BaseService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val manga = intent?.getParcelableExtra<ParcelableManga>(EXTRA_MANGA)?.manga
val manga = intent?.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga
val chapters = intent?.getLongArrayExtra(EXTRA_CHAPTERS_IDS)
return if (manga != null) {
jobs[startId] = downloadManga(startId, manga, chapters)
@@ -254,7 +255,7 @@ class DownloadService : BaseService() {
fun getDownloadedManga(intent: Intent?): Manga? {
if (intent?.action == ACTION_DOWNLOAD_COMPLETE) {
return intent.getParcelableExtra<ParcelableManga>(EXTRA_MANGA)?.manga
return intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga
}
return null
}

View File

@@ -28,6 +28,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class FavouritesListViewModel @AssistedInject constructor(
@Assisted private val categoryId: Long,
@@ -69,6 +70,7 @@ class FavouritesListViewModel @AssistedInject constructor(
actionStringRes = 0,
),
)
else -> list.toUi(mode, this)
}
}.catch {
@@ -79,7 +81,7 @@ class FavouritesListViewModel @AssistedInject constructor(
if (categoryId != NO_ID) {
launchJob {
categoryName = withContext(Dispatchers.Default) {
runCatching {
runCatchingCancellable {
repository.getCategory(categoryId).title
}.getOrNull()
}

View File

@@ -1,15 +0,0 @@
package org.koitharu.kotatsu.library.ui.adapter
import android.view.View
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.parsers.model.Manga
interface LibraryListEventListener : ListStateHolderListener {
fun onItemClick(item: Manga, section: LibrarySectionModel, view: View)
fun onItemLongClick(item: Manga, section: LibrarySectionModel, view: View): Boolean
fun onSectionClick(section: LibrarySectionModel, view: View)
}

View File

@@ -6,15 +6,22 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.update
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import java.text.Collator
import java.util.*
import java.util.Locale
import java.util.TreeSet
class FilterCoordinator(
private val repository: RemoteMangaRepository,
@@ -153,7 +160,7 @@ class FilterCoordinator(
}
private fun loadTagsAsync() = coroutineScope.async(Dispatchers.Default, CoroutineStart.LAZY) {
runCatching {
runCatchingCancellable {
repository.getTags()
}.onFailure { error ->
error.printStackTraceDebug()
@@ -204,4 +211,4 @@ class FilterCoordinator(
return collator?.compare(t1, t2) ?: compareValues(t1, t2)
}
}
}
}

View File

@@ -7,28 +7,40 @@ import androidx.annotation.WorkerThread
import androidx.collection.ArraySet
import androidx.core.net.toFile
import androidx.core.net.toUri
import java.io.File
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.runInterruptible
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.local.data.CbzFilter
import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.local.data.MangaIndex
import org.koitharu.kotatsu.local.data.TempFileFilter
import org.koitharu.kotatsu.parsers.model.*
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.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.toCamelCase
import org.koitharu.kotatsu.utils.AlphanumComparator
import org.koitharu.kotatsu.utils.CompositeMutex
import org.koitharu.kotatsu.utils.ext.deleteAwait
import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.readText
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import java.io.File
import java.util.Enumeration
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext
private const val MAX_PARALLELISM = 4
@@ -58,13 +70,14 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
if (!tags.isNullOrEmpty()) {
list.retainAll { x -> x.containsTags(tags) }
}
@Suppress("NON_EXHAUSTIVE_WHEN_STATEMENT")
when (sortOrder) {
SortOrder.ALPHABETICAL -> list.sortWith(compareBy(AlphanumComparator()) { x -> x.manga.title })
SortOrder.RATING -> list.sortByDescending { it.manga.rating }
SortOrder.NEWEST,
SortOrder.UPDATED,
-> list.sortByDescending { it.createdAt }
else -> Unit
}
return list.unwrap()
}
@@ -73,6 +86,7 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
manga.source != MangaSource.LOCAL -> requireNotNull(findSavedManga(manga)) {
"Manga is not local or saved"
}
else -> getFromFile(Uri.parse(manga.url).toFile())
}
@@ -236,7 +250,7 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
context: CoroutineContext,
): Deferred<LocalManga?> = async(context) {
runInterruptible {
runCatching { LocalManga(getFromFile(file), file) }.getOrNull()
runCatchingCancellable { LocalManga(getFromFile(file), file) }.getOrNull()
}
}

View File

@@ -9,13 +9,14 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import javax.inject.Inject
@AndroidEntryPoint
class LocalChaptersRemoveService : CoroutineIntentService() {
@@ -34,7 +35,7 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
}
override suspend fun processIntent(intent: Intent?) {
val manga = intent?.getParcelableExtra<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
val manga = intent?.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
startForeground()
val mangaWithChapters = localMangaRepository.getDetails(manga)

View File

@@ -29,6 +29,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
@HiltViewModel
class LocalListViewModel @Inject constructor(
@@ -63,6 +64,7 @@ class LocalListViewModel @Inject constructor(
actionStringRes = R.string._import,
),
)
else -> buildList(list.size + 1) {
add(createHeader(list, tags, order))
list.toUi(this, mode, this@LocalListViewModel)
@@ -104,7 +106,7 @@ class LocalListViewModel @Inject constructor(
for (manga in itemsToRemove) {
val original = repository.getRemoteManga(manga)
repository.delete(manga) || throw IOException("Unable to delete file")
runCatching {
runCatchingCancellable {
historyRepository.deleteOrSwap(manga, original)
}
mangaList.update { list ->
@@ -120,6 +122,8 @@ class LocalListViewModel @Inject constructor(
try {
listError.value = null
mangaList.value = repository.getList(0, selectedTags.value, sortOrder.value)
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
listError.value = e
}
@@ -128,7 +132,7 @@ class LocalListViewModel @Inject constructor(
private fun cleanup() {
if (!DownloadService.isRunning && !ImportService.isRunning && !LocalChaptersRemoveService.isRunning) {
viewModelScope.launch {
runCatching {
runCatchingCancellable {
repository.cleanup()
}.onFailure { error ->
error.printStackTraceDebug()

View File

@@ -34,7 +34,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.library.ui.LibraryFragment
import org.koitharu.kotatsu.shelf.ui.ShelfFragment
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.model.Manga
@@ -136,7 +136,7 @@ class MainActivity :
}
override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
if (fragment is LibraryFragment) {
if (fragment is ShelfFragment) {
binding.fab?.show()
} else {
binding.fab?.hide()
@@ -313,7 +313,7 @@ class MainActivity :
isResumeEnabled &&
!actionModeDelegate.isActionModeStarted &&
!isSearchOpened &&
topFragment is LibraryFragment
topFragment is ShelfFragment
) {
if (fab?.isVisible == false) {
fab.show()

View File

@@ -10,7 +10,7 @@ import com.google.android.material.navigation.NavigationBarView
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.explore.ui.ExploreFragment
import org.koitharu.kotatsu.library.ui.LibraryFragment
import org.koitharu.kotatsu.shelf.ui.ShelfFragment
import org.koitharu.kotatsu.settings.tools.ToolsFragment
import org.koitharu.kotatsu.tracker.ui.FeedFragment
import java.util.*
@@ -73,7 +73,7 @@ class MainNavigationDelegate(
private fun onNavigationItemSelected(@IdRes itemId: Int): Boolean {
setPrimaryFragment(
when (itemId) {
R.id.nav_library -> LibraryFragment.newInstance()
R.id.nav_shelf -> ShelfFragment.newInstance()
R.id.nav_explore -> ExploreFragment.newInstance()
R.id.nav_feed -> FeedFragment.newInstance()
R.id.nav_tools -> ToolsFragment.newInstance()

View File

@@ -62,8 +62,9 @@ class ReaderInfoBarView @JvmOverloads constructor(
init {
paint.strokeWidth = context.resources.resolveDp(2f)
val insetCorner = getSystemUiDimensionOffset("rounded_corner_content_padding")
val insetStart = getSystemUiDimensionOffset("status_bar_padding_start") + insetCorner
val insetEnd = getSystemUiDimensionOffset("status_bar_padding_end") + insetCorner
val fallbackInset = resources.getDimensionPixelOffset(R.dimen.reader_bar_inset_fallback)
val insetStart = getSystemUiDimensionOffset("status_bar_padding_start", fallbackInset) + insetCorner
val insetEnd = getSystemUiDimensionOffset("status_bar_padding_end", fallbackInset) + insetCorner
val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
insetLeft = if (isRtl) insetEnd else insetStart
insetRight = if (isRtl) insetStart else insetEnd
@@ -179,12 +180,12 @@ class ReaderInfoBarView @JvmOverloads constructor(
}
}
private fun getSystemUiDimensionOffset(name: String): Int = runCatching {
private fun getSystemUiDimensionOffset(name: String, fallback: Int = 0): Int = runCatching {
val manager = context.packageManager
val resources = manager.getResourcesForApplication("com.android.systemui")
val resId = resources.getIdentifier(name, "dimen", "com.android.systemui")
resources.getDimensionPixelOffset(resId)
}.onFailure {
it.printStackTraceDebug()
}.getOrDefault(0)
}.getOrDefault(fallback)
}

View File

@@ -38,6 +38,7 @@ import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.requireValue
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
private const val BOUNDS_PAGE_OFFSET = 2
private const val PREFETCH_LIMIT = 10
@@ -326,7 +327,7 @@ class ReaderViewModel @AssistedInject constructor(
?: manga.chapters?.randomOrNull()
?: error("There are no chapters in this manga")
val pages = repo.getPages(chapter)
return runCatching {
return runCatchingCancellable {
val isWebtoon = dataRepository.determineMangaIsWebtoon(repo, pages)
if (isWebtoon) ReaderMode.WEBTOON else defaultMode
}.onSuccess {
@@ -381,7 +382,7 @@ class ReaderViewModel @AssistedInject constructor(
*/
private fun HistoryRepository.saveStateAsync(manga: Manga, state: ReaderState, percent: Float): Job {
return processLifecycleScope.launch(Dispatchers.Default) {
runCatching {
runCatchingCancellable {
addOrUpdate(
manga = manga,
chapterId = state.chapterId,

View File

@@ -4,17 +4,23 @@ import android.net.Uri
import androidx.core.net.toUri
import androidx.lifecycle.Observer
import com.davemorrissey.labs.subscaleview.DefaultOnImageEventListener
import java.io.File
import java.io.IOException
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
import java.io.File
import java.io.IOException
class PageHolderDelegate(
private val loader: PageLoader,
@@ -102,6 +108,8 @@ class PageHolderDelegate(
loader.convertInPlace(file)
state = State.CONVERTED
callback.onImageReady(file.toUri())
} catch (ce: CancellationException) {
throw ce
} catch (e2: Throwable) {
e.addSuppressed(e2)
state = State.ERROR

View File

@@ -17,6 +17,7 @@ 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.referer
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.ext.setTextColorAttr
fun pageThumbnailAD(
@@ -72,7 +73,7 @@ fun pageThumbnailAD(
text = (item.number).toString()
}
job = scope.launch {
val drawable = runCatching {
val drawable = runCatchingCancellable {
loadPageThumbnail(item)
}.getOrNull()
binding.imageViewThumb.setImageDrawable(drawable)

View File

@@ -5,11 +5,16 @@ import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
@@ -21,13 +26,20 @@ import org.koitharu.kotatsu.list.ui.filter.FilterCoordinator
import org.koitharu.kotatsu.list.ui.filter.FilterItem
import org.koitharu.kotatsu.list.ui.filter.FilterState
import org.koitharu.kotatsu.list.ui.filter.OnFilterChangedListener
import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListHeader2
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorFooter
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import java.util.LinkedList
private const val FILTER_MIN_INTERVAL = 250L
@@ -138,6 +150,8 @@ class RemoteListViewModel @AssistedInject constructor(
mangaList.value = mangaList.value?.plus(list) ?: list
}
hasNextPage.value = list.isNotEmpty()
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
e.printStackTraceDebug()
listError.value = e

View File

@@ -3,15 +3,20 @@ package org.koitharu.kotatsu.scrobbling.domain
import androidx.collection.LongSparseArray
import androidx.collection.getOrElse
import androidx.core.text.parseAsHtml
import java.util.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.domain.model.*
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
import org.koitharu.kotatsu.utils.ext.findKeyByValue
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import java.util.EnumMap
abstract class Scrobbler(
protected val db: MangaDatabase,
@@ -47,7 +52,7 @@ abstract class Scrobbler(
private suspend fun ScrobblingEntity.toScrobblingInfo(mangaId: Long): ScrobblingInfo? {
val mangaInfo = infoCache.getOrElse(targetId) {
runCatching {
runCatchingCancellable {
getMangaInfo(targetId)
}.onFailure {
it.printStackTraceDebug()
@@ -72,9 +77,9 @@ abstract class Scrobbler(
}
suspend fun Scrobbler.tryScrobble(mangaId: Long, chapter: MangaChapter): Boolean {
return runCatching {
return runCatchingCancellable {
scrobble(mangaId, chapter)
}.onFailure {
it.printStackTraceDebug()
}.isSuccess
}
}

View File

@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class MangaSearchRepository @Inject constructor(
private val settings: AppSettings,
@@ -33,7 +34,7 @@ class MangaSearchRepository @Inject constructor(
fun globalSearch(query: String, concurrency: Int = DEFAULT_CONCURRENCY): Flow<Manga> =
settings.getMangaSources(includeHidden = false).asFlow()
.flatMapMerge(concurrency) { source ->
runCatching {
runCatchingCancellable {
mangaRepositoryFactory.create(source).getList(
offset = 0,
query = query,

View File

@@ -4,6 +4,7 @@ import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -12,7 +13,13 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorFooter
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
@@ -47,6 +54,7 @@ class SearchViewModel @AssistedInject constructor(
actionStringRes = 0,
),
)
else -> {
val result = ArrayList<ListModel>(list.size + 1)
list.toUi(result, mode)
@@ -94,6 +102,8 @@ class SearchViewModel @AssistedInject constructor(
mangaList.value = mangaList.value?.plus(list) ?: list
}
hasNextPage.value = list.isNotEmpty()
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
listError.value = e
}

View File

@@ -20,6 +20,7 @@ import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
private const val MAX_PARALLELISM = 4
private const val MIN_HAS_MORE_ITEMS = 8
@@ -54,6 +55,7 @@ class MultiSearchViewModel @AssistedInject constructor(
)
},
)
loading -> list + LoadingFooter
else -> list
}
@@ -85,6 +87,8 @@ class MultiSearchViewModel @AssistedInject constructor(
loadingData.value = true
query.postValue(q)
searchImpl(q)
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
listError.value = e
} finally {
@@ -98,7 +102,7 @@ class MultiSearchViewModel @AssistedInject constructor(
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
val deferredList = sources.map { source ->
async(dispatcher) {
runCatching {
runCatchingCancellable {
val list = mangaRepositoryFactory.create(source).getList(offset = 0, query = q)
.toUi(ListMode.GRID)
if (list.isNotEmpty()) {

View File

@@ -8,7 +8,7 @@ import androidx.preference.Preference
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
@@ -23,6 +23,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import javax.inject.Inject
@AndroidEntryPoint
class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cache) {
@@ -82,18 +83,22 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
clearCache(preference, CacheDir.PAGES)
true
}
AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
clearCache(preference, CacheDir.THUMBS)
true
}
AppSettings.KEY_COOKIES_CLEAR -> {
clearCookies()
true
}
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
clearSearchHistory(preference)
true
}
AppSettings.KEY_UPDATES_FEED_CLEAR -> {
viewLifecycleScope.launch {
trackerRepo.clearLogs()
@@ -107,6 +112,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
}
true
}
AppSettings.KEY_SHIKIMORI -> {
if (!shikimoriRepository.isAuthorized) {
launchShikimoriAuth()
@@ -115,6 +121,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
super.onPreferenceTreeClick(preference)
}
}
else -> super.onPreferenceTreeClick(preference)
}
}
@@ -127,6 +134,8 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
storageManager.clearCache(cache)
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 {

View File

@@ -7,7 +7,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
@@ -20,7 +19,14 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.utils.ext.*
import org.koitharu.kotatsu.utils.ext.awaitViewLifecycle
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.ext.serializableArgument
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
@AndroidEntryPoint
class SourceSettingsFragment : BasePreferenceFragment(0) {
@@ -66,12 +72,13 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
startActivity(SourceAuthActivity.newIntent(preference.context, source))
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
private fun loadUsername(owner: LifecycleOwner, preference: Preference) = owner.lifecycleScope.launch {
runCatching {
runCatchingCancellable {
preference.summary = null
withContext(Dispatchers.Default) {
requireNotNull(repository?.getAuthProvider()?.getUsername())
@@ -91,6 +98,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
).setAction(ExceptionResolver.getResolveStringId(error)) { resolveError(error) }
.show()
}
else -> preference.summary = error.getDisplayMessage(preference.context.resources)
}
error.printStackTraceDebug()

View File

@@ -11,13 +11,13 @@ import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import java.io.FileOutputStream
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.databinding.DialogProgressBinding
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.progress.Progress
import java.io.File
import java.io.FileOutputStream
@AndroidEntryPoint
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
@@ -91,6 +91,8 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
}
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_LONG).show()
dismiss()
} catch (e: InterruptedException) {
throw e
} catch (e: Exception) {
onError(e)
}

View File

@@ -7,6 +7,7 @@ import android.net.Uri
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.StringRes
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
class RingtonePickContract(@StringRes private val titleResId: Int) : ActivityResultContract<Uri?, Uri?>() {
@@ -30,6 +31,6 @@ class RingtonePickContract(@StringRes private val titleResId: Int) : ActivityRes
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
return intent?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
return intent?.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.domain
package org.koitharu.kotatsu.shelf.domain
import javax.inject.Inject
import kotlinx.coroutines.flow.*
@@ -10,7 +10,7 @@ import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
import org.koitharu.kotatsu.parsers.model.Manga
class LibraryRepository @Inject constructor(
class ShelfRepository @Inject constructor(
private val db: MangaDatabase,
) {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui
package org.koitharu.kotatsu.shelf.ui
import android.os.Bundle
import android.view.LayoutInflater
@@ -19,13 +19,13 @@ import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.FragmentLibraryBinding
import org.koitharu.kotatsu.databinding.FragmentShelfBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
import org.koitharu.kotatsu.history.ui.HistoryActivity
import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter
import org.koitharu.kotatsu.library.ui.adapter.LibraryListEventListener
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.shelf.ui.adapter.ShelfAdapter
import org.koitharu.kotatsu.shelf.ui.adapter.ShelfListEventListener
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
@@ -34,10 +34,10 @@ import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
@AndroidEntryPoint
class LibraryFragment :
BaseFragment<FragmentLibraryBinding>(),
class ShelfFragment :
BaseFragment<FragmentShelfBinding>(),
RecyclerViewOwner,
LibraryListEventListener {
ShelfListEventListener {
@Inject
lateinit var coil: ImageLoader
@@ -45,15 +45,15 @@ class LibraryFragment :
@Inject
lateinit var settings: AppSettings
private val viewModel by viewModels<LibraryViewModel>()
private var adapter: LibraryAdapter? = null
private var selectionController: SectionedSelectionController<LibrarySectionModel>? = null
private val viewModel by viewModels<ShelfViewModel>()
private var adapter: ShelfAdapter? = null
private var selectionController: SectionedSelectionController<ShelfSectionModel>? = null
override val recyclerView: RecyclerView
get() = binding.recyclerView
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): FragmentLibraryBinding {
return FragmentLibraryBinding.inflate(inflater, container, false)
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): FragmentShelfBinding {
return FragmentShelfBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -62,9 +62,9 @@ class LibraryFragment :
selectionController = SectionedSelectionController(
activity = requireActivity(),
owner = this,
callback = LibrarySelectionCallback(binding.recyclerView, childFragmentManager, viewModel),
callback = ShelfSelectionCallback(binding.recyclerView, childFragmentManager, viewModel),
)
adapter = LibraryAdapter(
adapter = ShelfAdapter(
lifecycleOwner = viewLifecycleOwner,
coil = coil,
listener = this,
@@ -73,7 +73,7 @@ class LibraryFragment :
)
binding.recyclerView.adapter = adapter
binding.recyclerView.setHasFixedSize(true)
addMenuProvider(LibraryMenuProvider(view.context, childFragmentManager, viewModel))
addMenuProvider(ShelfMenuProvider(view.context, childFragmentManager, viewModel))
viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError)
@@ -86,22 +86,22 @@ class LibraryFragment :
selectionController = null
}
override fun onItemClick(item: Manga, section: LibrarySectionModel, view: View) {
override fun onItemClick(item: Manga, section: ShelfSectionModel, view: View) {
if (selectionController?.onItemClick(section, item.id) != true) {
val intent = DetailsActivity.newIntent(view.context, item)
startActivity(intent)
}
}
override fun onItemLongClick(item: Manga, section: LibrarySectionModel, view: View): Boolean {
override fun onItemLongClick(item: Manga, section: ShelfSectionModel, view: View): Boolean {
return selectionController?.onItemLongClick(section, item.id) ?: false
}
override fun onSectionClick(section: LibrarySectionModel, view: View) {
override fun onSectionClick(section: ShelfSectionModel, view: View) {
selectionController?.clear()
val intent = when (section) {
is LibrarySectionModel.History -> HistoryActivity.newIntent(view.context)
is LibrarySectionModel.Favourites -> FavouritesActivity.newIntent(view.context, section.category)
is ShelfSectionModel.History -> HistoryActivity.newIntent(view.context)
is ShelfSectionModel.Favourites -> FavouritesActivity.newIntent(view.context, section.category)
}
startActivity(intent)
}
@@ -143,6 +143,6 @@ class LibraryFragment :
companion object {
fun newInstance() = LibraryFragment()
fun newInstance() = ShelfFragment()
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui
package org.koitharu.kotatsu.shelf.ui
import android.content.Context
import android.view.Menu
@@ -12,19 +12,19 @@ import java.util.*
import java.util.concurrent.TimeUnit
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.RememberSelectionDialogListener
import org.koitharu.kotatsu.library.ui.config.categories.LibraryCategoriesConfigSheet
import org.koitharu.kotatsu.library.ui.config.size.LibrarySizeBottomSheet
import org.koitharu.kotatsu.shelf.ui.config.categories.ShelfCategoriesConfigSheet
import org.koitharu.kotatsu.shelf.ui.config.size.ShelfSizeBottomSheet
import org.koitharu.kotatsu.local.ui.ImportDialogFragment
import org.koitharu.kotatsu.utils.ext.startOfDay
class LibraryMenuProvider(
class ShelfMenuProvider(
private val context: Context,
private val fragmentManager: FragmentManager,
private val viewModel: LibraryViewModel,
private val viewModel: ShelfViewModel,
) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_library, menu)
menuInflater.inflate(R.menu.opt_shelf, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
@@ -34,7 +34,7 @@ class LibraryMenuProvider(
true
}
R.id.action_grid_size -> {
LibrarySizeBottomSheet.show(fragmentManager)
ShelfSizeBottomSheet.show(fragmentManager)
true
}
R.id.action_import -> {
@@ -42,7 +42,7 @@ class LibraryMenuProvider(
true
}
R.id.action_categories -> {
LibraryCategoriesConfigSheet.show(fragmentManager)
ShelfCategoriesConfigSheet.show(fragmentManager)
true
}
else -> false

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui
package org.koitharu.kotatsu.shelf.ui
import android.content.Context
import android.view.Menu
@@ -11,33 +11,33 @@ import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.flattenTo
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
class LibrarySelectionCallback(
class ShelfSelectionCallback(
private val recyclerView: RecyclerView,
private val fragmentManager: FragmentManager,
private val viewModel: LibraryViewModel,
) : SectionedSelectionController.Callback<LibrarySectionModel> {
private val viewModel: ShelfViewModel,
) : SectionedSelectionController.Callback<ShelfSectionModel> {
private val context: Context
get() = recyclerView.context
override fun onCreateActionMode(
controller: SectionedSelectionController<LibrarySectionModel>,
controller: SectionedSelectionController<ShelfSectionModel>,
mode: ActionMode,
menu: Menu,
): Boolean {
mode.menuInflater.inflate(R.menu.mode_library, menu)
mode.menuInflater.inflate(R.menu.mode_shelf, menu)
return true
}
override fun onPrepareActionMode(
controller: SectionedSelectionController<LibrarySectionModel>,
controller: SectionedSelectionController<ShelfSectionModel>,
mode: ActionMode,
menu: Menu,
): Boolean {
@@ -47,7 +47,7 @@ class LibrarySelectionCallback(
}
override fun onActionItemClicked(
controller: SectionedSelectionController<LibrarySectionModel>,
controller: SectionedSelectionController<ShelfSectionModel>,
mode: ActionMode,
item: MenuItem,
): Boolean {
@@ -70,8 +70,8 @@ class LibrarySelectionCallback(
R.id.action_remove -> {
val (group, ids) = controller.snapshot().entries.singleOrNull { it.value.isNotEmpty() } ?: return false
when (group) {
is LibrarySectionModel.Favourites -> viewModel.removeFromFavourites(group.category, ids)
is LibrarySectionModel.History -> viewModel.removeFromHistory(ids)
is ShelfSectionModel.Favourites -> viewModel.removeFromFavourites(group.category, ids)
is ShelfSectionModel.History -> viewModel.removeFromHistory(ids)
}
mode.finish()
true
@@ -80,18 +80,18 @@ class LibrarySelectionCallback(
}
}
override fun onSelectionChanged(controller: SectionedSelectionController<LibrarySectionModel>, count: Int) {
override fun onSelectionChanged(controller: SectionedSelectionController<ShelfSectionModel>, count: Int) {
recyclerView.invalidateNestedItemDecorations()
}
override fun onCreateItemDecoration(
controller: SectionedSelectionController<LibrarySectionModel>,
section: LibrarySectionModel,
controller: SectionedSelectionController<ShelfSectionModel>,
section: ShelfSectionModel,
): AbstractSelectionItemDecoration = MangaSelectionDecoration(context)
private fun collectSelectedItemsMap(
controller: SectionedSelectionController<LibrarySectionModel>,
): Map<LibrarySectionModel, Set<Manga>> {
controller: SectionedSelectionController<ShelfSectionModel>,
): Map<ShelfSectionModel, Set<Manga>> {
val snapshot = controller.peekCheckedIds()
if (snapshot.isEmpty()) {
return emptyMap()
@@ -100,7 +100,7 @@ class LibrarySelectionCallback(
}
private fun collectSelectedItems(
controller: SectionedSelectionController<LibrarySectionModel>,
controller: SectionedSelectionController<ShelfSectionModel>,
): Set<Manga> {
val snapshot = controller.peekCheckedIds()
if (snapshot.isEmpty()) {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui
package org.koitharu.kotatsu.shelf.ui
import androidx.collection.ArraySet
import androidx.lifecycle.LiveData
@@ -20,8 +20,8 @@ import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.library.domain.LibraryRepository
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.shelf.domain.ShelfRepository
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.parsers.model.Manga
@@ -33,8 +33,8 @@ import org.koitharu.kotatsu.utils.ext.daysDiff
private const val HISTORY_MAX_SEGMENTS = 2
@HiltViewModel
class LibraryViewModel @Inject constructor(
repository: LibraryRepository,
class ShelfViewModel @Inject constructor(
repository: ShelfRepository,
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val trackingRepository: TrackingRepository,
@@ -101,7 +101,7 @@ class LibraryViewModel @Inject constructor(
val snapshot = content.value ?: return emptySet()
val result = ArraySet<Manga>(ids.size)
for (section in snapshot) {
if (section !is LibrarySectionModel) {
if (section !is ShelfSectionModel) {
continue
}
for (item in section.items) {
@@ -140,7 +140,7 @@ class LibraryViewModel @Inject constructor(
}
private suspend fun mapHistory(
destination: MutableList<in LibrarySectionModel.History>,
destination: MutableList<in ShelfSectionModel.History>,
list: List<MangaWithHistory>,
) {
val showPercent = settings.isReadingIndicatorsEnabled
@@ -151,7 +151,7 @@ class LibraryViewModel @Inject constructor(
groups[groups.keys.last()]?.addAll(subList)
}
for ((timeAgo, subList) in groups) {
destination += LibrarySectionModel.History(
destination += ShelfSectionModel.History(
items = subList.map { (manga, history) ->
val counter = trackingRepository.getNewChaptersCount(manga.id)
val percent = if (showPercent) history.percent else PROGRESS_NONE
@@ -164,12 +164,12 @@ class LibraryViewModel @Inject constructor(
}
private suspend fun mapFavourites(
destination: MutableList<in LibrarySectionModel.Favourites>,
destination: MutableList<in ShelfSectionModel.Favourites>,
favourites: Map<FavouriteCategory, List<Manga>>,
) {
for ((category, list) in favourites) {
if (list.isNotEmpty()) {
destination += LibrarySectionModel.Favourites(
destination += ShelfSectionModel.Favourites(
items = list.toUi(ListMode.GRID, this),
category = category,
showAllButtonText = R.string.show_all,

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.adapter
package org.koitharu.kotatsu.shelf.ui.adapter
import androidx.recyclerview.widget.DiffUtil
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
@@ -27,4 +27,4 @@ class MangaItemDiffCallback : DiffUtil.ItemCallback<ListModel>() {
else -> super.getChangePayload(oldItem, newItem)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.adapter
package org.koitharu.kotatsu.shelf.ui.adapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.adapter
package org.koitharu.kotatsu.shelf.ui.adapter
import android.content.Context
import androidx.lifecycle.LifecycleOwner
@@ -9,7 +9,7 @@ import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import kotlin.jvm.internal.Intrinsics
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD
@@ -17,19 +17,19 @@ import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
class LibraryAdapter(
class ShelfAdapter(
lifecycleOwner: LifecycleOwner,
coil: ImageLoader,
listener: LibraryListEventListener,
listener: ShelfListEventListener,
sizeResolver: ItemSizeResolver,
selectionController: SectionedSelectionController<LibrarySectionModel>,
selectionController: SectionedSelectionController<ShelfSectionModel>,
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()), FastScroller.SectionIndexer {
init {
val pool = RecyclerView.RecycledViewPool()
delegatesManager
.addDelegate(
libraryGroupAD(
shelfGroupAD(
sharedPool = pool,
lifecycleOwner = lifecycleOwner,
coil = coil,
@@ -45,7 +45,7 @@ class LibraryAdapter(
}
override fun getSectionText(context: Context, position: Int): CharSequence {
val item = items.getOrNull(position) as? LibrarySectionModel
val item = items.getOrNull(position) as? ShelfSectionModel
return item?.getTitle(context.resources) ?: ""
}
@@ -53,7 +53,7 @@ class LibraryAdapter(
override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
return when {
oldItem is LibrarySectionModel && newItem is LibrarySectionModel -> {
oldItem is ShelfSectionModel && newItem is ShelfSectionModel -> {
oldItem.key == newItem.key
}
else -> oldItem.javaClass == newItem.javaClass
@@ -66,7 +66,7 @@ class LibraryAdapter(
override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? {
return when {
oldItem is LibrarySectionModel && newItem is LibrarySectionModel -> Unit
oldItem is ShelfSectionModel && newItem is ShelfSectionModel -> Unit
else -> super.getChangePayload(oldItem, newItem)
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.adapter
package org.koitharu.kotatsu.shelf.ui.adapter
import android.view.View
import androidx.lifecycle.LifecycleOwner
@@ -12,7 +12,7 @@ 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.databinding.ItemListGroupBinding
import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -20,14 +20,14 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.removeItemDecoration
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
fun libraryGroupAD(
fun shelfGroupAD(
sharedPool: RecyclerView.RecycledViewPool,
lifecycleOwner: LifecycleOwner,
coil: ImageLoader,
sizeResolver: ItemSizeResolver,
selectionController: SectionedSelectionController<LibrarySectionModel>,
listener: LibraryListEventListener,
) = adapterDelegateViewBinding<LibrarySectionModel, ListModel, ItemListGroupBinding>(
selectionController: SectionedSelectionController<ShelfSectionModel>,
listener: ShelfListEventListener,
) = adapterDelegateViewBinding<ShelfSectionModel, ListModel, ItemListGroupBinding>(
{ layoutInflater, parent -> ItemListGroupBinding.inflate(layoutInflater, parent, false) },
) {
val listenerAdapter = object : OnListItemClickListener<Manga>, View.OnClickListener {

View File

@@ -0,0 +1,15 @@
package org.koitharu.kotatsu.shelf.ui.adapter
import android.view.View
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.parsers.model.Manga
interface ShelfListEventListener : ListStateHolderListener {
fun onItemClick(item: Manga, section: ShelfSectionModel, view: View)
fun onItemLongClick(item: Manga, section: ShelfSectionModel, view: View): Boolean
fun onSectionClick(section: ShelfSectionModel, view: View)
}

View File

@@ -1,16 +1,16 @@
package org.koitharu.kotatsu.library.ui.config.categories
package org.koitharu.kotatsu.shelf.ui.config.categories
import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
class LibraryCategoriesConfigAdapter(
class ShelfCategoriesConfigAdapter(
listener: OnListItemClickListener<FavouriteCategory>,
) : AsyncListDifferDelegationAdapter<FavouriteCategory>(DiffCallback()) {
init {
delegatesManager.addDelegate(libraryCategoryAD(listener))
delegatesManager.addDelegate(shelfCategoryAD(listener))
}
class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.config.categories
package org.koitharu.kotatsu.shelf.ui.config.categories
import android.os.Bundle
import android.view.LayoutInflater
@@ -15,12 +15,12 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.SheetBaseBinding
@AndroidEntryPoint
class LibraryCategoriesConfigSheet :
class ShelfCategoriesConfigSheet :
BaseBottomSheet<SheetBaseBinding>(),
OnListItemClickListener<FavouriteCategory>,
View.OnClickListener {
private val viewModel by viewModels<LibraryCategoriesConfigViewModel>()
private val viewModel by viewModels<ShelfCategoriesConfigViewModel>()
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetBaseBinding {
return SheetBaseBinding.inflate(inflater, container, false)
@@ -31,7 +31,7 @@ class LibraryCategoriesConfigSheet :
binding.headerBar.toolbar.setTitle(R.string.favourites_categories)
binding.buttonDone.isVisible = true
binding.buttonDone.setOnClickListener(this)
val adapter = LibraryCategoriesConfigAdapter(this)
val adapter = ShelfCategoriesConfigAdapter(this)
binding.recyclerView.adapter = adapter
viewModel.content.observe(viewLifecycleOwner) { adapter.items = it }
@@ -47,8 +47,8 @@ class LibraryCategoriesConfigSheet :
companion object {
private const val TAG = "LibraryCategoriesConfigSheet"
private const val TAG = "ShelfCategoriesConfigSheet"
fun show(fm: FragmentManager) = LibraryCategoriesConfigSheet().show(fm, TAG)
fun show(fm: FragmentManager) = ShelfCategoriesConfigSheet().show(fm, TAG)
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.config.categories
package org.koitharu.kotatsu.shelf.ui.config.categories
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
@HiltViewModel
class LibraryCategoriesConfigViewModel @Inject constructor(
class ShelfCategoriesConfigViewModel @Inject constructor(
private val favouritesRepository: FavouritesRepository,
) : BaseViewModel() {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.config.categories
package org.koitharu.kotatsu.shelf.ui.config.categories
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ItemCategoryCheckableMultipleBinding
fun libraryCategoryAD(
fun shelfCategoryAD(
listener: OnListItemClickListener<FavouriteCategory>,
) = adapterDelegateViewBinding<FavouriteCategory, FavouriteCategory, ItemCategoryCheckableMultipleBinding>(
{ layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) },

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.config.size
package org.koitharu.kotatsu.shelf.ui.config.size
import android.os.Bundle
import android.view.LayoutInflater
@@ -12,13 +12,13 @@ import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.SheetLibrarySizeBinding
import org.koitharu.kotatsu.databinding.SheetShelfSizeBinding
import org.koitharu.kotatsu.utils.ext.setValueRounded
import org.koitharu.kotatsu.utils.progress.IntPercentLabelFormatter
@AndroidEntryPoint
class LibrarySizeBottomSheet :
BaseBottomSheet<SheetLibrarySizeBinding>(),
class ShelfSizeBottomSheet :
BaseBottomSheet<SheetShelfSizeBinding>(),
Slider.OnChangeListener,
View.OnClickListener {
@@ -26,8 +26,8 @@ class LibrarySizeBottomSheet :
lateinit var settings: AppSettings
private var labelFormatter: LabelFormatter? = null
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetLibrarySizeBinding {
return SheetLibrarySizeBinding.inflate(inflater, container, false)
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetShelfSizeBinding {
return SheetShelfSizeBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -59,8 +59,8 @@ class LibrarySizeBottomSheet :
companion object {
private const val TAG = "LibrarySizeBottomSheet"
private const val TAG = "ShelfSizeBottomSheet"
fun show(fm: FragmentManager) = LibrarySizeBottomSheet().show(fm, TAG)
fun show(fm: FragmentManager) = ShelfSizeBottomSheet().show(fm, TAG)
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.library.ui.model
package org.koitharu.kotatsu.shelf.ui.model
import android.content.res.Resources
import androidx.annotation.StringRes
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
sealed class LibrarySectionModel(
sealed class ShelfSectionModel(
val items: List<MangaItemModel>,
@StringRes val showAllButtonText: Int,
) : ListModel {
@@ -20,7 +20,7 @@ sealed class LibrarySectionModel(
items: List<MangaItemModel>,
val timeAgo: DateTimeAgo?,
showAllButtonText: Int,
) : LibrarySectionModel(items, showAllButtonText) {
) : ShelfSectionModel(items, showAllButtonText) {
override val key: Any
get() = timeAgo?.javaClass ?: this::class.java
@@ -58,7 +58,7 @@ sealed class LibrarySectionModel(
items: List<MangaItemModel>,
val category: FavouriteCategory,
showAllButtonText: Int,
) : LibrarySectionModel(items, showAllButtonText) {
) : ShelfSectionModel(items, showAllButtonText) {
override val key: Any
get() = category.id

View File

@@ -26,6 +26,7 @@ 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
@@ -137,7 +138,7 @@ class SuggestionsWorker @AssistedInject constructor(
return (weight / maxWeight).pow(2.0).toFloat()
}
private suspend fun MangaRepository.getListSafe(tag: MangaTag) = runCatching {
private suspend fun MangaRepository.getListSafe(tag: MangaTag) = runCatchingCancellable {
getList(offset = 0, sortOrder = SortOrder.UPDATED, tags = setOf(tag))
}.onFailure { error ->
error.printStackTraceDebug()

View File

@@ -35,4 +35,4 @@ class SyncAuthenticator(
)
}
}.getOrNull()
}
}

View File

@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivitySyncAuthBinding
import org.koitharu.kotatsu.sync.domain.SyncAuthResult
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
@AndroidEntryPoint
class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickListener {
@@ -33,7 +34,8 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivitySyncAuthBinding.inflate(layoutInflater))
accountAuthenticatorResponse = intent.getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)
accountAuthenticatorResponse =
intent.getParcelableExtraCompat(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)
accountAuthenticatorResponse?.onRequestContinued()
binding.buttonCancel.setOnClickListener(this)
binding.buttonNext.setOnClickListener(this)
@@ -57,6 +59,7 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
)
}
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
override fun onBackPressed() {
if (binding.switcher.isVisible && binding.switcher.displayedChild > 0) {
binding.switcher.showPrevious()

View File

@@ -9,6 +9,7 @@ import android.os.Bundle
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.sync.domain.SyncHelper
import org.koitharu.kotatsu.utils.ext.onError
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) {
@@ -20,9 +21,9 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont
syncResult: SyncResult,
) {
val syncHelper = SyncHelper(context, account, provider)
runCatching {
runCatchingCancellable {
syncHelper.syncFavourites(syncResult)
SyncController(context).setLastSync(account, authority, System.currentTimeMillis())
}.onFailure(syncResult::onError)
}
}
}

View File

@@ -9,6 +9,7 @@ import android.os.Bundle
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.sync.domain.SyncHelper
import org.koitharu.kotatsu.utils.ext.onError
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) {
@@ -20,9 +21,9 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context
syncResult: SyncResult,
) {
val syncHelper = SyncHelper(context, account, provider)
runCatching {
runCatchingCancellable {
syncHelper.syncHistory(syncResult)
SyncController(context).setLastSync(account, authority, System.currentTimeMillis())
}.onFailure(syncResult::onError)
}
}
}

View File

@@ -28,6 +28,7 @@ import org.koitharu.kotatsu.tracker.domain.Tracker
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
import org.koitharu.kotatsu.utils.PendingIntentCompat
import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
import org.koitharu.kotatsu.utils.ext.trySetForeground
@@ -82,7 +83,7 @@ class TrackWorker @AssistedInject constructor(
val deferredList = coroutineScope {
tracks.map { (track, channelId) ->
async(dispatcher) {
runCatching {
runCatchingCancellable {
tracker.fetchUpdates(track, commit = true)
}.onSuccess { updates ->
if (updates.isValid && updates.isNotEmpty()) {

View File

@@ -1,50 +0,0 @@
package org.koitharu.kotatsu.utils
import androidx.annotation.MainThread
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Runnable
class PausingDispatcher(
private val dispatcher: CoroutineDispatcher,
) : CoroutineDispatcher() {
@Volatile
private var isPaused = false
private val queue = ConcurrentLinkedQueue<Task>()
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return isPaused || super.isDispatchNeeded(context)
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
if (isPaused) {
queue.add(Task(context, block))
} else {
dispatcher.dispatch(context, block)
}
}
@MainThread
fun pause() {
isPaused = true
}
@MainThread
fun resume() {
if (!isPaused) {
return
}
isPaused = false
while (true) {
val task = queue.poll() ?: break
dispatcher.dispatch(task.context, task.block)
}
}
private class Task(
val context: CoroutineContext,
val block: Runnable,
)
}

View File

@@ -24,7 +24,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.work.CoroutineWorker
import com.google.android.material.elevation.ElevationOverlayProvider
import kotlin.math.roundToLong
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.delay
@@ -37,6 +36,7 @@ import okio.IOException
import org.json.JSONException
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.utils.InternalResourceHelper
import kotlin.math.roundToLong
val Context.activityManager: ActivityManager?
get() = getSystemService(ACTIVITY_SERVICE) as? ActivityManager
@@ -46,7 +46,7 @@ val Context.connectivityManager: ConnectivityManager
fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this)
suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatching {
suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatchingCancellable {
val info = getForegroundInfo()
setForeground(info)
}.isSuccess
@@ -95,6 +95,7 @@ fun SyncResult.onError(error: Throwable) {
is OperationApplicationException,
is SQLException,
-> databaseError = true
is JSONException -> stats.numParseExceptions++
else -> if (BuildConfig.DEBUG) throw error
}

View File

@@ -1,47 +1,41 @@
@file:Suppress("DEPRECATION")
package org.koitharu.kotatsu.utils.ext
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags
import java.io.Serializable
@Suppress("DEPRECATION")
// https://issuetracker.google.com/issues/240585930
inline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String): T? {
if (!containsKey(key)) {
return null
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelable(key, T::class.java)
} else {
getParcelable(key) as? T
}
return getParcelable(key) as T?
}
@Suppress("DEPRECATION")
inline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String): T? {
if (!hasExtra(key)) {
return null
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableExtra(key, T::class.java)
} else {
getParcelableExtra(key) as? T
}
return getParcelableExtra(key) as T?
}
@Suppress("DEPRECATION")
inline fun <reified T : Serializable> Bundle.getSerializableCompat(key: String): T? {
if (!containsKey(key)) {
return null
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializable(key, T::class.java)
} else {
getSerializable(key) as? T
getSerializable(key) as T?
}
}
inline fun <reified T : Parcelable> Parcel.readParcelableCompat(): T? {
return readParcelable(ParcelableMangaTags::class.java.classLoader) as T?
}
inline fun <reified T : Serializable> Parcel.readSerializableCompat(): T? {
return readSerializable() as T?
}
inline fun <reified T : Serializable> Bundle.requireSerializable(key: String): T {
return checkNotNull(getSerializableCompat(key)) {
"Serializable of type \"${T::class.java.name}\" not found at \"$key\""

View File

@@ -3,16 +3,22 @@ package org.koitharu.kotatsu.utils.ext
import android.content.ActivityNotFoundException
import android.content.res.Resources
import androidx.collection.arraySetOf
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import kotlinx.coroutines.CancellationException
import okio.FileNotFoundException
import org.acra.ktx.sendWithAcra
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.*
import org.koitharu.kotatsu.core.exceptions.CaughtException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.exceptions.SyncApiException
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.exception.ParseException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is AuthRequiredException -> resources.getString(R.string.auth_required)
@@ -20,16 +26,19 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is ActivityNotFoundException,
is UnsupportedOperationException,
-> resources.getString(R.string.operation_not_supported)
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
is FileNotFoundException -> resources.getString(R.string.file_not_found)
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
is SyncApiException,
is ContentUnavailableException,
-> message
is ParseException -> shortMessage
is UnknownHostException,
is SocketTimeoutException,
-> resources.getString(R.string.network_error)
is WrongPasswordException -> resources.getString(R.string.wrong_password)
is NotFoundException -> resources.getString(R.string.not_found_404)
else -> localizedMessage
@@ -52,3 +61,15 @@ private val reportableExceptions = arraySetOf<Class<*>>(
ConcurrentModificationException::class.java,
UnsupportedOperationException::class.java,
)
inline fun <R> runCatchingCancellable(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (e: InterruptedException) {
throw e
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
Result.failure(e)
}
}

View File

@@ -15,8 +15,9 @@
<org.koitharu.kotatsu.reader.ui.ReaderInfoBarView
android:id="@+id/infoBar"
android:layout_width="match_parent"
android:layout_height="16dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:minHeight="16dp"
android:paddingHorizontal="6dp"
android:visibility="gone"
tools:visibility="visible" />

View File

@@ -6,4 +6,4 @@
android:layout_width="@dimen/widget_cover_width"
android:layout_height="@dimen/widget_cover_height"
android:scaleType="centerCrop"
tools:ignore="ContentDescription" />
tools:ignore="ContentDescription" />

View File

@@ -23,7 +23,8 @@
android:layout_width="@dimen/widget_cover_width"
android:layout_height="@dimen/widget_cover_height"
android:scaleType="centerCrop"
tools:ignore="ContentDescription" />
tools:ignore="ContentDescription"
tools:src="@sample/covers" />
<TextView
android:id="@+id/textView_title"
@@ -32,8 +33,10 @@
android:elegantTextHeight="false"
android:ellipsize="end"
android:lines="2"
android:textColor="?android:attr/textColorPrimary" />
android:padding="2dp"
android:textColor="?android:attr/textColorPrimary"
tools:text="@sample/titles" />
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_library"
android:id="@+id/nav_shelf"
android:icon="@drawable/ic_bookshelf_selector"
android:title="@string/manga_shelf" />

View File

@@ -372,4 +372,18 @@
<string name="import_completed_hint">Puedes eliminar el archivo original del almacenamiento para ahorrar espacio</string>
<string name="select_range">Selecciona el rango</string>
<string name="not_found_404">Contenido no encontrado o eliminado</string>
<string name="manga_error_description_pattern">Detalles del error:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Intenta &lt;a href=%2$s&gt;abrir el manga en un navegador web&lt;/a&gt; para asegurarte de que está disponible en tu fuente&lt;br&gt;2. Si está disponible, envía un informe de error a los desarrolladores.</string>
<string name="history_shortcuts_summary">Hacer que los mangas recientes estén disponibles mediante una pulsación larga en el icono de la aplicación</string>
<string name="color_correction_hint">Los ajustes de color elegidos serán recordados para este manga</string>
<string name="feed">Feed</string>
<string name="downloading_manga">Descargando manga</string>
<string name="history_shortcuts">Mostrar los accesos directos a los mangas recientes</string>
<string name="reader_control_ltr_summary">Tocando el borde derecho o pulsando la tecla derecha se pasa siempre a la página siguiente</string>
<string name="reader_control_ltr">Control ergonómico del lector</string>
<string name="color_correction">Corrección del color</string>
<string name="brightness">Brillo</string>
<string name="contrast">Contraste</string>
<string name="reset">Restablecer</string>
<string name="text_unsaved_changes_prompt">Tienes cambios sin guardar. ¿Quieres guardarlos o descartarlos\?</string>
<string name="discard">Descartar</string>
</resources>

View File

@@ -41,4 +41,9 @@
<item name="android:textColorHighlightInverse">@color/m3_dynamic_highlighted_text</item>
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_dark_default_color_primary_text</item>
</style>
</resources>
<style name="Theme.Kotatsu.AppWidgetContainer" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="android:colorBackground">@android:color/system_accent1_900</item>
<item name="android:panelColorBackground">@android:color/system_accent1_800</item>
</style>
</resources>

View File

@@ -47,4 +47,4 @@
<item name="android:panelColorBackground">@android:color/system_accent1_100</item>
</style>
</resources>
</resources>

View File

@@ -60,9 +60,9 @@
<string name="read">阅读</string>
<string name="you_have_not_favourites_yet">尚无收藏夹</string>
<string name="add_to_favourites">收藏这个</string>
<string name="add_new_category">新类别</string>
<string name="add_new_category">新分类</string>
<string name="add">添加</string>
<string name="enter_category_name">输入类别名称</string>
<string name="enter_category_name">输入分类名称</string>
<string name="save">保存</string>
<string name="share">分享</string>
<string name="create_shortcut">创建快捷方式…</string>
@@ -91,7 +91,7 @@
<string name="save_page">保存页面</string>
<string name="page_saved">保存</string>
<string name="share_image">分享图片</string>
<string name="_import">输入</string>
<string name="_import">导入</string>
<string name="delete">删除</string>
<string name="operation_not_supported">不支持此操作</string>
<string name="text_file_not_supported">选择 ZIP 或 CBZ 文件.</string>
@@ -139,20 +139,20 @@
<string name="notification_sound">通知声音</string>
<string name="light_indicator">LED指示器</string>
<string name="vibration">振动</string>
<string name="favourites_categories">收藏夹类别</string>
<string name="categories_">类别…</string>
<string name="favourites_categories">收藏夹分类</string>
<string name="categories_">分类…</string>
<string name="rename">重命名</string>
<string name="category_delete_confirm">将\"%s\"类别从你的收藏夹中删除?
<string name="category_delete_confirm">将\"%s\"分类从你的收藏夹中删除?
\n里面的所有漫画都会丢失。</string>
<string name="remove_category">删除</string>
<string name="text_empty_holder_primary">这里有点空…</string>
<string name="text_categories_holder">您可以使用类别来组织您的收藏夹。按«+»创建一个类别</string>
<string name="text_categories_holder">您可以使用类别来组织您的收藏夹。按«+»创建一个分类</string>
<string name="text_search_holder_secondary">尝试重新表述查询。</string>
<string name="text_history_holder_primary">你所读的内容将在这里显示</string>
<string name="text_history_holder_secondary">在侧面菜单中找到要读的内容.</string>
<string name="text_local_holder_primary">先保存内容</string>
<string name="text_local_holder_secondary">从在线来源保存或导入文件.</string>
<string name="manga_shelf">架子</string>
<string name="manga_shelf">书架</string>
<string name="recent_manga">最近</string>
<string name="pages_animation">页面动画</string>
<string name="manga_save_location">下载文件夹</string>
@@ -161,7 +161,7 @@
<string name="other_storage">其他存储</string>
<string name="done">完成</string>
<string name="all_favourites">所有收藏夹</string>
<string name="favourites_category_empty">空类别</string>
<string name="favourites_category_empty">空分类</string>
<string name="read_later">稍后阅读</string>
<string name="updates">更新</string>
<string name="text_feed_holder">你正在阅读的新章节显示在这里</string>
@@ -189,7 +189,7 @@
<string name="update_check_failed">无法查找更新</string>
<string name="no_update_available">没有更新</string>
<string name="right_to_left">从右到左</string>
<string name="create_category">新类别</string>
<string name="create_category">新分类</string>
<string name="scale_mode">缩放模式</string>
<string name="zoom_mode_fit_center">适应中心</string>
<string name="zoom_mode_fit_height">适应高度</string>
@@ -278,8 +278,8 @@
<string name="notifications_enable">启用通知</string>
<string name="name">命名</string>
<string name="edit">编辑</string>
<string name="edit_category">编辑类别</string>
<string name="empty_favourite_categories">没有收藏夹类别</string>
<string name="edit_category">编辑分类</string>
<string name="empty_favourite_categories">没有收藏夹分类</string>
<string name="bookmark_add">添加书签</string>
<string name="bookmark_remove">删除书签</string>
<string name="bookmarks">书签</string>
@@ -376,4 +376,13 @@
<string name="not_found_404">内容未找到或已移除</string>
<string name="reader_control_ltr_summary">点击屏幕右侧或按下右键翻到下一页</string>
<string name="reader_control_ltr">高效阅读器控制</string>
<string name="history_shortcuts_summary">长按应用图标显示最近阅读的漫画</string>
<string name="history_shortcuts">显示最近阅读漫画的快捷方式</string>
<string name="reset">重置</string>
<string name="color_correction">颜色校正</string>
<string name="brightness">亮度</string>
<string name="contrast">对比度</string>
<string name="color_correction_hint">所选颜色设置将会应用于此漫画</string>
<string name="text_unsaved_changes_prompt">您有未保存的更改,您希望存储还是放弃更改?</string>
<string name="discard">放弃</string>
</resources>

View File

@@ -29,6 +29,7 @@
<dimen name="widget_cover_width">84dp</dimen>
<dimen name="reading_progress_stroke">4dp</dimen>
<dimen name="reading_progress_text_size">10dp</dimen>
<dimen name="reader_bar_inset_fallback">8dp</dimen>
<dimen name="scrobbling_list_spacing">12dp</dimen>
<dimen name="search_suggestions_manga_height">124dp</dimen>

View File

@@ -5,9 +5,9 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42'
classpath 'com.android.tools.build:gradle:7.3.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.43.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -36,7 +36,7 @@ Object localProperty(String name, Object defaultValue = 'null') {
String currentBranch() {
def branchName = ""
try {
branchName = "git rev-parse --abbrev-ref HEAD".execute().text.trim();
branchName = "git rev-parse --abbrev-ref HEAD".execute().text.trim()
} catch (ignored) {
println "Git not found"
}