Compare commits
34 Commits
v4.0-a5
...
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 | |||
| 6243fc88c9 | |||
|
|
f74e865b06 | ||
|
|
1a2bc02188 | ||
| b0153e9f61 | |||
|
|
7f3fc1b88a | ||
|
|
40bf0c75bf | ||
|
|
9407bd205c | ||
|
|
3317c8dddc |
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 494
|
||||
versionName '4.0-a5'
|
||||
versionCode 497
|
||||
versionName '4.0-beta1'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -82,13 +82,13 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:f112a06ab6') {
|
||||
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'
|
||||
@@ -102,7 +102,7 @@ dependencies {
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
||||
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
|
||||
implementation 'com.google.android.material:material:1.7.0-beta01'
|
||||
implementation 'com.google.android.material:material:1.7.0-rc01'
|
||||
//noinspection LifecycleAnnotationProcessorWithJava8
|
||||
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
|
||||
|
||||
@@ -122,10 +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.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.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'
|
||||
@@ -145,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) },
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
import org.koitharu.kotatsu.core.network.DoHProvider
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.ext.getEnumValue
|
||||
import org.koitharu.kotatsu.utils.ext.observe
|
||||
import org.koitharu.kotatsu.utils.ext.putEnumValue
|
||||
@@ -208,6 +209,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
val dnsOverHttps: DoHProvider
|
||||
get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)
|
||||
|
||||
var localListOrder: SortOrder
|
||||
get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)
|
||||
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
|
||||
|
||||
fun isPagesPreloadAllowed(cm: ConnectivityManager): Boolean {
|
||||
return when (prefs.getString(KEY_PAGES_PRELOAD, null)?.toIntOrNull()) {
|
||||
NETWORK_ALWAYS -> true
|
||||
@@ -325,6 +330,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_READER_BAR = "reader_bar"
|
||||
const val KEY_SHORTCUTS = "dynamic_shortcuts"
|
||||
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
|
||||
const val KEY_LOCAL_LIST_ORDER = "local_order"
|
||||
|
||||
// About
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.koitharu.kotatsu.local.domain
|
||||
|
||||
import java.io.File
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
|
||||
class LocalManga(
|
||||
val manga: Manga,
|
||||
val file: File,
|
||||
) {
|
||||
|
||||
var createdAt: Long = -1L
|
||||
get() {
|
||||
if (field == -1L) {
|
||||
field = file.lastModified()
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as LocalManga
|
||||
|
||||
if (manga != other.manga) return false
|
||||
if (file != other.file) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = manga.hashCode()
|
||||
result = 31 * result + file.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
fun Collection<LocalManga>.unwrap(): List<Manga> = map { it.manga }
|
||||
|
||||
fun LocalManga.isMatchesQuery(query: String): Boolean {
|
||||
return manga.title.contains(query, ignoreCase = true) ||
|
||||
manga.altTitle?.contains(query, ignoreCase = true) == true
|
||||
}
|
||||
|
||||
fun LocalManga.containsTags(tags: Set<MangaTag>): Boolean {
|
||||
return manga.tags.containsAll(tags)
|
||||
}
|
||||
@@ -45,12 +45,9 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
||||
}
|
||||
val list = getRawList()
|
||||
if (query.isNotEmpty()) {
|
||||
list.retainAll { x ->
|
||||
x.title.contains(query, ignoreCase = true) ||
|
||||
x.altTitle?.contains(query, ignoreCase = true) == true
|
||||
}
|
||||
list.retainAll { x -> x.isMatchesQuery(query) }
|
||||
}
|
||||
return list
|
||||
return list.unwrap()
|
||||
}
|
||||
|
||||
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
|
||||
@@ -59,15 +56,17 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
||||
}
|
||||
val list = getRawList()
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
list.retainAll { x ->
|
||||
x.tags.containsAll(tags)
|
||||
}
|
||||
list.retainAll { x -> x.containsTags(tags) }
|
||||
}
|
||||
@Suppress("NON_EXHAUSTIVE_WHEN_STATEMENT")
|
||||
when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> list.sortBy { it.title }
|
||||
SortOrder.RATING -> list.sortBy { it.rating }
|
||||
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 }
|
||||
}
|
||||
return list
|
||||
return list.unwrap()
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga) = when {
|
||||
@@ -235,9 +234,9 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
||||
private fun CoroutineScope.getFromFileAsync(
|
||||
file: File,
|
||||
context: CoroutineContext,
|
||||
): Deferred<Manga?> = async(context) {
|
||||
): Deferred<LocalManga?> = async(context) {
|
||||
runInterruptible {
|
||||
runCatching { getFromFile(file) }.getOrNull()
|
||||
runCatching { LocalManga(getFromFile(file), file) }.getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +282,7 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
||||
locks.unlock(id)
|
||||
}
|
||||
|
||||
private suspend fun getRawList(): ArrayList<Manga> {
|
||||
private suspend fun getRawList(): ArrayList<LocalManga> {
|
||||
val files = getAllFiles()
|
||||
return coroutineScope {
|
||||
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
|
||||
|
||||
@@ -43,7 +43,7 @@ class DirMangaImporter(
|
||||
)
|
||||
it.sortChaptersByName()
|
||||
it.mergeWithExisting()
|
||||
it.finalize()
|
||||
it.finish()
|
||||
it.file
|
||||
}
|
||||
return localMangaRepository.getFromFile(dest)
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.viewModels
|
||||
@@ -13,10 +14,11 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
|
||||
class LocalListFragment : MangaListFragment() {
|
||||
class LocalListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener {
|
||||
|
||||
override val viewModel by viewModels<LocalListViewModel>()
|
||||
|
||||
@@ -30,6 +32,14 @@ class LocalListFragment : MangaListFragment() {
|
||||
ImportDialogFragment.show(childFragmentManager)
|
||||
}
|
||||
|
||||
override fun onFilterClick(view: View?) {
|
||||
super.onFilterClick(view)
|
||||
val menu = PopupMenu(requireContext(), view ?: binding.recyclerView)
|
||||
menu.inflate(R.menu.popup_order)
|
||||
menu.setOnMenuItemClickListener(this)
|
||||
menu.show()
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||
@@ -53,6 +63,17 @@ class LocalListFragment : MangaListFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
val order = when (item.itemId) {
|
||||
R.id.action_order_new -> SortOrder.NEWEST
|
||||
R.id.action_order_abs -> SortOrder.ALPHABETICAL
|
||||
R.id.action_order_rating -> SortOrder.RATING
|
||||
else -> return false
|
||||
}
|
||||
viewModel.setSortOrder(order)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) {
|
||||
MaterialAlertDialogBuilder(context ?: return)
|
||||
.setTitle(R.string.delete_manga)
|
||||
|
||||
@@ -23,4 +23,4 @@ class LocalListMenuProvider(
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
package org.koitharu.kotatsu.local.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
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
|
||||
@@ -30,18 +34,24 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
class LocalListViewModel @Inject constructor(
|
||||
private val repository: LocalMangaRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
settings: AppSettings,
|
||||
) : MangaListViewModel(settings) {
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val settings: AppSettings,
|
||||
) : MangaListViewModel(settings), ListExtraProvider {
|
||||
|
||||
val onMangaRemoved = SingleLiveEvent<Unit>()
|
||||
val sortOrder = MutableLiveData(settings.localListOrder)
|
||||
private val listError = MutableStateFlow<Throwable?>(null)
|
||||
private val mangaList = MutableStateFlow<List<Manga>?>(null)
|
||||
private val selectedTags = MutableStateFlow<Set<MangaTag>>(emptySet())
|
||||
private var refreshJob: Job? = null
|
||||
|
||||
override val content = combine(
|
||||
mangaList,
|
||||
createListModeFlow(),
|
||||
sortOrder.asFlow(),
|
||||
selectedTags,
|
||||
listError,
|
||||
) { list, mode, error ->
|
||||
) { list, mode, order, tags, error ->
|
||||
when {
|
||||
error != null -> listOf(error.toErrorState(canRetry = true))
|
||||
list == null -> listOf(LoadingState)
|
||||
@@ -53,7 +63,10 @@ class LocalListViewModel @Inject constructor(
|
||||
actionStringRes = R.string._import,
|
||||
),
|
||||
)
|
||||
else -> list.toUi(mode)
|
||||
else -> buildList(list.size + 1) {
|
||||
add(createHeader(list, tags, order))
|
||||
list.toUi(this, mode, this@LocalListViewModel)
|
||||
}
|
||||
}
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
|
||||
@@ -63,14 +76,27 @@ class LocalListViewModel @Inject constructor(
|
||||
watchDirectories()
|
||||
}
|
||||
|
||||
override fun onUpdateFilter(tags: Set<MangaTag>) {
|
||||
selectedTags.value = tags
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val prevJob = refreshJob
|
||||
refreshJob = launchLoadingJob(Dispatchers.Default) {
|
||||
prevJob?.cancelAndJoin()
|
||||
doRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRetry() = onRefresh()
|
||||
|
||||
fun setSortOrder(value: SortOrder) {
|
||||
sortOrder.value = value
|
||||
settings.localListOrder = value
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
fun delete(ids: Set<Long>) {
|
||||
launchLoadingJob {
|
||||
withContext(Dispatchers.Default) {
|
||||
@@ -93,7 +119,7 @@ class LocalListViewModel @Inject constructor(
|
||||
private suspend fun doRefresh() {
|
||||
try {
|
||||
listError.value = null
|
||||
mangaList.value = repository.getList(0, null, null)
|
||||
mangaList.value = repository.getList(0, selectedTags.value, sortOrder.value)
|
||||
} catch (e: Throwable) {
|
||||
listError.value = e
|
||||
}
|
||||
@@ -119,4 +145,46 @@ class LocalListViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createHeader(mangaList: List<Manga>, selectedTags: Set<MangaTag>, order: SortOrder): ListHeader2 {
|
||||
val tags = HashMap<MangaTag, Int>()
|
||||
for (item in mangaList) {
|
||||
for (tag in item.tags) {
|
||||
tags[tag] = tags[tag]?.plus(1) ?: 1
|
||||
}
|
||||
}
|
||||
val topTags = tags.entries.sortedByDescending { it.value }.take(6)
|
||||
val chips = LinkedList<ChipsView.ChipModel>()
|
||||
for ((tag, _) in topTags) {
|
||||
val model = ChipsView.ChipModel(
|
||||
icon = 0,
|
||||
title = tag.title,
|
||||
isCheckable = true,
|
||||
isChecked = tag in selectedTags,
|
||||
data = tag,
|
||||
)
|
||||
if (model.isChecked) {
|
||||
chips.addFirst(model)
|
||||
} else {
|
||||
chips.addLast(model)
|
||||
}
|
||||
}
|
||||
return ListHeader2(
|
||||
chips = chips,
|
||||
sortOrder = order,
|
||||
hasSelectedTags = selectedTags.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getCounter(mangaId: Long): Int {
|
||||
return trackingRepository.getNewChaptersCount(mangaId)
|
||||
}
|
||||
|
||||
override suspend fun getProgress(mangaId: Long): Float {
|
||||
return if (settings.isReadingIndicatorsEnabled) {
|
||||
historyRepository.getProgress(mangaId)
|
||||
} else {
|
||||
PROGRESS_NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
17
app/src/main/res/menu/popup_order.xml
Normal file
17
app/src/main/res/menu/popup_order.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_order_new"
|
||||
android:title="@string/newest" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_order_abs"
|
||||
android:title="@string/by_name" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_order_rating"
|
||||
android:title="@string/by_rating" />
|
||||
|
||||
</menu>
|
||||
@@ -51,7 +51,7 @@
|
||||
<string name="scale_mode">Mode mise à l\'échelle</string>
|
||||
<string name="report_github">Signaler un problème sur GitHub</string>
|
||||
<string name="create_category">Nouvelle catégorie</string>
|
||||
<string name="right_to_left">De droite à gauche (←)</string>
|
||||
<string name="right_to_left">De droite à gauche</string>
|
||||
<string name="no_update_available">Aucune mise à jour disponible</string>
|
||||
<string name="update_check_failed">Échec de la recherche de mise à jour</string>
|
||||
<string name="checking_for_updates">Recherche de mises à jour…</string>
|
||||
@@ -320,4 +320,69 @@
|
||||
<string name="invalid_domain_message">Domaine invalide</string>
|
||||
<string name="select_range">Sélectionner une plage</string>
|
||||
<string name="not_found_404">Contenu non trouvé ou supprimé</string>
|
||||
<string name="manga_error_description_pattern">Détails de l\'erreur :<br><tt>%1$s</tt><br><br>1. Essayez de <a href=%2$s>ouvrir le manga dans un navigateur web</a> pour vous assurer qu\'il est disponible sur sa source<br>2. S\'il est disponible, envoyez un rapport d\'erreur aux développeurs.</string>
|
||||
<string name="confirm_exit">Rappuyez sur Retour pour quitter</string>
|
||||
<string name="categories_delete_confirm">Êtes-vous sûr·e de vouloir supprimer les catégories de favoris sélectionnées \?
|
||||
\nTous les mangas qui s\'y trouvent seront perdus et ceci ne peut pas être annulé.</string>
|
||||
<string name="exit_confirmation_summary">Appuyez deux fois sur la touche Retour pour quitter l\'appli</string>
|
||||
<string name="available">Disponible</string>
|
||||
<string name="exit_confirmation">Confirmation de sortie</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="feed">Flux</string>
|
||||
<string name="enter_email_text">Entrez votre courriel pour continuer</string>
|
||||
<string name="app_update_available_s">Mise à jour de l\'application disponible : %s</string>
|
||||
<string name="importing_manga">Importation de mangas</string>
|
||||
<string name="removed_from_favourites">Retiré des favoris</string>
|
||||
<string name="clear_all_history">Effacer tout l\'historique</string>
|
||||
<string name="last_2_hours">Les 2 dernières heures</string>
|
||||
<string name="history_cleared">Historique effacé</string>
|
||||
<string name="manage">Gérer</string>
|
||||
<string name="no_bookmarks_yet">Aucun marque-page</string>
|
||||
<string name="no_bookmarks_summary">Vous pouvez créer un marque-page pendant la lecture d\'un manga</string>
|
||||
<string name="bookmarks_removed">Marque-pages supprimés</string>
|
||||
<string name="no_manga_sources">Aucune source de mangas</string>
|
||||
<string name="no_manga_sources_text">Autoriser les sources de mangas de lire des mangas en ligne</string>
|
||||
<string name="random">Aléatoire</string>
|
||||
<string name="reorder">Réordonner</string>
|
||||
<string name="empty">Vide</string>
|
||||
<string name="changelog">Journal des modifications</string>
|
||||
<string name="explore">Explorer</string>
|
||||
<string name="text_shelf_holder_primary">Vos mangas seront affichés ici</string>
|
||||
<string name="text_shelf_holder_secondary">Trouvez ce que vous voulez lire dans la section « Explorer »</string>
|
||||
<string name="canceled">Annulé</string>
|
||||
<string name="account_already_exists">Le compte existe déjà</string>
|
||||
<string name="back">Retour</string>
|
||||
<string name="sync">Synchronisation</string>
|
||||
<string name="sync_title">Synchronisez vos données</string>
|
||||
<string name="email_enter_hint">Entrez votre courriel pour continuer</string>
|
||||
<string name="saved_manga">Mangas sauvegardés</string>
|
||||
<string name="pages_cache">Cache des pages</string>
|
||||
<string name="other_cache">Autre cache</string>
|
||||
<string name="storage_usage">Utilisation du stockage</string>
|
||||
<string name="removed_from_s">Retiré de « %s »</string>
|
||||
<string name="options">Options</string>
|
||||
<string name="downloading_manga">Télécharger des mangas</string>
|
||||
<string name="incognito_mode">Mode incognito</string>
|
||||
<string name="no_chapters">Aucun chapitre</string>
|
||||
<string name="automatic_scroll">Défilement automatique</string>
|
||||
<string name="off_short">Désactivé</string>
|
||||
<string name="seconds_pattern">%s s</string>
|
||||
<string name="reader_info_pattern">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>
|
||||
<string name="reader_info_bar">Afficher la barre d\'infos dans le lecteur</string>
|
||||
<string name="comics_archive">Archives des BD</string>
|
||||
<string name="folder_with_images">Dossier avec des images</string>
|
||||
<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>
|
||||
@@ -326,4 +326,46 @@
|
||||
<string name="removed_from_favourites">Dihapus dari favorit</string>
|
||||
<string name="import_will_start_soon">Impor akan segera dimulai</string>
|
||||
<string name="not_found_404">Konten tidak ditemukan atau dihapus</string>
|
||||
<string name="exit_confirmation_summary">Tekan Kembali dua kali untuk keluar dari aplikasi</string>
|
||||
<string name="exit_confirmation">Konfirmasi keluar</string>
|
||||
<string name="pages_cache">Tembolok halaman</string>
|
||||
<string name="other_cache">Tembolok lainnya</string>
|
||||
<string name="storage_usage">Penggunaan penyimpanan</string>
|
||||
<string name="available">Tersedia</string>
|
||||
<string name="incognito_mode">Mode Incognito</string>
|
||||
<string name="app_update_available_s">Pembaruan aplikasi tersedia: %s</string>
|
||||
<string name="automatic_scroll">Gulir otomatis</string>
|
||||
<string name="comics_archive">Arsip komik</string>
|
||||
<string name="folder_with_images">Folder dengan gambar</string>
|
||||
<string name="import_completed_hint">Anda bisa menghapus berkas asli dari penyimpanan untuk menghemat ruang</string>
|
||||
<string name="feed">Umpan</string>
|
||||
<string name="manga_error_description_pattern">Detail galat:<br><tt>%1$s</tt><br><br>1. Coba untuk <a href=%2$s>membuka manga di peramban web</a> untuk memastikan bahwa itu tersedia di sumbernya<br>2. Jika tersedia, kirim laporan galat ke pengembang.</string>
|
||||
<string name="brightness">Kecerahan</string>
|
||||
<string name="contrast">Kontras</string>
|
||||
<string name="reset">Atur Ulang</string>
|
||||
<string name="color_correction_hint">Pengaturan warna yang dipilih akan diingat untuk manga ini</string>
|
||||
<string name="text_unsaved_changes_prompt">Anda memiliki perubahan belum tersimpan, apakah Anda ingin menyimpannya atau membuangnya\?</string>
|
||||
<string name="discard">Buang</string>
|
||||
<string name="use_fingerprint">Gunakan sidik jari jika tersedia</string>
|
||||
<string name="appwidget_shelf_description">Manga dari favorit Anda</string>
|
||||
<string name="appwidget_recent_description">Manga yang baru-baru ini Anda baca</string>
|
||||
<string name="data_deletion">Penghapusan data</string>
|
||||
<string name="account_already_exists">Akun sudah ada</string>
|
||||
<string name="back">Kembali</string>
|
||||
<string name="disable_battery_optimization_summary">Membantu pemeriksaan pembaruan di latar belakang</string>
|
||||
<string name="crash_text">Ada sesuatu yang salah. Mohon untuk mengirim laporan kutu (bug) ke pengembang untuk membantu kami memperbaikinya.</string>
|
||||
<string name="report">Lapor</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Manga yang ditandai sebagai NSFW tidak akan ditambahkan ke riwayat dan progres Anda tidak akan disimpan</string>
|
||||
<string name="clear_cookies_summary">Bisa membantu dalam beberapa masalah. Seluruh otorisasi akan menjadi tidak valid.</string>
|
||||
<string name="manage">Kelola</string>
|
||||
<string name="no_manga_sources_text">Aktifkan sumber manga untuk membaca manga daring</string>
|
||||
<string name="categories_delete_confirm">Apakah Anda yakin ingin menghapus kategori favorit yang dipilih\?
|
||||
\n Semua manga di sana akan hilang dan ini tidak bisa diurungkan.</string>
|
||||
<string name="reorder">Atur Ulang</string>
|
||||
<string name="saved_manga">Manga tersimpan</string>
|
||||
<string name="confirm_exit">Tekan Kembali lagi untuk keluar</string>
|
||||
<string name="no_chapters">Tidak ada bab</string>
|
||||
<string name="history_shortcuts">Tampilkan pintasan manga baru-baru ini</string>
|
||||
<string name="history_shortcuts_summary">Buat manga baru-baru ini tersedia dengan menekan panjang pada ikon aplikasi</string>
|
||||
<string name="select_range">Pilih jangkauan</string>
|
||||
</resources>
|
||||
@@ -374,4 +374,5 @@
|
||||
<string name="comics_archive">コミックアーカイブ</string>
|
||||
<string name="downloading_manga">漫画をダウンロードする</string>
|
||||
<string name="feed">フィード</string>
|
||||
<string name="manga_error_description_pattern">エラーの詳細:<br><tt>%1$s</tt><br><br>1. <a href=%2$s>Webブラウザで漫画を開いてみて</a>、そのソースで利用できるか確認する<br>2. 利用できる場合は、開発者にエラーレポートを送信する</a>。</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>
|
||||
@@ -374,4 +374,16 @@
|
||||
<string name="import_will_start_soon">Импорт скоро начнётся</string>
|
||||
<string name="feed">Лента</string>
|
||||
<string name="downloading_manga">Загрузка манги</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="reset">Сбросить</string>
|
||||
<string name="discard">Отменить</string>
|
||||
<string name="text_unsaved_changes_prompt">У вас есть несохраненные изменения, вы хотите сохранить или отменить их\?</string>
|
||||
<string name="contrast">Контрастность</string>
|
||||
<string name="brightness">Яркость</string>
|
||||
<string name="color_correction">Цветокоррекция</string>
|
||||
<string name="color_correction_hint">Выбранные настройки цвета запомнятся для этой манги</string>
|
||||
</resources>
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="try_again">Tekrar dene</string>
|
||||
<string name="clear_history">Geçmişi temizle</string>
|
||||
<string name="nothing_found">Hiçbir şey bulunamadı</string>
|
||||
<string name="history_is_empty">Sen geçmişiniz yok</string>
|
||||
<string name="history_is_empty">Geçmiş yok</string>
|
||||
<string name="read">Oku</string>
|
||||
<string name="you_have_not_favourites_yet">Henüz favorileriniz yok</string>
|
||||
<string name="add_to_favourites">Favoriniz</string>
|
||||
@@ -209,7 +209,7 @@
|
||||
<string name="new_version_s">Yeni sürüm: %s</string>
|
||||
<string name="protect_application">Uygulamayı koru</string>
|
||||
<string name="passwords_mismatch">Parolalar eşleşmiyor</string>
|
||||
<string name="right_to_left">Sağdan sola (←)</string>
|
||||
<string name="right_to_left">Sağdan-sola</string>
|
||||
<string name="create_category">Yeni kategori</string>
|
||||
<string name="file_not_found">Dosya bulunamadı</string>
|
||||
<string name="data_restored_success">Tüm veriler geri yüklendi</string>
|
||||
@@ -320,4 +320,66 @@
|
||||
<string name="invalid_domain_message">Geçersiz etki alanı</string>
|
||||
<string name="select_range">Aralık seç</string>
|
||||
<string name="not_found_404">İçerik bulunamadı veya kaldırıldı</string>
|
||||
<string name="text_shelf_holder_primary">Manganız burada görüntülenecek</string>
|
||||
<string name="canceled">İptal edilmiş</string>
|
||||
<string name="account_already_exists">Hesap zaten var</string>
|
||||
<string name="back">Geri</string>
|
||||
<string name="sync">Senkronizasyon</string>
|
||||
<string name="sync_title">Verini yedekle</string>
|
||||
<string name="email_enter_hint">Devam etmek için E-Postanızı girin</string>
|
||||
<string name="clear_all_history">Tüm gecmişi temizle</string>
|
||||
<string name="last_2_hours">Son 2 saat</string>
|
||||
<string name="history_cleared">Geçmiş temizlendi</string>
|
||||
<string name="manage">Yönet</string>
|
||||
<string name="no_bookmarks_yet">Yer işareti yok</string>
|
||||
<string name="no_bookmarks_summary">Manga okurken yer işareti oluşturabilirsiniz</string>
|
||||
<string name="bookmarks_removed">Yer işaretleri kaldırıldı</string>
|
||||
<string name="no_manga_sources">Manga kaynağı yok</string>
|
||||
<string name="no_manga_sources_text">Çevrimiçi manga okumak için manga kaynaklarını aktif edin</string>
|
||||
<string name="random">Rastgele</string>
|
||||
<string name="empty">Boş</string>
|
||||
<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>
|
||||
@@ -373,4 +373,16 @@
|
||||
<string name="removed_from_favourites">Видалено з уподобань</string>
|
||||
<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>
|
||||
27
app/src/main/res/values-zh-rCN/plurals.xml
Normal file
27
app/src/main/res/values-zh-rCN/plurals.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="items">
|
||||
<item quantity="other">%1$d 个项目</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="other">%1$d 个新章节</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="other">%1$d 章节</item>
|
||||
</plurals>
|
||||
<plurals name="chapters_from_x">
|
||||
<item quantity="other">来自 %2$d 中的 %1$d 个章节</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="other">%1$d 分钟前</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="other">%1$d 小时前</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="other">%1$d 天前</item>
|
||||
</plurals>
|
||||
<plurals name="pages">
|
||||
<item quantity="other">共 %1$d 页</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="settings">设置</string>
|
||||
<string name="local_storage">本地存储</string>
|
||||
<string name="favourites">喜欢</string>
|
||||
@@ -78,7 +78,7 @@
|
||||
<string name="sort_order">排序顺序</string>
|
||||
<string name="filter">过滤器</string>
|
||||
<string name="theme">主题</string>
|
||||
<string name="dark">黑暗</string>
|
||||
<string name="dark">深色</string>
|
||||
<string name="light">浅色</string>
|
||||
<string name="automatic">跟随系统</string>
|
||||
<string name="pages">页数</string>
|
||||
@@ -101,7 +101,7 @@
|
||||
<string name="cache">缓存</string>
|
||||
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
||||
<string name="standard">标准</string>
|
||||
<string name="webtoon">Webtoon</string>
|
||||
<string name="webtoon">条漫</string>
|
||||
<string name="read_mode">阅读模式</string>
|
||||
<string name="grid_size">网格大小</string>
|
||||
<string name="search_on_s">在%s上搜索</string>
|
||||
@@ -188,7 +188,7 @@
|
||||
<string name="checking_for_updates">检查更新…</string>
|
||||
<string name="update_check_failed">无法查找更新</string>
|
||||
<string name="no_update_available">没有更新</string>
|
||||
<string name="right_to_left">从右到左 (←)</string>
|
||||
<string name="right_to_left">从右到左</string>
|
||||
<string name="create_category">新类别</string>
|
||||
<string name="scale_mode">缩放模式</string>
|
||||
<string name="zoom_mode_fit_center">适应中心</string>
|
||||
@@ -249,7 +249,7 @@
|
||||
<string name="screenshots_block_all">始终阻止</string>
|
||||
<string name="suggestions">建议</string>
|
||||
<string name="suggestions_enable">启用建议</string>
|
||||
<string name="text_suggestion_holder">开始阅读漫画,你会得到个性化的建议</string>
|
||||
<string name="text_suggestion_holder">开始阅读漫画,你会得到个性化的建议</string>
|
||||
<string name="exclude_nsfw_from_suggestions">请勿推荐18+漫画</string>
|
||||
<string name="enabled">启用</string>
|
||||
<string name="disabled">禁用</string>
|
||||
@@ -290,7 +290,7 @@
|
||||
<string name="dns_over_https">DNS over HTTPS</string>
|
||||
<string name="default_mode">默认模式</string>
|
||||
<string name="detect_reader_mode">自动检测阅读器模式</string>
|
||||
<string name="detect_reader_mode_summary">自动检测漫画是否为网络漫画</string>
|
||||
<string name="detect_reader_mode_summary">自动检测漫画是否为条漫</string>
|
||||
<string name="disable_battery_optimization">禁用电池优化</string>
|
||||
<string name="disable_battery_optimization_summary">帮助进行背景更新检查</string>
|
||||
<string name="crash_text">出了点问题. 请向开发人员提交一份错误报告以帮助我们修复它.</string>
|
||||
@@ -313,4 +313,67 @@
|
||||
<string name="exclude_nsfw_from_history_summary">标记为NSFW的漫画将永远不会被添加到历史中你的进度也不会被保存</string>
|
||||
<string name="clear_cookies_summary">可以在出现一些问题时提供帮助. 所有授权将被视为无效</string>
|
||||
<string name="show_all">显示全部</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="invalid_domain_message">无效域名</string>
|
||||
<string name="text_shelf_holder_primary">此处将显示你的漫画</string>
|
||||
<string name="text_shelf_holder_secondary">在【浏览】页面搜索想要阅读的漫画</string>
|
||||
<string name="percent_string_pattern">%1$s%%</string>
|
||||
<string name="canceled">已取消</string>
|
||||
<string name="account_already_exists">账号已存在</string>
|
||||
<string name="back">返回</string>
|
||||
<string name="sync">同步</string>
|
||||
<string name="sync_title">同步您的数据</string>
|
||||
<string name="email_enter_hint">输入您的邮箱以继续</string>
|
||||
<string name="status_dropped">已放弃</string>
|
||||
<string name="select_range">选择范围</string>
|
||||
<string name="clear_all_history">清除所有历史</string>
|
||||
<string name="last_2_hours">过去2小时</string>
|
||||
<string name="bookmarks_removed">书签已移除</string>
|
||||
<string name="history_cleared">历史已清除</string>
|
||||
<string name="manage">管理</string>
|
||||
<string name="no_bookmarks_yet">还没有书签</string>
|
||||
<string name="no_bookmarks_summary">您可以在阅读漫画时创建书签</string>
|
||||
<string name="no_manga_sources">无漫画源</string>
|
||||
<string name="no_manga_sources_text">启用漫画源以在线阅读漫画</string>
|
||||
<string name="random">随机</string>
|
||||
<string name="categories_delete_confirm">您确定要删除选定的收藏夹吗?
|
||||
\n所有收藏夹中的漫画将丢失且无法恢复。</string>
|
||||
<string name="reorder">重新排序</string>
|
||||
<string name="empty">留空</string>
|
||||
<string name="changelog">变更日志</string>
|
||||
<string name="explore">浏览</string>
|
||||
<string name="automatic_scroll">自动滚动</string>
|
||||
<string name="off_short">关闭</string>
|
||||
<string name="reader_info_bar">在阅读器中显示信息栏</string>
|
||||
<string name="comics_archive">漫画压缩包</string>
|
||||
<string name="folder_with_images">图片文件夹</string>
|
||||
<string name="importing_manga">漫画导入中</string>
|
||||
<string name="seconds_pattern">%ss</string>
|
||||
<string name="reader_info_pattern">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>
|
||||
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d 的 %2$d 启用</string>
|
||||
<string name="size_s">大小:%s</string>
|
||||
<string name="confirm_exit">再按一次返回键退出</string>
|
||||
<string name="exit_confirmation_summary">按两次返回键退出应用</string>
|
||||
<string name="exit_confirmation">退出确认</string>
|
||||
<string name="saved_manga">已保存漫画</string>
|
||||
<string name="pages_cache">页面缓存</string>
|
||||
<string name="other_cache">其他缓存</string>
|
||||
<string name="storage_usage">存储占用</string>
|
||||
<string name="available">可用</string>
|
||||
<string name="enter_email_text">输入您的邮箱以继续</string>
|
||||
<string name="removed_from_favourites">从收藏中移除</string>
|
||||
<string name="removed_from_s">从 \"%s\" 中移除</string>
|
||||
<string name="options">选项</string>
|
||||
<string name="downloading_manga">下载中的漫画</string>
|
||||
<string name="incognito_mode">隐身模式</string>
|
||||
<string name="app_update_available_s">应用更新可用: %s</string>
|
||||
<string name="no_chapters">无章节</string>
|
||||
<string name="import_completed">导入完毕</string>
|
||||
<string name="import_completed_hint">您可以从存储中删除原文件以节省空间</string>
|
||||
<string name="import_will_start_soon">即将开始导入</string>
|
||||
<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