Compare commits
26 Commits
v4.0-a6
...
v4.0-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
| 734765dbdd | |||
|
|
117c4c5978 | ||
|
|
13e33a6614 | ||
|
|
affb495136 | ||
|
|
9b2dafd668 | ||
| 5f32c0401f | |||
| f35f40ed27 | |||
| 46f0d3ef74 | |||
| c27c785ac2 | |||
| 6e844e8c3b | |||
| 4186c36f30 | |||
| 2d727a0da8 | |||
| 39e574e9dc | |||
| efb94cbd67 | |||
| 757e33dfb4 | |||
| ab9bdf9f07 | |||
| a68632a888 | |||
| 9b0dc8b413 | |||
| f43fe5830e | |||
| ce616b328c | |||
| 824a8ff97a | |||
|
|
ef9018b92f | ||
|
|
a088d10935 | ||
| 2e561697ac | |||
| d242acd502 | |||
| d37b44d3f6 |
6
.idea/ktlint.xml
generated
6
.idea/ktlint.xml
generated
@@ -1,7 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KtlintProjectConfiguration">
|
||||
<enableKtlint>false</enableKtlint>
|
||||
<androidMode>true</androidMode>
|
||||
<treatAsErrors>false</treatAsErrors>
|
||||
<disabledRules>
|
||||
<list>
|
||||
<option value="no-empty-first-line-in-method-block" />
|
||||
</list>
|
||||
</disabledRules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 495
|
||||
versionName '4.0-a6'
|
||||
versionCode 497
|
||||
versionName '4.0-beta1'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -82,13 +82,13 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:551a1d70ae') {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:b3a9c5fcda') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.activity:activity-ktx:1.5.1'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
@@ -122,9 +122,9 @@ dependencies {
|
||||
implementation 'androidx.hilt:hilt-work:1.0.0'
|
||||
kapt 'androidx.hilt:hilt-compiler:1.0.0'
|
||||
|
||||
implementation 'io.coil-kt:coil-base:2.2.0'
|
||||
implementation 'io.coil-kt:coil-svg:2.2.0'
|
||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:2942b797a2'
|
||||
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.solkin:disk-lru-cache:1.4'
|
||||
|
||||
implementation 'ch.acra:acra-http:5.9.6'
|
||||
@@ -144,7 +144,7 @@ dependencies {
|
||||
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
|
||||
|
||||
androidTestImplementation 'androidx.room:room-testing:2.4.3'
|
||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.13.0'
|
||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
|
||||
|
||||
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.43.2'
|
||||
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.43.2'
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.ActionBarContextView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.view.ViewCompat
|
||||
@@ -92,7 +91,8 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // TODO remove
|
||||
ActivityCompat.recreate(this)
|
||||
// ActivityCompat.recreate(this)
|
||||
TODO("Test error")
|
||||
return true
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
|
||||
@@ -60,7 +60,19 @@ class SectionedSelectionController<T : Any>(
|
||||
startActionMode()
|
||||
notifySelectionChanged()
|
||||
}
|
||||
recyclerView.addItemDecoration(decoration)
|
||||
var shouldAddDecoration = true
|
||||
for (i in (0 until recyclerView.itemDecorationCount).reversed()) {
|
||||
val decor = recyclerView.getItemDecorationAt(i)
|
||||
if (decor === decoration) {
|
||||
shouldAddDecoration = false
|
||||
break
|
||||
} else if (decor.javaClass == decoration.javaClass) {
|
||||
recyclerView.removeItemDecorationAt(i)
|
||||
}
|
||||
}
|
||||
if (shouldAddDecoration) {
|
||||
recyclerView.addItemDecoration(decoration)
|
||||
}
|
||||
if (pendingData?.isEmpty() == true) {
|
||||
pendingData = null
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class ScrollbarAnimator(
|
||||
.animate()
|
||||
.translationX(0f)
|
||||
.alpha(1f)
|
||||
.setListener(null)
|
||||
.setDuration(animationDuration)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
@@ -12,7 +13,6 @@ import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup
|
||||
import org.koitharu.kotatsu.list.ui.adapter.*
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class BookmarksGroupAdapter(
|
||||
coil: ImageLoader,
|
||||
@@ -34,11 +34,11 @@ class BookmarksGroupAdapter(
|
||||
selectionController = selectionController,
|
||||
bookmarkClickListener = bookmarkClickListener,
|
||||
groupClickListener = groupClickListener,
|
||||
)
|
||||
),
|
||||
)
|
||||
.addDelegate(loadingStateAD())
|
||||
.addDelegate(loadingFooterAD())
|
||||
.addDelegate(emptyStateListAD(listener))
|
||||
.addDelegate(emptyStateListAD(coil, listener))
|
||||
.addDelegate(errorStateListAD(listener))
|
||||
}
|
||||
|
||||
@@ -64,4 +64,4 @@ class BookmarksGroupAdapter(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||
@@ -17,9 +20,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
|
||||
import org.koitharu.kotatsu.utils.TaggedActivityResult
|
||||
import org.koitharu.kotatsu.utils.isSuccess
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class ExceptionResolver private constructor(
|
||||
private val activity: FragmentActivity?,
|
||||
@@ -65,7 +65,7 @@ class ExceptionResolver private constructor(
|
||||
cont.invokeOnCancellation {
|
||||
continuations.remove(CloudFlareDialog.TAG, cont)
|
||||
fm.clearFragmentResultListener(CloudFlareDialog.TAG)
|
||||
dialog.dismiss()
|
||||
dialog.dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,4 +94,4 @@ class ExceptionResolver private constructor(
|
||||
|
||||
fun canResolve(e: Throwable) = getResolveStringId(e) != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
// Limits to avoid TransactionTooLargeException
|
||||
private const val MAX_SAFE_SIZE = 1024 * 512 // Assume that 512 kb is safe parcel size
|
||||
private const val MAX_SAFE_CHAPTERS_COUNT = 40 // this is 100% safe
|
||||
private const val MAX_SAFE_CHAPTERS_COUNT = 32 // this is 100% safe
|
||||
|
||||
class ParcelableManga(
|
||||
val manga: Manga,
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.media.ThumbnailUtils
|
||||
import android.os.Build
|
||||
import android.util.Size
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -15,6 +14,8 @@ import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.room.InvalidationTracker
|
||||
import coil.ImageLoader
|
||||
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
|
||||
@@ -112,13 +113,14 @@ class ShortcutsUpdater @Inject constructor(
|
||||
|
||||
private suspend fun buildShortcutInfo(manga: Manga): ShortcutInfoCompat.Builder {
|
||||
val icon = runCatching {
|
||||
val bmp = coil.execute(
|
||||
coil.execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(manga.coverUrl)
|
||||
.size(iconSize.width, iconSize.height)
|
||||
.precision(Precision.EXACT)
|
||||
.scale(Scale.FILL)
|
||||
.build(),
|
||||
).requireBitmap()
|
||||
ThumbnailUtils.extractThumbnail(bmp, iconSize.width, iconSize.height, 0)
|
||||
}.fold(
|
||||
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
|
||||
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
|
||||
|
||||
@@ -6,8 +6,6 @@ import androidx.appcompat.view.ActionMode
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -202,18 +200,4 @@ class ChaptersFragment :
|
||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
binding.progressBar.isVisible = isLoading
|
||||
}
|
||||
|
||||
private fun findBottomSheetBehavior(): BottomSheetBehavior<*>? {
|
||||
val v = view ?: return null
|
||||
for (p in v.parents) {
|
||||
val layoutParams = (p as? View)?.layoutParams
|
||||
if (layoutParams is CoordinatorLayout.LayoutParams) {
|
||||
val behavior = layoutParams.behavior
|
||||
if (behavior is BottomSheetBehavior<*>) {
|
||||
return behavior
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ class ChaptersMenuProvider(
|
||||
}
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
menu.findItem(R.id.action_reversed).isChecked = viewModel.isChaptersReversed.value == true
|
||||
menu.findItem(R.id.action_search).isVisible = viewModel.isChaptersEmpty.value == false
|
||||
menu.findItem(R.id.action_reversed)?.isChecked = viewModel.isChaptersReversed.value == true
|
||||
menu.findItem(R.id.action_search)?.isVisible = viewModel.isChaptersEmpty.value == false
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
|
||||
@@ -155,7 +155,7 @@ class DownloadManager @AssistedInject constructor(
|
||||
}
|
||||
outState.value = DownloadState.PostProcessing(startId, data, cover)
|
||||
output.mergeWithExisting()
|
||||
output.finalize()
|
||||
output.finish()
|
||||
val localManga = localMangaRepository.getFromFile(output.file)
|
||||
outState.value = DownloadState.Done(startId, data, cover, localManga)
|
||||
} catch (e: CancellationException) {
|
||||
|
||||
@@ -4,12 +4,12 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
|
||||
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class CategoriesAdapter(
|
||||
coil: ImageLoader,
|
||||
@@ -20,7 +20,7 @@ class CategoriesAdapter(
|
||||
|
||||
init {
|
||||
delegatesManager.addDelegate(categoryAD(coil, lifecycleOwner, onItemClickListener))
|
||||
.addDelegate(emptyStateListAD(listListener))
|
||||
.addDelegate(emptyStateListAD(coil, listListener))
|
||||
.addDelegate(loadingStateAD())
|
||||
}
|
||||
|
||||
@@ -56,4 +56,4 @@ class CategoriesAdapter(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class HistoryListMenuProvider(
|
||||
private val context: Context,
|
||||
@@ -38,6 +38,6 @@ class HistoryListMenuProvider(
|
||||
}
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
menu.findItem(R.id.action_history_grouping).isChecked = viewModel.isGroupingEnabled.value == true
|
||||
menu.findItem(R.id.action_history_grouping)?.isChecked = viewModel.isGroupingEnabled.value == true
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>() {
|
||||
|
||||
private fun setDrawable(drawable: Drawable?) {
|
||||
if (drawable != null) {
|
||||
view.setImage(ImageSource.bitmap(drawable.toBitmap()))
|
||||
view.setImage(ImageSource.Bitmap(drawable.toBitmap()))
|
||||
} else {
|
||||
view.recycle()
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class LibraryAdapter(
|
||||
)
|
||||
.addDelegate(loadingStateAD())
|
||||
.addDelegate(loadingFooterAD())
|
||||
.addDelegate(emptyStateListAD(listener))
|
||||
.addDelegate(emptyStateListAD(coil, listener))
|
||||
.addDelegate(errorStateListAD(listener))
|
||||
}
|
||||
|
||||
|
||||
@@ -55,11 +55,8 @@ fun libraryGroupAD(
|
||||
binding.recyclerView.addItemDecoration(spacingDecoration)
|
||||
binding.buttonMore.setOnClickListener(listenerAdapter)
|
||||
|
||||
bind { payloads ->
|
||||
if (payloads.isEmpty()) {
|
||||
binding.recyclerView.removeItemDecoration(AbstractSelectionItemDecoration::class.java)
|
||||
selectionController.attachToRecyclerView(item, binding.recyclerView)
|
||||
}
|
||||
bind {
|
||||
selectionController.attachToRecyclerView(item, binding.recyclerView)
|
||||
binding.textViewTitle.text = item.getTitle(context.resources)
|
||||
binding.buttonMore.setTextAndVisible(item.showAllButtonText)
|
||||
adapter.items = item.items
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
@file:SuppressLint("UnsafeOptInUsageError")
|
||||
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.badge.BadgeUtils
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
@CheckResult
|
||||
fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {
|
||||
return if (counter > 0) {
|
||||
val badgeDrawable = badge ?: initBadge(this)
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
|
||||
|
||||
fun emptyStateListAD(
|
||||
coil: ImageLoader,
|
||||
listener: ListStateHolderListener,
|
||||
) = adapterDelegateViewBinding<EmptyState, ListModel, ItemEmptyStateBinding>(
|
||||
{ inflater, parent -> ItemEmptyStateBinding.inflate(inflater, parent, false) }
|
||||
{ inflater, parent -> ItemEmptyStateBinding.inflate(inflater, parent, false) },
|
||||
) {
|
||||
|
||||
binding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() }
|
||||
|
||||
bind {
|
||||
binding.icon.setImageResource(item.icon)
|
||||
binding.icon.newImageRequest(item.icon)?.enqueueWith(coil)
|
||||
binding.textPrimary.setText(item.textPrimary)
|
||||
binding.textSecondary.setTextAndVisible(item.textSecondary)
|
||||
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.icon.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
open class MangaListAdapter(
|
||||
coil: ImageLoader,
|
||||
@@ -24,7 +24,7 @@ open class MangaListAdapter(
|
||||
.addDelegate(ITEM_TYPE_DATE, relatedDateItemAD())
|
||||
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener))
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(coil, listener))
|
||||
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener))
|
||||
.addDelegate(ITEM_TYPE_HEADER_2, listHeader2AD(listener))
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ fun mangaListDetailedItemAD(
|
||||
}
|
||||
binding.textViewRating.textAndVisible = item.rating
|
||||
binding.textViewTags.text = item.tags
|
||||
itemView.bindBadge(badge, item.counter)
|
||||
badge = itemView.bindBadge(badge, item.counter)
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
|
||||
@@ -40,7 +40,7 @@ fun mangaListItemAD(
|
||||
lifecycle(lifecycleOwner)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
itemView.bindBadge(badge, item.counter)
|
||||
badge = itemView.bindBadge(badge, item.counter)
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
|
||||
@@ -62,7 +62,7 @@ class CbzMangaOutput(
|
||||
index.addChapter(chapter)
|
||||
}
|
||||
|
||||
suspend fun finalize() {
|
||||
suspend fun finish() {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
output.put(ENTRY_NAME_INDEX, index.toString())
|
||||
output.finish()
|
||||
|
||||
@@ -43,7 +43,7 @@ class DirMangaImporter(
|
||||
)
|
||||
it.sortChaptersByName()
|
||||
it.mergeWithExisting()
|
||||
it.finalize()
|
||||
it.finish()
|
||||
it.file
|
||||
}
|
||||
return localMangaRepository.getFromFile(dest)
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
@@ -289,16 +290,16 @@ class MainActivity :
|
||||
}
|
||||
|
||||
private fun onFirstStart() {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
lifecycleScope.launch(Dispatchers.Main) { // not a default `Main.immediate` dispatcher
|
||||
when {
|
||||
!settings.isSourcesSelected -> OnboardDialogFragment.showWelcome(supportFragmentManager)
|
||||
settings.newSources.isNotEmpty() -> NewSourcesDialogFragment.show(supportFragmentManager)
|
||||
}
|
||||
withContext(Dispatchers.Default) {
|
||||
TrackWorker.setup(applicationContext)
|
||||
SuggestionsWorker.setup(applicationContext)
|
||||
}
|
||||
requestNotificationsPermission()
|
||||
when {
|
||||
!settings.isSourcesSelected -> OnboardDialogFragment.showWelcome(supportFragmentManager)
|
||||
settings.newSources.isNotEmpty() -> NewSourcesDialogFragment.show(supportFragmentManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ class ReaderActivity :
|
||||
val resolveTextId = ExceptionResolver.getResolveStringId(e)
|
||||
if (resolveTextId != 0) {
|
||||
dialog.setPositiveButton(resolveTextId, listener)
|
||||
} else {
|
||||
} else if (e.isReportable()) {
|
||||
dialog.setPositiveButton(R.string.report, listener)
|
||||
}
|
||||
dialog.show()
|
||||
|
||||
@@ -37,11 +37,13 @@ abstract class BasePageHolder<B : ViewBinding>(
|
||||
|
||||
protected abstract fun onBind(data: ReaderPage)
|
||||
|
||||
fun onAttachedToWindow() {
|
||||
@CallSuper
|
||||
open fun onAttachedToWindow() {
|
||||
delegate.onAttachedToWindow()
|
||||
}
|
||||
|
||||
fun onDetachedFromWindow() {
|
||||
@CallSuper
|
||||
open fun onDetachedFromWindow() {
|
||||
delegate.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.reader.ui.pager
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.Observer
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.davemorrissey.labs.subscaleview.DefaultOnImageEventListener
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.*
|
||||
@@ -21,7 +21,7 @@ class PageHolderDelegate(
|
||||
private val readerSettings: ReaderSettings,
|
||||
private val callback: Callback,
|
||||
private val exceptionResolver: ExceptionResolver,
|
||||
) : SubsamplingScaleImageView.DefaultOnImageEventListener(), Observer<ReaderSettings> {
|
||||
) : DefaultOnImageEventListener, Observer<ReaderSettings> {
|
||||
|
||||
private val scope = loader.loaderScope + Dispatchers.Main.immediate
|
||||
private var state = State.EMPTY
|
||||
@@ -76,7 +76,7 @@ class PageHolderDelegate(
|
||||
callback.onImageShown()
|
||||
}
|
||||
|
||||
override fun onImageLoadError(e: Exception) {
|
||||
override fun onImageLoadError(e: Throwable) {
|
||||
val file = this.file
|
||||
error = e
|
||||
if (state == State.LOADED && e is IOException && file != null && file.exists()) {
|
||||
|
||||
@@ -32,11 +32,11 @@ class ReversedPageHolder(
|
||||
binding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()
|
||||
when (settings.zoomMode) {
|
||||
ZoomMode.FIT_CENTER -> {
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
|
||||
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||
resetScaleAndCenter()
|
||||
}
|
||||
ZoomMode.FIT_HEIGHT -> {
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM
|
||||
minScale = height / sHeight.toFloat()
|
||||
setScaleAndCenter(
|
||||
minScale,
|
||||
@@ -44,7 +44,7 @@ class ReversedPageHolder(
|
||||
)
|
||||
}
|
||||
ZoomMode.FIT_WIDTH -> {
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM
|
||||
minScale = width / sWidth.toFloat()
|
||||
setScaleAndCenter(
|
||||
minScale,
|
||||
@@ -52,7 +52,7 @@ class ReversedPageHolder(
|
||||
)
|
||||
}
|
||||
ZoomMode.KEEP_START -> {
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
|
||||
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||
setScaleAndCenter(
|
||||
maxScale,
|
||||
PointF(sWidth.toFloat(), 0f),
|
||||
|
||||
@@ -7,8 +7,6 @@ import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
@@ -28,9 +26,8 @@ open class PageHolder(
|
||||
View.OnClickListener {
|
||||
|
||||
init {
|
||||
binding.ssiv.setExecutor(Dispatchers.Default.asExecutor())
|
||||
binding.ssiv.setEagerLoadingEnabled(!isLowRamDevice(context))
|
||||
binding.ssiv.setOnImageEventListener(delegate)
|
||||
binding.ssiv.isEagerLoadingEnabled = !isLowRamDevice(context)
|
||||
binding.ssiv.addOnImageEventListener(delegate)
|
||||
@Suppress("LeakingThis")
|
||||
bindingInfo.buttonRetry.setOnClickListener(this)
|
||||
binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled
|
||||
@@ -63,7 +60,7 @@ open class PageHolder(
|
||||
}
|
||||
|
||||
override fun onImageReady(uri: Uri) {
|
||||
binding.ssiv.setImage(ImageSource.uri(uri))
|
||||
binding.ssiv.setImage(ImageSource.Uri(uri))
|
||||
}
|
||||
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
@@ -74,11 +71,11 @@ open class PageHolder(
|
||||
binding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()
|
||||
when (settings.zoomMode) {
|
||||
ZoomMode.FIT_CENTER -> {
|
||||
binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
|
||||
binding.ssiv.minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||
binding.ssiv.resetScaleAndCenter()
|
||||
}
|
||||
ZoomMode.FIT_HEIGHT -> {
|
||||
binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
binding.ssiv.minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM
|
||||
binding.ssiv.minScale = binding.ssiv.height / binding.ssiv.sHeight.toFloat()
|
||||
binding.ssiv.setScaleAndCenter(
|
||||
binding.ssiv.minScale,
|
||||
@@ -86,7 +83,7 @@ open class PageHolder(
|
||||
)
|
||||
}
|
||||
ZoomMode.FIT_WIDTH -> {
|
||||
binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
binding.ssiv.minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM
|
||||
binding.ssiv.minScale = binding.ssiv.width / binding.ssiv.sWidth.toFloat()
|
||||
binding.ssiv.setScaleAndCenter(
|
||||
binding.ssiv.minScale,
|
||||
@@ -94,7 +91,7 @@ open class PageHolder(
|
||||
)
|
||||
}
|
||||
ZoomMode.KEEP_START -> {
|
||||
binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
|
||||
binding.ssiv.minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||
binding.ssiv.setScaleAndCenter(
|
||||
binding.ssiv.maxScale,
|
||||
PointF(0f, 0f),
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.davemorrissey.labs.subscaleview.decoder.SkiaPooledImageRegionDecoder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
|
||||
@@ -24,11 +25,12 @@ class WebtoonHolder(
|
||||
View.OnClickListener {
|
||||
|
||||
private var scrollToRestore = 0
|
||||
private val goneOnInvisibleListener = GoneOnInvisibleListener(bindingInfo.progressBar)
|
||||
|
||||
init {
|
||||
binding.ssiv.setOnImageEventListener(delegate)
|
||||
binding.ssiv.regionDecoderFactory = SkiaPooledImageRegionDecoder.Factory()
|
||||
binding.ssiv.addOnImageEventListener(delegate)
|
||||
bindingInfo.buttonRetry.setOnClickListener(this)
|
||||
GoneOnInvisibleListener(bindingInfo.progressBar).attach()
|
||||
}
|
||||
|
||||
override fun onBind(data: ReaderPage) {
|
||||
@@ -40,6 +42,16 @@ class WebtoonHolder(
|
||||
binding.ssiv.recycle()
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
goneOnInvisibleListener.attach()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
goneOnInvisibleListener.detach()
|
||||
}
|
||||
|
||||
override fun onLoadingStarted() {
|
||||
bindingInfo.layoutError.isVisible = false
|
||||
bindingInfo.progressBar.showCompat()
|
||||
@@ -56,13 +68,13 @@ class WebtoonHolder(
|
||||
}
|
||||
|
||||
override fun onImageReady(uri: Uri) {
|
||||
binding.ssiv.setImage(ImageSource.uri(uri))
|
||||
binding.ssiv.setImage(ImageSource.Uri(uri))
|
||||
}
|
||||
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
binding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()
|
||||
with(binding.ssiv) {
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM
|
||||
minScale = width / sWidth.toFloat()
|
||||
maxScale = minScale
|
||||
scrollTo(
|
||||
|
||||
@@ -5,10 +5,7 @@ import android.graphics.PointF
|
||||
import android.util.AttributeSet
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
||||
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
|
||||
import org.koitharu.kotatsu.utils.ext.parents
|
||||
|
||||
private const val SCROLL_UNKNOWN = -1
|
||||
@@ -23,11 +20,6 @@ class WebtoonImageView @JvmOverloads constructor(
|
||||
private var scrollPos = 0
|
||||
private var scrollRange = SCROLL_UNKNOWN
|
||||
|
||||
init {
|
||||
setExecutor(Dispatchers.Default.asExecutor())
|
||||
setEagerLoadingEnabled(!isLowRamDevice(context))
|
||||
}
|
||||
|
||||
fun scrollBy(delta: Int) {
|
||||
val maxScroll = getScrollRange()
|
||||
if (maxScroll == 0) {
|
||||
@@ -113,4 +105,4 @@ class WebtoonImageView @JvmOverloads constructor(
|
||||
private fun parentHeight(): Int {
|
||||
return parents.firstNotNullOfOrNull { it as? RecyclerView }?.height ?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
|
||||
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
|
||||
import org.koitharu.kotatsu.list.ui.adapter.*
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchListModel
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class MultiSearchAdapter(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
@@ -34,11 +34,11 @@ class MultiSearchAdapter(
|
||||
selectionDecoration = selectionDecoration,
|
||||
listener = listener,
|
||||
itemClickListener = itemClickListener,
|
||||
)
|
||||
),
|
||||
)
|
||||
.addDelegate(loadingStateAD())
|
||||
.addDelegate(loadingFooterAD())
|
||||
.addDelegate(emptyStateListAD(listener))
|
||||
.addDelegate(emptyStateListAD(coil, listener))
|
||||
.addDelegate(errorStateListAD(listener))
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class OnboardDialogFragment :
|
||||
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
||||
super.onBuildDialog(builder)
|
||||
.setPositiveButton(R.string.done, this)
|
||||
.setCancelable(true)
|
||||
.setCancelable(false)
|
||||
if (isWelcome) {
|
||||
builder.setTitle(R.string.welcome)
|
||||
} else {
|
||||
|
||||
@@ -11,8 +11,6 @@ import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.*
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.pow
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
@@ -21,12 +19,16 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion
|
||||
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asArrayList
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.pow
|
||||
|
||||
@HiltWorker
|
||||
class SuggestionsWorker @AssistedInject constructor(
|
||||
@@ -94,14 +96,10 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
|
||||
val rawResults = coroutineScope {
|
||||
tagsBySources.flatMap { (source, tags) ->
|
||||
val repo = mangaRepositoryFactory.create(source)
|
||||
val repo = mangaRepositoryFactory.tryCreate(source) ?: return@flatMap emptyList()
|
||||
tags.map { tag ->
|
||||
async(dispatcher) {
|
||||
repo.getList(
|
||||
offset = 0,
|
||||
sortOrder = SortOrder.UPDATED,
|
||||
tags = setOf(tag),
|
||||
)
|
||||
repo.getListSafe(tag)
|
||||
}
|
||||
}
|
||||
}.awaitAll().flatten().asArrayList()
|
||||
@@ -139,6 +137,18 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
return (weight / maxWeight).pow(2.0).toFloat()
|
||||
}
|
||||
|
||||
private suspend fun MangaRepository.getListSafe(tag: MangaTag) = runCatching {
|
||||
getList(offset = 0, sortOrder = SortOrder.UPDATED, tags = setOf(tag))
|
||||
}.onFailure { error ->
|
||||
error.printStackTraceDebug()
|
||||
}.getOrDefault(emptyList())
|
||||
|
||||
private fun MangaRepository.Factory.tryCreate(source: MangaSource) = runCatching {
|
||||
create(source)
|
||||
}.onFailure { error ->
|
||||
error.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "suggestions"
|
||||
|
||||
@@ -4,11 +4,11 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
import org.koitharu.kotatsu.list.ui.adapter.*
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class FeedAdapter(
|
||||
coil: ImageLoader,
|
||||
@@ -23,7 +23,7 @@ class FeedAdapter(
|
||||
.addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD())
|
||||
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener))
|
||||
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(coil, listener))
|
||||
.addDelegate(ITEM_TYPE_DATE_HEADER, relatedDateItemAD())
|
||||
}
|
||||
|
||||
|
||||
@@ -19,5 +19,10 @@ class GoneOnInvisibleListener(
|
||||
|
||||
fun attach() {
|
||||
view.viewTreeObserver.addOnGlobalLayoutListener(this)
|
||||
onGlobalLayout()
|
||||
}
|
||||
}
|
||||
|
||||
fun detach() {
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import java.io.Serializable
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
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 {
|
||||
@@ -17,6 +20,9 @@ inline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String): 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 {
|
||||
@@ -26,6 +32,9 @@ inline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String)
|
||||
|
||||
@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 {
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 okio.FileNotFoundException
|
||||
@@ -23,24 +24,31 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
|
||||
is FileNotFoundException -> resources.getString(R.string.file_not_found)
|
||||
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
||||
is SyncApiException,
|
||||
is ContentUnavailableException, -> message
|
||||
is ContentUnavailableException,
|
||||
-> message
|
||||
is ParseException -> shortMessage
|
||||
is UnknownHostException,
|
||||
is SocketTimeoutException, -> resources.getString(R.string.network_error)
|
||||
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
|
||||
} ?: resources.getString(R.string.error_occurred)
|
||||
|
||||
fun Throwable.isReportable(): Boolean {
|
||||
if (this !is Exception) {
|
||||
return true
|
||||
}
|
||||
return this is ParseException || this is IllegalArgumentException ||
|
||||
this is IllegalStateException || this.javaClass == RuntimeException::class.java
|
||||
return this is Error || this.javaClass in reportableExceptions
|
||||
}
|
||||
|
||||
fun Throwable.report(message: String?) {
|
||||
val exception = CaughtException(this, message)
|
||||
exception.sendWithAcra()
|
||||
}
|
||||
|
||||
private val reportableExceptions = arraySetOf<Class<*>>(
|
||||
ParseException::class.java,
|
||||
RuntimeException::class.java,
|
||||
IllegalStateException::class.java,
|
||||
IllegalArgumentException::class.java,
|
||||
ConcurrentModificationException::class.java,
|
||||
UnsupportedOperationException::class.java,
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
@@ -22,6 +23,7 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter
|
||||
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ShelfConfigActivity :
|
||||
BaseActivity<ActivityCategoriesBinding>(),
|
||||
OnListItemClickListener<CategoryItem>,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:scrollIndicators="top|bottom"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_source_locale" />
|
||||
|
||||
@@ -374,4 +374,15 @@
|
||||
<string name="import_completed">Importation terminée</string>
|
||||
<string name="import_completed_hint">Vous pouvez supprimer le fichier original du stockage pour gagner de l\'espace</string>
|
||||
<string name="import_will_start_soon">L\'importation va bientôt commencer</string>
|
||||
<string name="text_unsaved_changes_prompt">Vous avez des modifications non sauvegardées, voulez-vous les sauvegarder ou les abandonner \?</string>
|
||||
<string name="discard">Abandonner</string>
|
||||
<string name="history_shortcuts_summary">Rendre les mangas récents disponibles en appuyant longuement sur l\'icône de l\'application</string>
|
||||
<string name="reader_control_ltr_summary">Taper sur le bord droit ou appuyer sur la touche droite permet toujours de passer à la page suivante</string>
|
||||
<string name="reader_control_ltr">Contrôle ergonomique du lecteur</string>
|
||||
<string name="history_shortcuts">Afficher les raccourcis des mangas récents</string>
|
||||
<string name="color_correction">Correction des couleurs</string>
|
||||
<string name="brightness">Luminosité</string>
|
||||
<string name="contrast">Contraste</string>
|
||||
<string name="reset">Réinitialiser</string>
|
||||
<string name="color_correction_hint">Les paramètres de couleurs choisis seront sauvegardés pour ce manga</string>
|
||||
</resources>
|
||||
@@ -247,7 +247,7 @@
|
||||
<string name="data_restored_success">Todos os dados foram restaurados</string>
|
||||
<string name="protect_application">Proteja o aplicativo</string>
|
||||
<string name="updates_feed_cleared">Limpo</string>
|
||||
<string name="right_to_left">Da direita para a esquerda (←)</string>
|
||||
<string name="right_to_left">Da direita para a esquerda</string>
|
||||
<string name="scale_mode">Modo de escala</string>
|
||||
<string name="zoom_mode_fit_height">Ajustar à altura</string>
|
||||
<string name="black_dark_theme">Preto</string>
|
||||
@@ -296,4 +296,93 @@
|
||||
<string name="detect_reader_mode_summary">Detectar automaticamente se o mangá é webtoon</string>
|
||||
<string name="disable_battery_optimization">Desative a otimização da bateria</string>
|
||||
<string name="disable_battery_optimization_summary">Ajuda com verificações de atualizações em segundo plano</string>
|
||||
<string name="invalid_domain_message">Endereço inválido</string>
|
||||
<string name="use_fingerprint">Usar impressão digital, se disponível</string>
|
||||
<string name="appwidget_shelf_description">Mangás dos seus favoritos</string>
|
||||
<string name="appwidget_recent_description">Seus mangás recentemente lidos</string>
|
||||
<string name="status_completed">Concluído</string>
|
||||
<string name="status_on_hold">Em espera</string>
|
||||
<string name="status_dropped">Desistido</string>
|
||||
<string name="text_shelf_holder_primary">Seus mangás serão exibidos aqui</string>
|
||||
<string name="text_shelf_holder_secondary">Encontre o que ler na seção <«Explorar»</string>
|
||||
<string name="canceled">Cancelado</string>
|
||||
<string name="account_already_exists">Essa conta já existe</string>
|
||||
<string name="back">Voltar</string>
|
||||
<string name="sync">Sincronização</string>
|
||||
<string name="sync_title">Sincronize seus dados</string>
|
||||
<string name="email_enter_hint">Digite seu e-mail para continuar</string>
|
||||
<string name="tracking">Monitoramento</string>
|
||||
<string name="logout">Sair</string>
|
||||
<string name="crash_text">Algo deu errado. Por favor, envie um relatório de bug para ajudar os desenvolvedores a consertarem isso.</string>
|
||||
<string name="send">Enviar</string>
|
||||
<string name="status_planned">Planejado</string>
|
||||
<string name="status_reading">Lendo</string>
|
||||
<string name="status_re_reading">Relendo</string>
|
||||
<string name="report">Reportar</string>
|
||||
<string name="clear_cookies_summary">Pode ajudar no caso de alguns problemas. Todas as autorizações serão invalidadas</string>
|
||||
<string name="show_reading_indicators">Mostrar indicadores de progresso de leitura</string>
|
||||
<string name="data_deletion">Exclusão de dados</string>
|
||||
<string name="show_reading_indicators_summary">Mostrar percentual de leitura no histórico e nos favoritos</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Mangás marcados como +18 nunca serão adicionados ao histórico e seu progresso não sera salvo</string>
|
||||
<string name="show_all">Mostrar tudo</string>
|
||||
<string name="clear_all_history">Limpar todo o histórico</string>
|
||||
<string name="last_2_hours">Ultimas 2 horas</string>
|
||||
<string name="history_cleared">Histórico apagado</string>
|
||||
<string name="manage">Gerenciar</string>
|
||||
<string name="no_bookmarks_yet">Sem páginas marcadas ainda</string>
|
||||
<string name="no_bookmarks_summary">Você pode criar um marcador de página enquanto lé o mangá</string>
|
||||
<string name="bookmarks_removed">Marcadores de página removidos</string>
|
||||
<string name="no_manga_sources">Sem fontes de mangás</string>
|
||||
<string name="select_range">Selecionar intervalo</string>
|
||||
<string name="disable_all">Desabilitar tudo</string>
|
||||
<string name="no_chapters">Sem capítulos</string>
|
||||
<string name="manga_error_description_pattern">Detalhes do erro:<br><tt>%1$s</tt><br><br>1. Tente <a href=%2$s>abrir o mangá em um navegador de internet</a>para garantir que ele está disponível na fonte<br>2. Se estiver disponível, envie um relatório de erro para os desenvolvedores.</string>
|
||||
<string name="feed">Feed</string>
|
||||
<string name="history_shortcuts_summary">Torne os mangás recentes visíveis pressionando o ícone do aplicativo</string>
|
||||
<string name="no_manga_sources_text">Habilite as fontes de maná para ler online</string>
|
||||
<string name="random">Aleatório</string>
|
||||
<string name="categories_delete_confirm">Tem certeza que deseja excluir as categorias favoritas selecionadas\?
|
||||
\nTodos os mangás serão perdidos e isso não pode ser desfeito.</string>
|
||||
<string name="reorder">Reordenar</string>
|
||||
<string name="empty">Vazio</string>
|
||||
<string name="changelog">Registro de alterações</string>
|
||||
<string name="explore">Explorar</string>
|
||||
<string name="confirm_exit">Pressione Voltar novamente para sair</string>
|
||||
<string name="exit_confirmation_summary">Pressione Voltar duas vezes para sair do app</string>
|
||||
<string name="exit_confirmation">Confirmação de saída</string>
|
||||
<string name="saved_manga">Mangás salvos</string>
|
||||
<string name="pages_cache">Cache de páginas</string>
|
||||
<string name="other_cache">Outro cache</string>
|
||||
<string name="storage_usage">Uso de armazenamento</string>
|
||||
<string name="available">Disponível</string>
|
||||
<string name="enter_email_text">Digite seu e-mail para continuar</string>
|
||||
<string name="removed_from_favourites">Removido dos favoritos</string>
|
||||
<string name="removed_from_s">Removido de \"%s\"</string>
|
||||
<string name="options">Opções</string>
|
||||
<string name="downloading_manga">Baixando mangá</string>
|
||||
<string name="incognito_mode">Modo anônimo</string>
|
||||
<string name="app_update_available_s">Atualização do aplicativo disponível: %s</string>
|
||||
<string name="automatic_scroll">Rolagem automática</string>
|
||||
<string name="off_short">Desligado</string>
|
||||
<string name="reader_info_pattern">Cap. %1$d/%2$d Pág. %3$d/%4$d</string>
|
||||
<string name="reader_info_bar">Mostrar barra de informações no leitor</string>
|
||||
<string name="folder_with_images">Pasta com imagens</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="seconds_pattern">%ss</string>
|
||||
<string name="comics_archive">Arquivo de quadrinhos</string>
|
||||
<string name="importing_manga">Importando mangá(s)</string>
|
||||
<string name="import_completed">Importação completa</string>
|
||||
<string name="import_completed_hint">Você pode excluir o arquivo original do dispositivo para salvar espaço</string>
|
||||
<string name="import_will_start_soon">A importação começará em breve</string>
|
||||
<string name="history_shortcuts">Mostrar atalhos de mangás recentes</string>
|
||||
<string name="reader_control_ltr_summary">Tocar na borda direita ou pressionar a tecla direita sempre passa para a próxima página</string>
|
||||
<string name="reader_control_ltr">Controle de leitura ergonômico</string>
|
||||
<string name="color_correction">Correção de cor</string>
|
||||
<string name="brightness">Luminosidade</string>
|
||||
<string name="contrast">Contraste</string>
|
||||
<string name="reset">Redefinir</string>
|
||||
<string name="color_correction_hint">As configurações de cor escolhidas serão lembradas para esse mangá</string>
|
||||
<string name="text_unsaved_changes_prompt">Você tem alterações não salvas, deseja salvá-las ou descartá-las\?</string>
|
||||
<string name="discard">Descartar</string>
|
||||
<string name="not_found_404">Conteúdo não encontrado ou removido</string>
|
||||
</resources>
|
||||
@@ -341,4 +341,45 @@
|
||||
<string name="explore">Keşfet</string>
|
||||
<string name="confirm_exit">Çıkmak için tekrar Geri tıkla</string>
|
||||
<string name="exit_confirmation_summary">Çıkmak için iki defa Geri tıkla</string>
|
||||
<string name="removed_from_favourites">Favorilerden kaldırıldı</string>
|
||||
<string name="downloading_manga">Manga indiriliyor</string>
|
||||
<string name="changelog">Değişiklik günlüğü</string>
|
||||
<string name="exit_confirmation">Çıkış doğrulaması</string>
|
||||
<string name="enter_email_text">Devam etmek için e-postanızı girin</string>
|
||||
<string name="removed_from_s">\"%s\"dan kaldırıldı</string>
|
||||
<string name="comics_archive">Çizgi roman arşivi</string>
|
||||
<string name="manga_error_description_pattern">Hata ayrıntıları:<br><tt>%1$s</tt><br><br>1.Mangayı <a href=%2$s>kaynağında mevcut olduğuna emin olmak için</a> bir web tarayıcısında açın<br>2. Mevcutsa, geliştiricilere bir hata reporu gönderin.</string>
|
||||
<string name="text_shelf_holder_secondary">«Keşfet» kısmında neler okuyacağınızı bulun</string>
|
||||
<string name="categories_delete_confirm">Seçilen favori kategorileri silmek istediğinizden emin misiniz\?
|
||||
\nİçindeki tüm mangalar kaybolur ve bu işlem geri alınamaz.</string>
|
||||
<string name="reorder">Yeniden sırala</string>
|
||||
<string name="pages_cache">Sayfa önbelleği</string>
|
||||
<string name="other_cache">Diğer önbellekler</string>
|
||||
<string name="storage_usage">Depolama kullanımı</string>
|
||||
<string name="available">Mevcut</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="options">Seçenekler</string>
|
||||
<string name="incognito_mode">Gizli mod</string>
|
||||
<string name="app_update_available_s">Uygulama güncellemesi mevcut: %s</string>
|
||||
<string name="no_chapters">Bölüm yok</string>
|
||||
<string name="automatic_scroll">Otomatik kaydır</string>
|
||||
<string name="off_short">Kapalı</string>
|
||||
<string name="seconds_pattern">%ss</string>
|
||||
<string name="reader_info_pattern">Böl. %1$d/%2$d Sayf. %3$d/%4$d</string>
|
||||
<string name="reader_info_bar">Okuyucuda bilgi çubuğu göster</string>
|
||||
<string name="folder_with_images">Resimlerle klasör</string>
|
||||
<string name="importing_manga">Manga içe aktarılıyor</string>
|
||||
<string name="import_completed">İçe aktarım tamamlandı</string>
|
||||
<string name="import_completed_hint">Yer açmak için orijinal dosyayı depolamadan silebilirsiniz</string>
|
||||
<string name="import_will_start_soon">İçe aktarım birazdan başlayacak</string>
|
||||
<string name="feed">Feed</string>
|
||||
<string name="history_shortcuts">En son manga kısayollarını göster</string>
|
||||
<string name="reader_control_ltr">Ergonomik okuyucu kontrol</string>
|
||||
<string name="color_correction">Renk düzeltme</string>
|
||||
<string name="brightness">Parlaklık</string>
|
||||
<string name="contrast">Kontrast</string>
|
||||
<string name="reset">Sıfırla</string>
|
||||
<string name="color_correction_hint">Seçilen renk ayarları bu manga için hatırlanacaktır</string>
|
||||
<string name="text_unsaved_changes_prompt">Kaydedilmemiş değişiklikleriniz var, kaydetmek mi istersiniz yoksa, yoksaymak mı istersiniz\?</string>
|
||||
<string name="discard">Yoksay</string>
|
||||
</resources>
|
||||
@@ -374,4 +374,15 @@
|
||||
<string name="other_cache">Інший кеш</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="manga_error_description_pattern">Деталі помилки:<br><tt>%1$s</tt><br><br>1. Спробуйте <a href=%2$s>відкрити манґу у веб-браузері</a>, щоб переконатися, що вона доступна в джерелі<br>2. Якщо вона доступна, то надішліть звіт про помилку розробникам.</string>
|
||||
<string name="history_shortcuts">Показувати ярлики останньої прочитаної манґи</string>
|
||||
<string name="history_shortcuts_summary">Зробити нещодавно прочитану манґу доступною за довгим натисканням на іконку програми</string>
|
||||
<string name="reader_control_ltr_summary">Натискання на правий край або натискання правої клавіші завжди переходить на наступну сторінку</string>
|
||||
<string name="reader_control_ltr">Ергономічне керування режимом читання</string>
|
||||
<string name="brightness">Яскравість</string>
|
||||
<string name="color_correction">Корекція кольору</string>
|
||||
<string name="contrast">Контрастність</string>
|
||||
<string name="reset">Скинути</string>
|
||||
<string name="color_correction_hint">Вибрані параметри кольору будуть запам\'ятовані для цієї манґи</string>
|
||||
<string name="text_unsaved_changes_prompt">У вас є незбережені зміни, ви хочете зберегти чи скасувати їх\?</string>
|
||||
<string name="discard">Скасувати</string>
|
||||
</resources>
|
||||
@@ -374,4 +374,6 @@
|
||||
<string name="feed">订阅源</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="not_found_404">内容未找到或已移除</string>
|
||||
<string name="reader_control_ltr_summary">点击屏幕右侧或按下右键翻到下一页</string>
|
||||
<string name="reader_control_ltr">高效阅读器控制</string>
|
||||
</resources>
|
||||
@@ -6,7 +6,7 @@
|
||||
<string name="url_twitter" translatable="false">https://twitter.com/kotatsuapp</string>
|
||||
<string name="url_reddit" translatable="false">https://reddit.com/user/kotatsuapp</string>
|
||||
<string name="url_weblate" translatable="false">https://hosted.weblate.org/engage/kotatsu</string>
|
||||
<string name="url_error_report" translatable="false">http://86.57.183.214:8082/report</string>
|
||||
<string name="url_error_report" translatable="false">https://acra.rumblur.space/report</string>
|
||||
<string name="account_type_sync" translatable="false">org.kotatsu.sync</string>
|
||||
<string name="url_sync_server" translatable="false">http://86.57.183.214:8081</string>
|
||||
<string-array name="values_theme" translatable="false">
|
||||
|
||||
Reference in New Issue
Block a user