Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3e315e2a6 | ||
|
|
bfc733784f | ||
|
|
3ff25de252 | ||
|
|
3c726c1c56 | ||
|
|
9cb7ff691f | ||
|
|
645ae3124f | ||
|
|
a3d1922913 | ||
|
|
62d2ea8f15 | ||
|
|
823752076b | ||
|
|
3cbd392c72 | ||
|
|
57f62f5860 | ||
|
|
648fab6be5 | ||
|
|
817ae68e67 | ||
|
|
7c4b91ddc4 | ||
|
|
d54e015195 | ||
|
|
e369d1ba9d | ||
|
|
1a4358998b | ||
|
|
c53a833d9d | ||
|
|
afff700ad3 | ||
|
|
5bc00bc7f5 | ||
|
|
e2ace90cdb | ||
|
|
1afbd2b6a8 | ||
|
|
d36c5af0c4 | ||
|
|
705bb2b084 | ||
|
|
a208d13930 | ||
|
|
44d8861b7f | ||
|
|
9821f06ca1 | ||
|
|
92f9f56f59 | ||
|
|
424c4d8827 | ||
|
|
24cf2a2725 | ||
|
|
1a5c3c1f6f | ||
|
|
0b8fbf892a | ||
|
|
a2f9356b8a | ||
|
|
7003463bac | ||
|
|
7a663fa9c1 | ||
|
|
9cc1cdac62 | ||
|
|
8d7f44d2da | ||
|
|
930d4dfd83 | ||
|
|
290cb652ee |
@@ -19,8 +19,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 1004
|
||||
versionName = '8.0'
|
||||
versionCode = 1005
|
||||
versionName = '8.1'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||
ksp {
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
||||
|
||||
class KotatsuApp : BaseApp() {
|
||||
|
||||
@@ -67,7 +66,6 @@ class KotatsuApp : BaseApp() {
|
||||
setClassInstanceLimit(PagesCache::class.java, 1)
|
||||
setClassInstanceLimit(MangaLoaderContext::class.java, 1)
|
||||
setClassInstanceLimit(PageLoader::class.java, 1)
|
||||
setClassInstanceLimit(ReaderViewModel::class.java, 1)
|
||||
penaltyLog()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) {
|
||||
penaltyListener(notifier.executor, notifier)
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import leakcanary.LeakCanary
|
||||
import org.koitharu.kotatsu.KotatsuApp
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -24,6 +25,7 @@ class SettingsMenuProvider(
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
super.onPrepareMenu(menu)
|
||||
menu.findItem(R.id.action_leakcanary).isChecked = application.isLeakCanaryEnabled
|
||||
menu.findItem(R.id.action_ssiv_debug).isChecked = SubsamplingScaleImageView.isDebug
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
@@ -44,6 +46,13 @@ class SettingsMenuProvider(
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_ssiv_debug -> {
|
||||
val checked = !menuItem.isChecked
|
||||
menuItem.isChecked = checked
|
||||
SubsamplingScaleImageView.isDebug = checked
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_ssiv_debug"
|
||||
android:checkable="true"
|
||||
android:title="SSIV debug"
|
||||
app:showAsAction="never"
|
||||
tools:ignore="HardcodedText" />
|
||||
<item
|
||||
android:id="@+id/action_leakcanary"
|
||||
android:checkable="true"
|
||||
|
||||
@@ -28,8 +28,11 @@ abstract class BaseBrowserActivity : BaseActivity<ActivityBrowserBinding>(), Bro
|
||||
viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
|
||||
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)
|
||||
onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
||||
onCreate2(savedInstanceState)
|
||||
}
|
||||
|
||||
protected abstract fun onCreate2(savedInstanceState: Bundle?)
|
||||
|
||||
override fun onApplyWindowInsets(
|
||||
v: View,
|
||||
insets: WindowInsetsCompat
|
||||
|
||||
@@ -25,8 +25,7 @@ class BrowserActivity : BaseBrowserActivity() {
|
||||
@Inject
|
||||
lateinit var mangaRepositoryFactory: MangaRepository.Factory
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
override fun onCreate2(savedInstanceState: Bundle?) {
|
||||
setDisplayHomeAsUp(true, true)
|
||||
val mangaSource = MangaSource(intent?.getStringExtra(AppRouter.KEY_SOURCE))
|
||||
val repository = mangaRepositoryFactory.create(mangaSource) as? ParserMangaRepository
|
||||
|
||||
@@ -37,8 +37,7 @@ class CloudFlareActivity : BaseBrowserActivity(), CloudFlareCallback {
|
||||
|
||||
private lateinit var cfClient: CloudFlareClient
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
override fun onCreate2(savedInstanceState: Bundle?) {
|
||||
setDisplayHomeAsUp(true, true)
|
||||
val url = intent?.dataString
|
||||
if (url.isNullOrEmpty()) {
|
||||
|
||||
@@ -102,6 +102,9 @@ open class BaseApp : Application(), Configuration.Provider {
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base)
|
||||
if (ACRA.isACRASenderServiceProcess()) {
|
||||
return
|
||||
}
|
||||
initAcra {
|
||||
buildConfigClass = BuildConfig::class.java
|
||||
reportFormat = StringFormat.JSON
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.koitharu.kotatsu.core.image
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import androidx.collection.ArrayMap
|
||||
import coil3.memory.MemoryCache
|
||||
import coil3.request.SuccessResult
|
||||
import coil3.util.CoilUtils
|
||||
import kotlinx.parcelize.Parceler
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class CoilMemoryCacheKey(
|
||||
val data: MemoryCache.Key
|
||||
) : Parcelable {
|
||||
|
||||
companion object : Parceler<CoilMemoryCacheKey> {
|
||||
override fun CoilMemoryCacheKey.write(parcel: Parcel, flags: Int) = with(data) {
|
||||
parcel.writeString(key)
|
||||
parcel.writeInt(extras.size)
|
||||
for (entry in extras.entries) {
|
||||
parcel.writeString(entry.key)
|
||||
parcel.writeString(entry.value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun create(parcel: Parcel): CoilMemoryCacheKey = CoilMemoryCacheKey(
|
||||
MemoryCache.Key(
|
||||
key = parcel.readString().orEmpty(),
|
||||
extras = run {
|
||||
val size = parcel.readInt()
|
||||
val map = ArrayMap<String, String>(size)
|
||||
repeat(size) {
|
||||
map.put(parcel.readString(), parcel.readString())
|
||||
}
|
||||
map
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
fun from(view: View): CoilMemoryCacheKey? {
|
||||
return (CoilUtils.result(view) as? SuccessResult)?.memoryCacheKey?.let {
|
||||
CoilMemoryCacheKey(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,13 @@ import org.koitharu.kotatsu.core.parser.external.ExternalMangaSource
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayName
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.toLocale
|
||||
import org.koitharu.kotatsu.core.util.ext.toLocaleOrNull
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.splitTwoParts
|
||||
import com.google.android.material.R as materialR
|
||||
import java.util.Locale
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
data object LocalMangaSource : MangaSource {
|
||||
override val name = "LOCAL"
|
||||
@@ -79,6 +81,8 @@ tailrec fun MangaSource.unwrap(): MangaSource = if (this is MangaSourceInfo) {
|
||||
this
|
||||
}
|
||||
|
||||
fun MangaSource.getLocale(): Locale? = (unwrap() as? MangaParserSource)?.locale?.toLocaleOrNull()
|
||||
|
||||
fun MangaSource.getSummary(context: Context): String? = when (val source = unwrap()) {
|
||||
is MangaParserSource -> {
|
||||
val type = context.getString(source.contentType.titleResId)
|
||||
@@ -99,7 +103,7 @@ fun MangaSource.getTitle(context: Context): String = when (val source = unwrap()
|
||||
}
|
||||
|
||||
fun SpannableStringBuilder.appendNsfwLabel(context: Context) = inSpans(
|
||||
ForegroundColorSpan(context.getThemeColor(materialR.attr.colorError, Color.RED)),
|
||||
ForegroundColorSpan(context.getThemeColor(appcompatR.attr.colorError, Color.RED)),
|
||||
RelativeSizeSpan(0.74f),
|
||||
SuperscriptSpan(),
|
||||
) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity
|
||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.image.CoilMemoryCacheKey
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.model.MangaSourceInfo
|
||||
import org.koitharu.kotatsu.core.model.appUrl
|
||||
@@ -105,7 +106,7 @@ import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity
|
||||
import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity
|
||||
import java.io.File
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
class AppRouter private constructor(
|
||||
private val activity: FragmentActivity?,
|
||||
@@ -180,11 +181,12 @@ class AppRouter private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
fun openImage(url: String, source: MangaSource?, anchor: View? = null) {
|
||||
fun openImage(url: String, source: MangaSource?, anchor: View? = null, preview: CoilMemoryCacheKey? = null) {
|
||||
startActivity(
|
||||
Intent(contextOrNull(), ImageActivity::class.java)
|
||||
.setData(url.toUri())
|
||||
.putExtra(KEY_SOURCE, source?.name),
|
||||
.putExtra(KEY_SOURCE, source?.name)
|
||||
.putExtra(KEY_PREVIEW, preview),
|
||||
anchor?.let { scaleUpActivityOptionsOf(it) },
|
||||
)
|
||||
}
|
||||
@@ -412,7 +414,7 @@ class AppRouter private constructor(
|
||||
return
|
||||
}
|
||||
buildAlertDialog(contextOrNull() ?: return) {
|
||||
setIcon(context.getThemeDrawable(materialR.attr.actionModeShareDrawable))
|
||||
setIcon(context.getThemeDrawable(appcompatR.attr.actionModeShareDrawable))
|
||||
setTitle(R.string.share)
|
||||
setItems(
|
||||
arrayOf(
|
||||
@@ -587,8 +589,11 @@ class AppRouter private constructor(
|
||||
/** Private utils **/
|
||||
|
||||
private fun startActivity(intent: Intent, options: Bundle? = null) {
|
||||
fragment?.startActivity(intent, options)
|
||||
?: activity?.startActivity(intent, options)
|
||||
fragment?.also {
|
||||
if (it.host != null) {
|
||||
it.startActivity(intent, options)
|
||||
}
|
||||
} ?: activity?.startActivity(intent, options)
|
||||
}
|
||||
|
||||
private fun startActivitySafe(intent: Intent): Boolean = try {
|
||||
@@ -768,6 +773,7 @@ class AppRouter private constructor(
|
||||
const val KEY_MANGA = "manga"
|
||||
const val KEY_MANGA_LIST = "manga_list"
|
||||
const val KEY_PAGES = "pages"
|
||||
const val KEY_PREVIEW = "preview"
|
||||
const val KEY_QUERY = "query"
|
||||
const val KEY_READER_MODE = "reader_mode"
|
||||
const val KEY_SORT_ORDER = "sort_order"
|
||||
|
||||
@@ -105,6 +105,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
get() = prefs.getEnumValue(KEY_LIST_MODE_FAVORITES, listMode)
|
||||
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE_FAVORITES, value) }
|
||||
|
||||
val isTagsWarningsEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_TAGS_WARNINGS, true)
|
||||
|
||||
var isNsfwContentDisabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_DISABLE_NSFW, false)
|
||||
set(value) = prefs.edit { putBoolean(KEY_DISABLE_NSFW, value) }
|
||||
@@ -359,6 +362,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
val isSuggestionsExcludeNsfw: Boolean
|
||||
get() = prefs.getBoolean(KEY_SUGGESTIONS_EXCLUDE_NSFW, false)
|
||||
|
||||
val isSuggestionsIncludeDisabledSources: Boolean
|
||||
get() = prefs.getBoolean(KEY_SUGGESTIONS_DISABLED_SOURCES, false)
|
||||
|
||||
val isSuggestionsNotificationAvailable: Boolean
|
||||
get() = prefs.getBoolean(KEY_SUGGESTIONS_NOTIFICATIONS, false)
|
||||
|
||||
@@ -658,6 +664,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_SUGGESTIONS_WIFI_ONLY = "suggestions_wifi"
|
||||
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"
|
||||
const val KEY_SUGGESTIONS_EXCLUDE_TAGS = "suggestions_exclude_tags"
|
||||
const val KEY_SUGGESTIONS_DISABLED_SOURCES = "suggestions_disabled_sources"
|
||||
const val KEY_SUGGESTIONS_NOTIFICATIONS = "suggestions_notifications"
|
||||
const val KEY_SHIKIMORI = "shikimori"
|
||||
const val KEY_ANILIST = "anilist"
|
||||
@@ -728,6 +735,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_BACKUP_TG_ENABLED = "backup_periodic_tg_enabled"
|
||||
const val KEY_BACKUP_TG_CHAT = "backup_periodic_tg_chat_id"
|
||||
const val KEY_MANGA_LIST_BADGES = "manga_list_badges"
|
||||
const val KEY_TAGS_WARNINGS = "tags_warnings"
|
||||
|
||||
// keys for non-persistent preferences
|
||||
const val KEY_APP_VERSION = "app_version"
|
||||
|
||||
@@ -103,7 +103,7 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
supportActionBar?.run {
|
||||
setDisplayHomeAsUpEnabled(isEnabled)
|
||||
if (showUpAsClose) {
|
||||
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
||||
setHomeAsUpIndicator(materialR.drawable.ic_clear_black_24)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.isLayoutReversed
|
||||
import org.koitharu.kotatsu.databinding.FastScrollerBinding
|
||||
import kotlin.math.roundToInt
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
private const val SCROLLBAR_HIDE_DELAY = 1000L
|
||||
@@ -132,7 +133,7 @@ class FastScroller @JvmOverloads constructor(
|
||||
clipChildren = false
|
||||
orientation = HORIZONTAL
|
||||
|
||||
@ColorInt var bubbleColor = context.getThemeColor(materialR.attr.colorControlNormal, Color.DKGRAY)
|
||||
@ColorInt var bubbleColor = context.getThemeColor(appcompatR.attr.colorControlNormal, Color.DKGRAY)
|
||||
@ColorInt var handleColor = bubbleColor
|
||||
@ColorInt var trackColor = context.getThemeColor(materialR.attr.colorOutline, Color.LTGRAY)
|
||||
@ColorInt var textColor = context.getThemeColor(android.R.attr.textColorPrimaryInverse, Color.WHITE)
|
||||
|
||||
@@ -5,6 +5,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class RetainedLifecycleCoroutineScope(
|
||||
@@ -14,7 +15,9 @@ class RetainedLifecycleCoroutineScope(
|
||||
override val coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
|
||||
|
||||
init {
|
||||
lifecycle.addOnClearedListener(this)
|
||||
launch(Dispatchers.Main.immediate) {
|
||||
lifecycle.addOnClearedListener(this@RetainedLifecycleCoroutineScope)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.koitharu.kotatsu.core.ui.image.AnimatedPlaceholderDrawable
|
||||
import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): ImageRequest.Builder? {
|
||||
@@ -112,7 +113,7 @@ fun ImageRequest.Builder.bookmarkExtra(bookmark: Bookmark): ImageRequest.Builder
|
||||
fun ImageRequest.Builder.defaultPlaceholders(context: Context): ImageRequest.Builder {
|
||||
val errorColor = ColorUtils.blendARGB(
|
||||
context.getThemeColor(materialR.attr.colorErrorContainer),
|
||||
context.getThemeColor(materialR.attr.colorBackgroundFloating),
|
||||
context.getThemeColor(appcompatR.attr.colorBackgroundFloating),
|
||||
0.25f,
|
||||
)
|
||||
return placeholder(AnimatedPlaceholderDrawable(context))
|
||||
|
||||
@@ -87,6 +87,10 @@ fun <T, R> Collection<T>.mapSortedByCount(isDescending: Boolean = true, mapper:
|
||||
return sorted.map { it.first }
|
||||
}
|
||||
|
||||
fun Collection<CharSequence?>.contains(element: CharSequence?, ignoreCase: Boolean): Boolean = any { x ->
|
||||
(x == null && element == null) || (x != null && element != null && x.contains(element, ignoreCase))
|
||||
}
|
||||
|
||||
fun Collection<CharSequence?>.indexOfContains(element: CharSequence?, ignoreCase: Boolean): Int = indexOfFirst { x ->
|
||||
(x == null && element == null) || (x != null && element != null && x.contains(element, ignoreCase))
|
||||
}
|
||||
|
||||
@@ -21,7 +21,13 @@ inline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {
|
||||
|
||||
fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException()
|
||||
|
||||
fun String.toLocale() = Locale(this)
|
||||
fun String.toLocale(): Locale = Locale.forLanguageTag(this)
|
||||
|
||||
fun String.toLocaleOrNull() = if (isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
toLocale().takeUnless { it.displayName == this }
|
||||
}
|
||||
|
||||
fun Locale?.getDisplayName(context: Context): String = when (this) {
|
||||
null -> context.getString(R.string.all_languages)
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.util.ext
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.Px
|
||||
import androidx.core.util.TypedValueCompat
|
||||
@@ -30,7 +31,10 @@ fun Context.getSystemBoolean(resName: String, fallback: Boolean): Boolean {
|
||||
fun Resources.getQuantityStringSafe(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any): String = try {
|
||||
getQuantityString(resId, quantity, *formatArgs)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
e.report(silent = true)
|
||||
e.printStackTraceDebug()
|
||||
formatArgs.firstOrNull()?.toString() ?: quantity.toString()
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.VANILLA_ICE_CREAM) { // known issue
|
||||
e.printStackTraceDebug()
|
||||
formatArgs.firstOrNull()?.toString() ?: quantity.toString()
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.details.data
|
||||
|
||||
import org.koitharu.kotatsu.core.model.getLocale
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -7,6 +8,7 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.reader.data.filterChapters
|
||||
import java.util.Locale
|
||||
|
||||
data class MangaDetails(
|
||||
private val manga: Manga,
|
||||
@@ -39,6 +41,13 @@ data class MangaDetails(
|
||||
|
||||
fun toManga() = manga
|
||||
|
||||
fun getLocale(): Locale? {
|
||||
findAppropriateLocale(chapters.keys.singleOrNull())?.let {
|
||||
return it
|
||||
}
|
||||
return manga.source.getLocale()
|
||||
}
|
||||
|
||||
fun filterChapters(branch: String?) = MangaDetails(
|
||||
manga = manga.filterChapters(branch),
|
||||
localManga = localManga?.run {
|
||||
@@ -69,4 +78,16 @@ data class MangaDetails(
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun findAppropriateLocale(name: String?): Locale? {
|
||||
if (name.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return Locale.getAvailableLocales().find { lc ->
|
||||
name.contains(lc.getDisplayName(lc), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayName(Locale.ENGLISH), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayLanguage(lc), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayLanguage(Locale.ENGLISH), ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.text.Spannable
|
||||
import android.text.TextPaint
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
|
||||
class AuthorSpan(private val listener: OnAuthorClickListener) : ClickableSpan() {
|
||||
|
||||
override fun onClick(widget: View) {
|
||||
val text = (widget as? TextView)?.text as? Spannable ?: return
|
||||
val start = text.getSpanStart(this)
|
||||
val end = text.getSpanEnd(this)
|
||||
val selected = text.substring(start, end).trim()
|
||||
if (selected.isNotEmpty()) {
|
||||
listener.onAuthorClick(selected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
ds.setColor(ds.linkColor)
|
||||
}
|
||||
|
||||
fun interface OnAuthorClickListener {
|
||||
|
||||
fun onAuthorClick(author: String)
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.SpannedString
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.core.text.method.LinkMovementMethodCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isGone
|
||||
@@ -43,6 +46,7 @@ import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.core.image.CoilMemoryCacheKey
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.model.LocalMangaSource
|
||||
import org.koitharu.kotatsu.core.model.UnknownMangaSource
|
||||
@@ -100,10 +104,12 @@ import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaListModel
|
||||
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
|
||||
import org.koitharu.kotatsu.parsers.model.ContentRating
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
@@ -115,7 +121,7 @@ class DetailsActivity :
|
||||
View.OnClickListener,
|
||||
View.OnLayoutChangeListener, ViewTreeObserver.OnDrawListener,
|
||||
ChipsView.OnChipClickListener, OnListItemClickListener<Bookmark>,
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
SwipeRefreshLayout.OnRefreshListener, AuthorSpan.OnAuthorClickListener {
|
||||
|
||||
@Inject
|
||||
lateinit var shortcutManager: AppShortcutManager
|
||||
@@ -135,7 +141,6 @@ class DetailsActivity :
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
viewBinding.chipFavorite.setOnClickListener(this)
|
||||
infoBinding.textViewLocal.setOnClickListener(this)
|
||||
infoBinding.textViewAuthor.setOnClickListener(this)
|
||||
infoBinding.textViewSource.setOnClickListener(this)
|
||||
viewBinding.imageViewCover.setOnClickListener(this)
|
||||
viewBinding.textViewTitle.setOnClickListener(this)
|
||||
@@ -145,6 +150,7 @@ class DetailsActivity :
|
||||
viewBinding.textViewDescription.addOnLayoutChangeListener(this)
|
||||
viewBinding.swipeRefreshLayout.setOnRefreshListener(this)
|
||||
viewBinding.textViewDescription.viewTreeObserver.addOnDrawListener(this)
|
||||
infoBinding.textViewAuthor.movementMethod = LinkMovementMethodCompat.getInstance()
|
||||
viewBinding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()
|
||||
viewBinding.chipsTags.onChipClickListener = this
|
||||
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
|
||||
@@ -179,16 +185,6 @@ class DetailsActivity :
|
||||
viewModel.isStatsAvailable.observe(this, menuInvalidator)
|
||||
viewModel.remoteManga.observe(this, menuInvalidator)
|
||||
viewModel.tags.observe(this, ::onTagsChanged)
|
||||
viewModel.branches.observe(this) {
|
||||
val branch = it.singleOrNull()
|
||||
infoBinding.textViewTranslation.textAndVisible = branch?.name
|
||||
infoBinding.textViewTranslation.drawableStart = branch?.locale?.let {
|
||||
LocaleUtils.getEmojiFlag(it)
|
||||
}?.let {
|
||||
TextDrawable.compound(infoBinding.textViewTranslation, it)
|
||||
}
|
||||
infoBinding.textViewTranslationLabel.isVisible = infoBinding.textViewTranslation.isVisible
|
||||
}
|
||||
viewModel.chapters.observe(this, PrefetchObserver(this))
|
||||
viewModel.onDownloadStarted
|
||||
.filterNot { appRouter.isChapterPagesSheetShown() }
|
||||
@@ -202,36 +198,31 @@ class DetailsActivity :
|
||||
addMenuProvider(menuProvider)
|
||||
}
|
||||
|
||||
override fun isNsfwContent(): Flow<Boolean> = viewModel.manga.map { it?.isNsfw == true }
|
||||
override fun isNsfwContent(): Flow<Boolean> = viewModel.manga.map { it?.contentRating == ContentRating.ADULT }
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.textView_author -> {
|
||||
val manga = viewModel.manga.value
|
||||
val author = manga?.author ?: return
|
||||
router.showAuthorDialog(author, manga.source)
|
||||
}
|
||||
|
||||
R.id.textView_source -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.openList(manga.source, null, null)
|
||||
}
|
||||
|
||||
R.id.textView_local -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.showLocalInfoDialog(manga)
|
||||
}
|
||||
|
||||
R.id.chip_favorite -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.showFavoriteDialog(manga)
|
||||
}
|
||||
|
||||
R.id.imageView_cover -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.openImage(
|
||||
url = viewModel.coverUrl.value ?: return,
|
||||
source = manga.source,
|
||||
preview = CoilMemoryCacheKey.from(viewBinding.imageViewCover),
|
||||
anchor = v,
|
||||
)
|
||||
}
|
||||
@@ -251,17 +242,17 @@ class DetailsActivity :
|
||||
}
|
||||
|
||||
R.id.button_scrobbling_more -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.showScrobblingSelectorSheet(manga, null)
|
||||
}
|
||||
|
||||
R.id.button_related_more -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.openRelated(manga)
|
||||
}
|
||||
|
||||
R.id.textView_title -> {
|
||||
val title = viewModel.manga.value?.title?.nullIfEmpty() ?: return
|
||||
val title = viewModel.getMangaOrNull()?.title?.nullIfEmpty() ?: return
|
||||
buildAlertDialog(this) {
|
||||
setMessage(title)
|
||||
setNegativeButton(R.string.close, null)
|
||||
@@ -273,6 +264,10 @@ class DetailsActivity :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAuthorClick(author: String) {
|
||||
router.showAuthorDialog(author, viewModel.getMangaOrNull()?.source ?: return)
|
||||
}
|
||||
|
||||
override fun onChipClick(chip: Chip, data: Any?) {
|
||||
val tag = data as? MangaTag ?: return
|
||||
router.showTagDialog(tag)
|
||||
@@ -306,7 +301,6 @@ class DetailsActivity :
|
||||
oldBottom: Int
|
||||
) {
|
||||
with(viewBinding) {
|
||||
buttonDescriptionMore.isVisible = textViewDescription.isTextTruncated
|
||||
containerBottomSheet?.let { sheet ->
|
||||
val peekHeight = BottomSheetBehavior.from(sheet).peekHeight
|
||||
if (scrollView.paddingBottom != peekHeight) {
|
||||
@@ -407,11 +401,21 @@ class DetailsActivity :
|
||||
with(viewBinding) {
|
||||
textViewTitle.text = manga.title
|
||||
textViewSubtitle.textAndVisible = manga.altTitles.joinToString("\n")
|
||||
textViewNsfw.isVisible = manga.isNsfw
|
||||
textViewNsfw16.isVisible = manga.contentRating == ContentRating.SUGGESTIVE
|
||||
textViewNsfw18.isVisible = manga.contentRating == ContentRating.ADULT
|
||||
textViewDescription.text = details.description.ifNullOrEmpty { getString(R.string.no_description) }
|
||||
}
|
||||
with(infoBinding) {
|
||||
textViewAuthor.textAndVisible = manga.author
|
||||
val translation = details.getLocale()
|
||||
infoBinding.textViewTranslation.textAndVisible = translation?.getDisplayLanguage(translation)
|
||||
?.toTitleCase(translation)
|
||||
infoBinding.textViewTranslation.drawableStart = translation?.let {
|
||||
LocaleUtils.getEmojiFlag(it)
|
||||
}?.let {
|
||||
TextDrawable.compound(infoBinding.textViewTranslation, it)
|
||||
}
|
||||
infoBinding.textViewTranslationLabel.isVisible = infoBinding.textViewTranslation.isVisible
|
||||
textViewAuthor.textAndVisible = manga.getAuthorsString()
|
||||
textViewAuthorLabel.isVisible = textViewAuthor.isVisible
|
||||
if (manga.hasRating) {
|
||||
ratingBarRating.rating = manga.rating * ratingBarRating.numStars
|
||||
@@ -533,6 +537,24 @@ class DetailsActivity :
|
||||
return getString(R.string.chapters_time_pattern, this, timeFormatted)
|
||||
}
|
||||
|
||||
private fun Manga.getAuthorsString(): SpannedString? {
|
||||
if (authors.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
return buildSpannedString {
|
||||
authors.forEach { a ->
|
||||
if (a.isNotEmpty()) {
|
||||
if (isNotEmpty()) {
|
||||
append(", ")
|
||||
}
|
||||
inSpans(AuthorSpan(this@DetailsActivity)) {
|
||||
append(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.nullIfEmpty()
|
||||
}
|
||||
|
||||
private class PrefetchObserver(
|
||||
private val context: Context,
|
||||
) : FlowCollector<List<ChapterListItem>?> {
|
||||
|
||||
@@ -69,7 +69,7 @@ class DetailsMenuProvider(
|
||||
}
|
||||
|
||||
R.id.action_online -> {
|
||||
router.openDetails(manga)
|
||||
router.openDetails(viewModel.remoteManga.value ?: return false)
|
||||
}
|
||||
|
||||
R.id.action_related -> {
|
||||
|
||||
@@ -15,16 +15,17 @@ import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.core.util.ext.getItem
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
||||
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val radius = context.resources.getDimension(materialR.dimen.abc_control_corner_material)
|
||||
private val radius = context.resources.getDimension(appcompatR.dimen.abc_control_corner_material)
|
||||
private val checkIcon = ContextCompat.getDrawable(context, materialR.drawable.ic_mtrl_checked_circle)
|
||||
private val iconOffset = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_offset)
|
||||
private val iconSize = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_size)
|
||||
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
|
||||
private val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)
|
||||
private val fillColor = ColorUtils.setAlphaComponent(
|
||||
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
|
||||
0x74,
|
||||
@@ -32,7 +33,7 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
|
||||
|
||||
init {
|
||||
paint.color = ColorUtils.setAlphaComponent(
|
||||
context.getThemeColor(materialR.attr.colorPrimary, Color.DKGRAY),
|
||||
context.getThemeColor(appcompatR.attr.colorPrimary, Color.DKGRAY),
|
||||
98,
|
||||
)
|
||||
paint.style = Paint.Style.FILL
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.details.ui.model
|
||||
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import java.util.Locale
|
||||
|
||||
data class MangaBranch(
|
||||
val name: String?,
|
||||
@@ -11,8 +10,6 @@ data class MangaBranch(
|
||||
val isCurrent: Boolean,
|
||||
) : ListModel {
|
||||
|
||||
val locale: Locale? by lazy(::findAppropriateLocale)
|
||||
|
||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||
return other is MangaBranch && other.name == name
|
||||
}
|
||||
@@ -28,16 +25,4 @@ data class MangaBranch(
|
||||
override fun toString(): String {
|
||||
return "$name: $count"
|
||||
}
|
||||
|
||||
private fun findAppropriateLocale(): Locale? {
|
||||
if (name.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return Locale.getAvailableLocales().find { lc ->
|
||||
name.contains(lc.getDisplayName(lc), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayName(Locale.ENGLISH), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayLanguage(lc), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayLanguage(Locale.ENGLISH), ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -27,7 +26,6 @@ import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
|
||||
import org.koitharu.kotatsu.core.util.ext.findCurrentPagerFragment
|
||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
||||
import org.koitharu.kotatsu.core.util.ext.menuView
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
@@ -106,9 +104,6 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(),
|
||||
}
|
||||
val binding = viewBinding ?: return
|
||||
val isActionModeStarted = actionModeDelegate?.isActionModeStarted == true
|
||||
if (sheet.context.isAnimationsEnabled) {
|
||||
TransitionManager.beginDelayedTransition(binding.toolbar)
|
||||
}
|
||||
binding.toolbar.menuView?.isVisible = newState == STATE_EXPANDED && !isActionModeStarted
|
||||
binding.splitButtonRead.isVisible = newState != STATE_EXPANDED && !isActionModeStarted
|
||||
&& viewModel is DetailsViewModel
|
||||
|
||||
@@ -71,7 +71,7 @@ class BookmarksViewModel @Inject constructor(
|
||||
if (b.isNullOrEmpty()) {
|
||||
continue
|
||||
}
|
||||
result += ListHeader(chapter.name)
|
||||
result += ListHeader(chapter)
|
||||
result.addAll(b)
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
|
||||
@@ -3,10 +3,12 @@ package org.koitharu.kotatsu.details.ui.pager.pages
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.transformations
|
||||
import coil3.size.Scale
|
||||
import coil3.size.Size
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
@@ -43,6 +45,7 @@ fun pageThumbnailAD(
|
||||
size(thumbSize)
|
||||
scale(Scale.FILL)
|
||||
allowRgb565(true)
|
||||
transformations(TrimTransformation())
|
||||
decodeRegion(0)
|
||||
mangaSourceExtra(item.page.source)
|
||||
enqueueWith(coil)
|
||||
|
||||
@@ -130,7 +130,7 @@ class PagesViewModel @Inject constructor(
|
||||
for (page in snapshot) {
|
||||
if (page.chapterId != previousChapterId) {
|
||||
chaptersLoader.peekChapter(page.chapterId)?.let {
|
||||
add(ListHeader(it.name))
|
||||
add(ListHeader(it))
|
||||
}
|
||||
previousChapterId = page.chapterId
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
data class DownloadItemModel(
|
||||
val id: UUID,
|
||||
@@ -62,7 +62,7 @@ data class DownloadItemModel(
|
||||
fun getErrorMessage(context: Context): CharSequence? = if (error != null) {
|
||||
buildSpannedString {
|
||||
bold {
|
||||
color(context.getThemeColor(materialR.attr.colorError, Color.RED)) {
|
||||
color(context.getThemeColor(appcompatR.attr.colorError, Color.RED)) {
|
||||
append(error)
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ data class DownloadItemModel(
|
||||
}
|
||||
|
||||
override fun compareTo(other: DownloadItemModel): Int {
|
||||
return timestamp.compareTo(other.timestamp)
|
||||
return timestamp compareTo other.timestamp
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.core.util.ext.getItem
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class DownloadsSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
||||
@@ -23,7 +24,7 @@ class DownloadsSelectionDecoration(context: Context) : AbstractSelectionItemDeco
|
||||
private val checkIcon = ContextCompat.getDrawable(context, materialR.drawable.ic_mtrl_checked_circle)
|
||||
private val iconOffset = context.resources.getDimensionPixelOffset(R.dimen.card_indicator_offset)
|
||||
private val iconSize = context.resources.getDimensionPixelOffset(R.dimen.card_indicator_size)
|
||||
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
|
||||
private val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)
|
||||
private val fillColor = ColorUtils.setAlphaComponent(
|
||||
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
|
||||
0x74,
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.room.withTransaction
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -354,7 +353,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
.conflate()
|
||||
}
|
||||
|
||||
private fun getExternalSources() = context.packageManager.queryIntentContentProviders(
|
||||
fun getExternalSources(): List<ExternalMangaSource> = context.packageManager.queryIntentContentProviders(
|
||||
Intent("app.kotatsu.parser.PROVIDE_MANGA"), 0,
|
||||
).map { resolveInfo ->
|
||||
ExternalMangaSource(
|
||||
|
||||
@@ -36,15 +36,15 @@ class RecoverMangaUseCase @Inject constructor(
|
||||
) = Manga(
|
||||
id = broken.id,
|
||||
title = current.title,
|
||||
altTitle = current.altTitle,
|
||||
altTitles = current.altTitles,
|
||||
url = current.url,
|
||||
publicUrl = current.publicUrl,
|
||||
rating = current.rating,
|
||||
isNsfw = current.isNsfw,
|
||||
contentRating = current.contentRating,
|
||||
coverUrl = current.coverUrl,
|
||||
tags = current.tags,
|
||||
state = current.state,
|
||||
author = current.author,
|
||||
authors = current.authors,
|
||||
largeCoverUrl = current.largeCoverUrl,
|
||||
description = current.description,
|
||||
chapters = current.chapters,
|
||||
|
||||
@@ -14,12 +14,13 @@ import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.core.util.ext.getItem
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class SourceSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
||||
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
|
||||
private val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)
|
||||
private val fillColor = ColorUtils.setAlphaComponent(
|
||||
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
|
||||
0x74,
|
||||
|
||||
@@ -13,13 +13,14 @@ import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.core.util.ext.getItem
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class CategoriesSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
||||
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val radius = context.resources.getDimension(R.dimen.list_selector_corner)
|
||||
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
|
||||
private val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)
|
||||
private val fillColor = ColorUtils.setAlphaComponent(
|
||||
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
|
||||
0x74,
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.util.ext.setThemeTextAppearance
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ViewFilterFieldBinding
|
||||
import java.util.LinkedList
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class FilterFieldLayout @JvmOverloads constructor(
|
||||
@@ -100,7 +101,7 @@ class FilterFieldLayout @JvmOverloads constructor(
|
||||
label.drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_error_small)
|
||||
TextViewCompat.setCompoundDrawableTintList(
|
||||
label,
|
||||
context.getThemeColorStateList(materialR.attr.colorControlNormal),
|
||||
context.getThemeColorStateList(appcompatR.attr.colorControlNormal),
|
||||
)
|
||||
addView(label)
|
||||
errorView = label
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import javax.inject.Inject
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
class FilterHeaderProducer @Inject constructor(
|
||||
private val searchRepository: MangaSearchRepository,
|
||||
@@ -129,7 +129,7 @@ class FilterHeaderProducer @Inject constructor(
|
||||
result.addFirst(
|
||||
ChipsView.ChipModel(
|
||||
title = snapshot.query,
|
||||
icon = materialR.drawable.abc_ic_search_api_material,
|
||||
icon = appcompatR.drawable.abc_ic_search_api_material,
|
||||
isCloseable = true,
|
||||
data = snapshot.query,
|
||||
),
|
||||
|
||||
@@ -11,21 +11,20 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import coil3.Image
|
||||
import coil3.ImageLoader
|
||||
import coil3.asDrawable
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.ErrorResult
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.SuccessResult
|
||||
import coil3.request.lifecycle
|
||||
import coil3.target.ViewTarget
|
||||
import coil3.target.GenericViewTarget
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||
import org.koitharu.kotatsu.core.image.CoilMemoryCacheKey
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
@@ -36,6 +35,7 @@ import org.koitharu.kotatsu.core.util.ext.end
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayIcon
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
@@ -44,7 +44,7 @@ import org.koitharu.kotatsu.core.util.ext.start
|
||||
import org.koitharu.kotatsu.databinding.ActivityImageBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemErrorStateBinding
|
||||
import javax.inject.Inject
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
||||
@@ -63,7 +63,6 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
||||
setContentView(ActivityImageBinding.inflate(layoutInflater))
|
||||
viewBinding.buttonBack.setOnClickListener(this)
|
||||
viewBinding.buttonMenu.setOnClickListener(this)
|
||||
val imageUrl = requireNotNull(intent.data)
|
||||
|
||||
val menuProvider = ImageMenuProvider(
|
||||
activity = this,
|
||||
@@ -74,14 +73,14 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
||||
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
|
||||
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.root, null))
|
||||
viewModel.onImageSaved.observeEvent(this, ::onImageSaved)
|
||||
loadImage(imageUrl)
|
||||
loadImage()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_back -> dispatchNavigateUp()
|
||||
R.id.button_menu -> menuMediator.onLongClick(v)
|
||||
else -> loadImage(intent.data)
|
||||
else -> loadImage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,10 +121,11 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
||||
return insets.consumeAll(typeMask)
|
||||
}
|
||||
|
||||
private fun loadImage(url: Uri?) {
|
||||
private fun loadImage() {
|
||||
ImageRequest.Builder(this)
|
||||
.data(url)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.data(intent.data)
|
||||
.memoryCacheKey(intent.getParcelableExtraCompat<CoilMemoryCacheKey>(AppRouter.KEY_PREVIEW)?.data)
|
||||
.memoryCachePolicy(CachePolicy.READ_ONLY)
|
||||
.lifecycle(this)
|
||||
.listener(this)
|
||||
.mangaSourceExtra(MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE)))
|
||||
@@ -147,22 +147,24 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
||||
button.setImageDrawable(
|
||||
CircularProgressDrawable(this).also {
|
||||
it.setStyle(CircularProgressDrawable.LARGE)
|
||||
it.setColorSchemeColors(getThemeColor(materialR.attr.colorControlNormal))
|
||||
it.setColorSchemeColors(getThemeColor(appcompatR.attr.colorControlNormal))
|
||||
it.start()
|
||||
},
|
||||
)
|
||||
} else {
|
||||
button.setImageResource(materialR.drawable.abc_ic_menu_overflow_material)
|
||||
button.setImageResource(appcompatR.drawable.abc_ic_menu_overflow_material)
|
||||
}
|
||||
}
|
||||
|
||||
private class SsivTarget(
|
||||
override val view: SubsamplingScaleImageView,
|
||||
) : ViewTarget<SubsamplingScaleImageView> {
|
||||
) : GenericViewTarget<SubsamplingScaleImageView>() {
|
||||
|
||||
override fun onError(error: Image?) = setDrawable(error?.asDrawable(view.resources))
|
||||
|
||||
override fun onSuccess(result: Image) = setDrawable(result.asDrawable(view.resources))
|
||||
override var drawable: Drawable? = null
|
||||
set(value) {
|
||||
field = value
|
||||
setImageDrawable(value)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return (this === other) || (other is SsivTarget && view == other.view)
|
||||
@@ -172,7 +174,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(),
|
||||
|
||||
override fun toString() = "SsivTarget(view=$view)"
|
||||
|
||||
private fun setDrawable(drawable: Drawable?) {
|
||||
private fun setImageDrawable(drawable: Drawable?) {
|
||||
if (drawable != null) {
|
||||
view.setImage(ImageSource.bitmap(drawable.toBitmap()))
|
||||
} else {
|
||||
|
||||
@@ -140,7 +140,7 @@ class MangaListMapper @Inject constructor(
|
||||
|
||||
@ColorRes
|
||||
private fun getTagTint(tag: MangaTag): Int {
|
||||
return if (tag.title.lowercase() in dict) {
|
||||
return if (settings.isTagsWarningsEnabled && tag.title.lowercase() in dict) {
|
||||
R.color.warning
|
||||
} else {
|
||||
0
|
||||
@@ -148,7 +148,7 @@ class MangaListMapper @Inject constructor(
|
||||
}
|
||||
|
||||
private fun readTagsDict(context: Context): ScatterSet<String> =
|
||||
context.resources.openRawResource(R.raw.tags_redlist).use {
|
||||
context.resources.openRawResource(R.raw.tags_warnlist).use {
|
||||
val set = MutableScatterSet<String>()
|
||||
it.bufferedReader().forEachLine { x ->
|
||||
val line = x.trim()
|
||||
|
||||
@@ -15,12 +15,13 @@ import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.core.util.ext.getItem
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaListModel
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
open class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
||||
|
||||
protected val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
protected val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
|
||||
protected val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)
|
||||
protected val fillColor = ColorUtils.setAlphaComponent(
|
||||
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
|
||||
0x74,
|
||||
|
||||
@@ -32,7 +32,7 @@ fun mangaListDetailedItemAD(
|
||||
|
||||
bind { payloads ->
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.textViewAuthor.textAndVisible = item.manga.author
|
||||
binding.textViewAuthor.textAndVisible = item.manga.authors.joinToString(", ")
|
||||
binding.progressView.setProgress(
|
||||
value = item.progress,
|
||||
animate = ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads,
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.koitharu.kotatsu.list.ui.model
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import org.koitharu.kotatsu.core.model.getLocalizedTitle
|
||||
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
|
||||
data class ListHeader private constructor(
|
||||
private val textRaw: Any,
|
||||
@@ -25,6 +27,13 @@ data class ListHeader private constructor(
|
||||
badge: String? = null,
|
||||
) : this(textRaw = textRes, buttonTextRes, payload, badge)
|
||||
|
||||
constructor(
|
||||
chapter: MangaChapter,
|
||||
@StringRes buttonTextRes: Int = 0,
|
||||
payload: Any? = null,
|
||||
badge: String? = null,
|
||||
) : this(textRaw = chapter, buttonTextRes, payload, badge)
|
||||
|
||||
constructor(
|
||||
dateTimeAgo: DateTimeAgo,
|
||||
@StringRes buttonTextRes: Int = 0,
|
||||
@@ -36,6 +45,7 @@ data class ListHeader private constructor(
|
||||
is CharSequence -> textRaw
|
||||
is Int -> if (textRaw != 0) context.getString(textRaw) else null
|
||||
is DateTimeAgo -> textRaw.format(context)
|
||||
is MangaChapter -> textRaw.getLocalizedTitle(context.resources)
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.local.data
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -17,7 +16,6 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.AlphanumComparator
|
||||
import org.koitharu.kotatsu.core.util.ext.deleteAwait
|
||||
import org.koitharu.kotatsu.core.util.ext.isWriteable
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.takeIfWriteable
|
||||
import org.koitharu.kotatsu.core.util.ext.withChildren
|
||||
@@ -45,6 +43,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val MAX_PARALLELISM = 4
|
||||
private const val FILENAME_SKIP = ".notamanga"
|
||||
|
||||
@Singleton
|
||||
class LocalMangaRepository @Inject constructor(
|
||||
@@ -140,7 +139,7 @@ class LocalMangaRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun delete(manga: Manga): Boolean {
|
||||
val file = Uri.parse(manga.url).toFile()
|
||||
val file = manga.url.toUri().toFile()
|
||||
val result = file.deleteAwait()
|
||||
if (result) {
|
||||
localMangaIndex.delete(manga.id)
|
||||
@@ -256,8 +255,10 @@ class LocalMangaRepository @Inject constructor(
|
||||
private suspend fun getAllFiles() = storageManager.getReadableDirs()
|
||||
.asSequence()
|
||||
.flatMap { dir ->
|
||||
dir.withChildren { children -> children.filterNot { it.isHidden }.toList() }
|
||||
dir.withChildren { children -> children.filterNot { it.isHidden || it.shouldSkip() }.toList() }
|
||||
}
|
||||
|
||||
private fun Collection<LocalManga>.unwrap(): List<Manga> = map { it.manga }
|
||||
|
||||
private fun File.shouldSkip(): Boolean = isDirectory && File(this, FILENAME_SKIP).exists()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.local.domain.model
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import org.koitharu.kotatsu.core.util.ext.contains
|
||||
import org.koitharu.kotatsu.core.util.ext.creationTime
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
@@ -26,8 +27,8 @@ data class LocalManga(
|
||||
|
||||
fun isMatchesQuery(query: String): Boolean {
|
||||
return manga.title.contains(query, ignoreCase = true) ||
|
||||
manga.altTitle?.contains(query, ignoreCase = true) == true ||
|
||||
manga.author?.contains(query, ignoreCase = true) == true
|
||||
manga.altTitles.contains(query, ignoreCase = true) ||
|
||||
manga.authors.contains(query, ignoreCase = true)
|
||||
}
|
||||
|
||||
fun containsTags(tags: Collection<String>): Boolean {
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.setProgressIcon
|
||||
import org.koitharu.kotatsu.databinding.DialogLocalInfoBinding
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LocalInfoDialog : AlertDialogFragment<DialogLocalInfoBinding>(), View.OnClickListener {
|
||||
@@ -86,7 +86,7 @@ class LocalInfoDialog : AlertDialogFragment<DialogLocalInfoBinding>(), View.OnCl
|
||||
val total = size + available
|
||||
val segment = SegmentedBarView.Segment(
|
||||
percent = (size.toDouble() / total.toDouble()).toFloat(),
|
||||
color = KotatsuColors.segmentColor(view.context, materialR.attr.colorPrimary),
|
||||
color = KotatsuColors.segmentColor(view.context, appcompatR.attr.colorPrimary),
|
||||
)
|
||||
requireViewBinding().labelUsed.text = view.context.getString(
|
||||
R.string.memory_usage_pattern,
|
||||
|
||||
@@ -429,9 +429,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
supportActionBar?.apply {
|
||||
setHomeAsUpIndicator(
|
||||
if (isOpened) {
|
||||
materialR.drawable.abc_ic_ab_back_material
|
||||
materialR.drawable.ic_arrow_back_black_24
|
||||
} else {
|
||||
materialR.drawable.abc_ic_search_api_material
|
||||
materialR.drawable.ic_search_black_24
|
||||
},
|
||||
)
|
||||
setHomeActionContentDescription(
|
||||
|
||||
@@ -8,20 +8,6 @@ fun Manga.filterChapters(branch: String?): Manga {
|
||||
return withChapters(chapters = chapters?.filter { it.branch == branch })
|
||||
}
|
||||
|
||||
private fun Manga.withChapters(chapters: List<MangaChapter>?) = Manga(
|
||||
id = id,
|
||||
title = title,
|
||||
altTitle = altTitle,
|
||||
url = url,
|
||||
publicUrl = publicUrl,
|
||||
rating = rating,
|
||||
isNsfw = isNsfw,
|
||||
coverUrl = coverUrl,
|
||||
tags = tags,
|
||||
state = state,
|
||||
author = author,
|
||||
largeCoverUrl = largeCoverUrl,
|
||||
description = description,
|
||||
private fun Manga.withChapters(chapters: List<MangaChapter>?) = copy(
|
||||
chapters = chapters,
|
||||
source = source,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -8,9 +8,15 @@ import androidx.collection.LongSparseArray
|
||||
import androidx.collection.set
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import coil3.BitmapImage
|
||||
import coil3.Image
|
||||
import coil3.ImageLoader
|
||||
import coil3.memory.MemoryCache
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.transformations
|
||||
import coil3.toBitmap
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import dagger.hilt.android.ActivityRetainedLifecycle
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.Deferred
|
||||
@@ -24,6 +30,7 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okio.use
|
||||
@@ -36,9 +43,9 @@ import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.parser.CachingMangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.MimeTypes
|
||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
|
||||
import org.koitharu.kotatsu.core.util.ext.cancelChildrenAndJoin
|
||||
import org.koitharu.kotatsu.core.util.ext.compressToPNG
|
||||
@@ -49,6 +56,8 @@ import org.koitharu.kotatsu.core.util.ext.isFileUri
|
||||
import org.koitharu.kotatsu.core.util.ext.isNotEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode
|
||||
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
||||
import org.koitharu.kotatsu.core.util.ext.lifecycleScope
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.ramAvailable
|
||||
import org.koitharu.kotatsu.core.util.ext.toMimeType
|
||||
@@ -76,13 +85,14 @@ class PageLoader @Inject constructor(
|
||||
lifecycle: ActivityRetainedLifecycle,
|
||||
@MangaHttpClient private val okHttp: OkHttpClient,
|
||||
private val cache: PagesCache,
|
||||
private val coil: ImageLoader,
|
||||
private val settings: AppSettings,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
private val imageProxyInterceptor: ImageProxyInterceptor,
|
||||
private val downloadSlowdownDispatcher: DownloadSlowdownDispatcher,
|
||||
) {
|
||||
|
||||
val loaderScope = RetainedLifecycleCoroutineScope(lifecycle) + InternalErrorHandler() + Dispatchers.Default
|
||||
val loaderScope = lifecycle.lifecycleScope + InternalErrorHandler() + Dispatchers.Default
|
||||
|
||||
private val tasks = LongSparseArray<ProgressDeferred<Uri, Float>>()
|
||||
private val semaphore = Semaphore(3)
|
||||
@@ -121,6 +131,41 @@ class PageLoader @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadPreview(page: MangaPage): ImageSource? {
|
||||
val preview = page.preview
|
||||
if (preview.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(preview)
|
||||
.mangaSourceExtra(page.source)
|
||||
.transformations(TrimTransformation())
|
||||
.build()
|
||||
return coil.execute(request).image?.toImageSource()
|
||||
}
|
||||
|
||||
fun peekPreviewSource(preview: String?): ImageSource? {
|
||||
if (preview.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
coil.memoryCache?.let { cache ->
|
||||
val key = MemoryCache.Key(preview)
|
||||
cache[key]?.image?.let {
|
||||
return if (it is BitmapImage) {
|
||||
ImageSource.cachedBitmap(it.toBitmap())
|
||||
} else {
|
||||
ImageSource.bitmap(it.toBitmap())
|
||||
}
|
||||
}
|
||||
}
|
||||
coil.diskCache?.let { cache ->
|
||||
cache.openSnapshot(preview)?.use { snapshot ->
|
||||
return ImageSource.file(snapshot.data.toFile())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<Uri, Float> {
|
||||
var task = tasks[page.id]?.takeIf { it.isValid() }
|
||||
if (force) {
|
||||
@@ -237,7 +282,7 @@ class PageLoader @Inject constructor(
|
||||
if (!skipCache) {
|
||||
cache.get(pageUrl)?.let { return it.toUri() }
|
||||
}
|
||||
val uri = Uri.parse(pageUrl)
|
||||
val uri = pageUrl.toUri()
|
||||
return when {
|
||||
uri.isZipUri() -> if (uri.scheme == URI_SCHEME_ZIP) {
|
||||
uri
|
||||
@@ -264,6 +309,12 @@ class PageLoader @Inject constructor(
|
||||
return context.ramAvailable <= FileSize.MEGABYTES.convert(PREFETCH_MIN_RAM_MB, FileSize.BYTES)
|
||||
}
|
||||
|
||||
private fun Image.toImageSource(): ImageSource = if (this is BitmapImage) {
|
||||
ImageSource.cachedBitmap(toBitmap())
|
||||
} else {
|
||||
ImageSource.bitmap(toBitmap())
|
||||
}
|
||||
|
||||
private fun Deferred<Uri>.isValid(): Boolean {
|
||||
return getCompletionResultOrNull()?.map { uri ->
|
||||
uri.exists() && uri.isTargetNotEmpty()
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.ContentRating
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
@@ -177,7 +178,7 @@ class ReaderViewModel @Inject constructor(
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null),
|
||||
)
|
||||
|
||||
val isMangaNsfw = manga.map { it?.isNsfw == true }
|
||||
val isMangaNsfw = manga.map { it?.contentRating == ContentRating.ADULT }
|
||||
|
||||
val isBookmarkAdded = readingState.flatMapLatest { state ->
|
||||
val manga = mangaDetails.value?.toManga()
|
||||
|
||||
@@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.yield
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
@@ -108,19 +107,29 @@ class PageHolderDelegate(
|
||||
}
|
||||
|
||||
override fun onReady() {
|
||||
state = State.SHOWING
|
||||
error = null
|
||||
callback.onImageShowing(readerSettings)
|
||||
if (state >= State.LOADED) {
|
||||
state = State.SHOWING
|
||||
error = null
|
||||
callback.onImageShowing(readerSettings, isPreview = false)
|
||||
} else if (state == State.LOADING_WITH_PREVIEW) {
|
||||
callback.onImageShowing(readerSettings, isPreview = true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onImageLoaded() {
|
||||
state = State.SHOWN
|
||||
error = null
|
||||
callback.onImageShown()
|
||||
if (state >= State.LOADED) {
|
||||
state = State.SHOWN
|
||||
error = null
|
||||
callback.onImageShown()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onImageLoadError(e: Throwable) {
|
||||
e.printStackTraceDebug()
|
||||
if (state < State.LOADED) {
|
||||
// ignore preview error
|
||||
return
|
||||
}
|
||||
val uri = this.uri
|
||||
error = e
|
||||
if (state == State.LOADED && e is IOException && uri != null && uri.toFileOrNull()?.exists() != false) {
|
||||
@@ -133,7 +142,7 @@ class PageHolderDelegate(
|
||||
|
||||
override fun onChanged(value: ReaderSettings) {
|
||||
if (state == State.SHOWN) {
|
||||
callback.onImageShowing(readerSettings)
|
||||
callback.onImageShowing(readerSettings, isPreview = false)
|
||||
}
|
||||
callback.onConfigChanged()
|
||||
}
|
||||
@@ -172,21 +181,25 @@ class PageHolderDelegate(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doLoad(data: MangaPage, force: Boolean) {
|
||||
private suspend fun doLoad(data: MangaPage, force: Boolean) = coroutineScope {
|
||||
state = State.LOADING
|
||||
error = null
|
||||
callback.onLoadingStarted()
|
||||
yield()
|
||||
launch {
|
||||
val preview = loader.loadPreview(data) ?: return@launch
|
||||
if (state == State.LOADING) {
|
||||
state = State.LOADING_WITH_PREVIEW
|
||||
callback.onPreviewReady(preview)
|
||||
}
|
||||
}
|
||||
try {
|
||||
val task = withContext(Dispatchers.Default) {
|
||||
loader.loadPageAsync(data, force)
|
||||
}
|
||||
uri = coroutineScope {
|
||||
val progressObserver = observeProgress(this, task.progressAsFlow())
|
||||
val file = task.await()
|
||||
progressObserver.cancelAndJoin()
|
||||
file
|
||||
}
|
||||
val progressObserver = observeProgress(this, task.progressAsFlow())
|
||||
val file = task.await()
|
||||
progressObserver.cancelAndJoin()
|
||||
uri = file
|
||||
state = State.LOADED
|
||||
cachedBounds = if (readerSettings.isPagesCropEnabled(isWebtoon)) {
|
||||
loader.getTrimmedBounds(checkNotNull(uri))
|
||||
@@ -223,7 +236,7 @@ class PageHolderDelegate(
|
||||
}
|
||||
|
||||
enum class State {
|
||||
EMPTY, LOADING, LOADED, CONVERTING, CONVERTED, SHOWING, SHOWN, ERROR
|
||||
EMPTY, LOADING, LOADING_WITH_PREVIEW, LOADED, CONVERTING, CONVERTED, SHOWING, SHOWN, ERROR
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
@@ -232,9 +245,11 @@ class PageHolderDelegate(
|
||||
|
||||
fun onError(e: Throwable)
|
||||
|
||||
fun onPreviewReady(source: ImageSource)
|
||||
|
||||
fun onImageReady(source: ImageSource)
|
||||
|
||||
fun onImageShowing(settings: ReaderSettings)
|
||||
fun onImageShowing(settings: ReaderSettings, isPreview: Boolean)
|
||||
|
||||
fun onImageShown()
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class DoublePageHolder(
|
||||
.gravity = (if (isEven) Gravity.START else Gravity.END) or Gravity.BOTTOM
|
||||
}
|
||||
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
override fun onImageShowing(settings: ReaderSettings, isPreview: Boolean) {
|
||||
with(binding.ssiv) {
|
||||
maxScale = 2f * maxOf(
|
||||
width / sWidth.toFloat(),
|
||||
|
||||
@@ -27,7 +27,7 @@ class ReversedPageHolder(
|
||||
.gravity = Gravity.START or Gravity.BOTTOM
|
||||
}
|
||||
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
override fun onImageShowing(settings: ReaderSettings, isPreview: Boolean) {
|
||||
with(binding.ssiv) {
|
||||
maxScale = 2f * maxOf(
|
||||
width / sWidth.toFloat(),
|
||||
|
||||
@@ -89,11 +89,15 @@ open class PageHolder(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreviewReady(source: ImageSource) {
|
||||
binding.ssiv.setImage(source)
|
||||
}
|
||||
|
||||
override fun onImageReady(source: ImageSource) {
|
||||
binding.ssiv.setImage(source)
|
||||
}
|
||||
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
override fun onImageShowing(settings: ReaderSettings, isPreview: Boolean) {
|
||||
binding.ssiv.maxScale = 2f * maxOf(
|
||||
binding.ssiv.width / binding.ssiv.sWidth.toFloat(),
|
||||
binding.ssiv.height / binding.ssiv.sHeight.toFloat(),
|
||||
|
||||
@@ -89,11 +89,13 @@ class WebtoonHolder(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreviewReady(source: ImageSource) = Unit
|
||||
|
||||
override fun onImageReady(source: ImageSource) {
|
||||
binding.ssiv.setImage(source)
|
||||
}
|
||||
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
override fun onImageShowing(settings: ReaderSettings, isPreview: Boolean) {
|
||||
binding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()
|
||||
with(binding.ssiv) {
|
||||
scrollTo(
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
||||
import org.koitharu.kotatsu.scrobbling.common.ui.config.adapter.ScrobblingMangaAdapter
|
||||
import javax.inject.Inject
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
|
||||
@@ -114,7 +114,7 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
|
||||
private fun onUserChanged(user: ScrobblerUser?) {
|
||||
if (user == null) {
|
||||
viewBinding.imageViewAvatar.disposeImageRequest()
|
||||
viewBinding.imageViewAvatar.setImageResource(materialR.drawable.abc_ic_menu_overflow_material)
|
||||
viewBinding.imageViewAvatar.setImageResource(appcompatR.drawable.abc_ic_menu_overflow_material)
|
||||
return
|
||||
}
|
||||
viewBinding.imageViewAvatar.newImageRequest(this, user.avatar)
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.model.isNsfw
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.contains
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
@@ -83,7 +84,7 @@ class SearchV2Helper @AssistedInject constructor(
|
||||
}
|
||||
|
||||
SearchKind.AUTHOR -> retainAll { m ->
|
||||
m.author.isNullOrEmpty() || m.author.equals(query, ignoreCase = true)
|
||||
m.authors.isEmpty() || m.authors.contains(query, ignoreCase = true)
|
||||
}
|
||||
|
||||
SearchKind.SIMPLE, // no filtering expected
|
||||
@@ -99,7 +100,7 @@ class SearchV2Helper @AssistedInject constructor(
|
||||
}
|
||||
|
||||
SearchKind.AUTHOR -> sortByDescending { m ->
|
||||
m.author?.equals(query, ignoreCase = true) == true
|
||||
m.authors.contains(query, ignoreCase = true)
|
||||
}
|
||||
|
||||
SearchKind.TAG -> sortByDescending { m ->
|
||||
|
||||
@@ -23,19 +23,19 @@ import org.koitharu.kotatsu.core.util.ext.drawableEnd
|
||||
import org.koitharu.kotatsu.core.util.ext.drawableStart
|
||||
import org.koitharu.kotatsu.search.domain.SearchKind
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
private const val DRAWABLE_END = 2
|
||||
|
||||
class SearchEditText @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = materialR.attr.editTextStyle,
|
||||
@AttrRes defStyleAttr: Int = appcompatR.attr.editTextStyle,
|
||||
) : AppCompatEditText(context, attrs, defStyleAttr) {
|
||||
|
||||
var searchSuggestionListener: SearchSuggestionListener? = null
|
||||
private val clearIcon =
|
||||
ContextCompat.getDrawable(context, materialR.drawable.abc_ic_clear_material)
|
||||
ContextCompat.getDrawable(context, appcompatR.drawable.abc_ic_clear_material)
|
||||
private var isEmpty = text.isNullOrEmpty()
|
||||
|
||||
init {
|
||||
|
||||
@@ -4,14 +4,14 @@ import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.AttrRes
|
||||
import com.google.android.material.R
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
class SearchToolbar @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = R.attr.toolbarStyle,
|
||||
@AttrRes defStyleAttr: Int = appcompatR.attr.toolbarStyle,
|
||||
) : MaterialToolbar(context, attrs, defStyleAttr) {
|
||||
|
||||
private val bgDrawable = MaterialShapeDrawable(context, attrs, defStyleAttr, 0)
|
||||
@@ -21,4 +21,4 @@ class SearchToolbar @JvmOverloads constructor(
|
||||
bgDrawable.setShadowColor(Color.DKGRAY)
|
||||
background = bgDrawable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.koitharu.kotatsu.databinding.ActivityReaderTapActionsBinding
|
||||
import org.koitharu.kotatsu.reader.domain.TapGridArea
|
||||
import org.koitharu.kotatsu.reader.ui.tapgrid.TapAction
|
||||
import java.util.EnumMap
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ReaderTapGridConfigActivity : BaseActivity<ActivityReaderTapActionsBinding>(), View.OnClickListener,
|
||||
@@ -157,7 +157,7 @@ class ReaderTapGridConfigActivity : BaseActivity<ActivityReaderTapActionsBinding
|
||||
}
|
||||
|
||||
private fun createBackground(action: TapAction?): Drawable? {
|
||||
val ripple = getThemeDrawable(materialR.attr.selectableItemBackground)
|
||||
val ripple = getThemeDrawable(appcompatR.attr.selectableItemBackground)
|
||||
return if (action == null) {
|
||||
ripple
|
||||
} else {
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.configureForParser
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
||||
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
@@ -36,11 +35,7 @@ class SourceAuthActivity : BaseBrowserActivity(), BrowserCallback {
|
||||
|
||||
private lateinit var authProvider: MangaParserAuthProvider
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) {
|
||||
return
|
||||
}
|
||||
override fun onCreate2(savedInstanceState: Bundle?) {
|
||||
val source = MangaSource(intent?.getStringExtra(EXTRA_SOURCE))
|
||||
if (source !is MangaParserSource) {
|
||||
finishAfterTransition()
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemEmptyHintBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
fun sourceCatalogItemSourceAD(
|
||||
coil: ImageLoader,
|
||||
@@ -46,7 +46,7 @@ fun sourceCatalogItemSourceAD(
|
||||
listener.onItemClick(item, v)
|
||||
}
|
||||
val basePadding = context.getThemeDimensionPixelOffset(
|
||||
materialR.attr.listPreferredItemPaddingEnd,
|
||||
appcompatR.attr.listPreferredItemPaddingEnd,
|
||||
binding.root.paddingStart,
|
||||
)
|
||||
binding.root.updatePaddingRelative(
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class BarChartView @JvmOverloads constructor(
|
||||
@@ -38,7 +39,7 @@ class BarChartView @JvmOverloads constructor(
|
||||
private val chartBounds = RectF()
|
||||
|
||||
@ColorInt
|
||||
var barColor: Int = context.getThemeColor(materialR.attr.colorAccent)
|
||||
var barColor: Int = context.getThemeColor(appcompatR.attr.colorAccent)
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
|
||||
@@ -48,11 +48,13 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.model.distinctById
|
||||
import org.koitharu.kotatsu.core.model.getLocale
|
||||
import org.koitharu.kotatsu.core.model.isNsfw
|
||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||
import org.koitharu.kotatsu.core.nav.ReaderIntent
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.LocaleComparator
|
||||
import org.koitharu.kotatsu.core.util.ext.asArrayList
|
||||
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
|
||||
import org.koitharu.kotatsu.core.util.ext.awaitWorkInfosByTag
|
||||
@@ -179,7 +181,7 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
historyRepository.getList(0, 20) +
|
||||
favouritesRepository.getLastManga(20)
|
||||
).distinctById()
|
||||
val sources = sourcesRepository.getEnabledSources()
|
||||
val sources = getSources()
|
||||
if (seed.isEmpty() || sources.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
@@ -188,7 +190,7 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
|
||||
val semaphore = Semaphore(MAX_PARALLELISM)
|
||||
val producer = channelFlow {
|
||||
for (it in sources.shuffled()) {
|
||||
for (it in sources) {
|
||||
if (it.isNsfw() && (appSettings.isSuggestionsExcludeNsfw || appSettings.isNsfwContentDisabled)) {
|
||||
continue
|
||||
}
|
||||
@@ -243,6 +245,18 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
return suggestions.size
|
||||
}
|
||||
|
||||
private suspend fun getSources(): List<MangaSource> {
|
||||
if (appSettings.isSuggestionsIncludeDisabledSources) {
|
||||
val result = sourcesRepository.allMangaSources.toMutableList<MangaSource>()
|
||||
result.addAll(sourcesRepository.getExternalSources())
|
||||
result.shuffle()
|
||||
result.sortWith(compareBy(nullsLast(LocaleComparator())) { it.getLocale() })
|
||||
return result
|
||||
} else {
|
||||
return sourcesRepository.getEnabledSources().shuffled()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getList(
|
||||
source: MangaSource,
|
||||
tags: List<String>,
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.databinding.ItemTrackDebugBinding
|
||||
import org.koitharu.kotatsu.tracker.data.TrackEntity
|
||||
import com.google.android.material.R as materialR
|
||||
import androidx.appcompat.R as appcompatR
|
||||
|
||||
fun trackDebugAD(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
@@ -58,7 +58,7 @@ fun trackDebugAD(
|
||||
if (item.lastResult == TrackEntity.RESULT_FAILED) {
|
||||
append(" - ")
|
||||
bold {
|
||||
color(context.getThemeColor(materialR.attr.colorError, Color.RED)) {
|
||||
color(context.getThemeColor(appcompatR.attr.colorError, Color.RED)) {
|
||||
append(item.lastError ?: getString(R.string.error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,19 +74,27 @@
|
||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_nsfw"
|
||||
android:id="@+id/textView_nsfw_16"
|
||||
style="@style/Widget.Kotatsu.TextView.Badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_indicator_offset"
|
||||
android:background="@drawable/bg_chip"
|
||||
android:backgroundTint="@color/warning"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:paddingVertical="2dp"
|
||||
android:backgroundTint="@color/nsfw_16"
|
||||
android:text="@string/nsfw_16"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||
app:layout_constraintEnd_toEndOf="@id/imageView_cover"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_nsfw_18"
|
||||
style="@style/Widget.Kotatsu.TextView.Badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_indicator_offset"
|
||||
android:backgroundTint="@color/nsfw_18"
|
||||
android:text="@string/nsfw"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="?textAppearanceLabelMedium"
|
||||
android:textColor="?colorOnError"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
|
||||
|
||||
|
||||
@@ -66,19 +66,27 @@
|
||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_nsfw"
|
||||
android:id="@+id/textView_nsfw_16"
|
||||
style="@style/Widget.Kotatsu.TextView.Badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_indicator_offset"
|
||||
android:background="@drawable/bg_chip"
|
||||
android:backgroundTint="@color/warning"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:paddingVertical="2dp"
|
||||
android:backgroundTint="@color/nsfw_16"
|
||||
android:text="@string/nsfw_16"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||
app:layout_constraintEnd_toEndOf="@id/imageView_cover"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_nsfw_18"
|
||||
style="@style/Widget.Kotatsu.TextView.Badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_indicator_offset"
|
||||
android:backgroundTint="@color/nsfw_18"
|
||||
android:text="@string/nsfw"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="?textAppearanceLabelMedium"
|
||||
android:textColor="?colorOnError"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
|
||||
|
||||
|
||||
@@ -64,11 +64,8 @@
|
||||
android:id="@+id/textView_author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/custom_selectable_item_background"
|
||||
android:padding="4dp"
|
||||
android:singleLine="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/textView_author_label"
|
||||
@@ -87,7 +84,7 @@
|
||||
android:text="@string/translation"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
app:layout_constraintStart_toStartOf="@id/card_details"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_author_label" />
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_author" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_translation"
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:max="100" />
|
||||
android:max="100"
|
||||
app:hideAnimationBehavior="escape"
|
||||
app:showAnimationBehavior="none" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_error"
|
||||
@@ -38,7 +40,7 @@
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_retry"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
style="?materialButtonTonalStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
@@ -46,7 +48,7 @@
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_error_details"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
style="?borderlessButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/details" />
|
||||
|
||||
@@ -47,7 +47,9 @@
|
||||
android:id="@+id/split_button_read"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical">
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="0dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_read"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
yaoi
|
||||
yaoi(bl)
|
||||
yuri
|
||||
trap
|
||||
traps
|
||||
@@ -320,8 +320,7 @@
|
||||
<string name="no_chapters">لا توجد فصول</string>
|
||||
<string name="allow_unstable_updates">السماح بالتحديثات غير مستقرة</string>
|
||||
<string name="allow_unstable_updates_summary">تلقي إشعارات حول الإصدارات الغير مستقرة</string>
|
||||
<string name="categories_delete_confirm">هل أنت متأكد أنك تريد حذف قوائم المُفضلة المحددة
|
||||
\n؟ سيتم فقدان جميع المانجا فيها ولا يمكن التراجع عن هذا.</string>
|
||||
<string name="categories_delete_confirm">هل أنت متأكد أنك تريد حذف قوائم المُفضلة المحددة \n؟ سيتم فقدان جميع المانجا فيها ولا يمكن التراجع عن هذا.</string>
|
||||
<string name="pages_cache">بيانات التخزين المؤقت للصفحات</string>
|
||||
<string name="not_found_404">المحتوى غير موجود أو تمت إزالته</string>
|
||||
<string name="comics_archive_import_description">يمكنك اختيار ملف أو أكثر بتنسيق cbz أو zip ، سيتم التعرف على كل ملف على أنه مانغا منفصلة.</string>
|
||||
|
||||
@@ -275,8 +275,7 @@
|
||||
<string name="clear_all_history">Ачысціць усю гісторыю</string>
|
||||
<string name="history_cleared">Гісторыя ачышчана</string>
|
||||
<string name="incognito_mode">Рэжым інкогніта</string>
|
||||
<string name="categories_delete_confirm">Вы ўпэўнены, што хочаце выдаліць выбраныя абраныя катэгорыі?
|
||||
\nУся манга ў ім будзе страчана, і гэта нельга будзе адрабіць.</string>
|
||||
<string name="categories_delete_confirm">Вы ўпэўнены, што хочаце выдаліць выбраныя абраныя катэгорыі? \nУся манга ў ім будзе страчана, і гэта нельга будзе адрабіць.</string>
|
||||
<string name="no_bookmarks_summary">Вы можаце стварыць закладку падчас чытання мангі</string>
|
||||
<string name="saved_manga">Захаваная манга</string>
|
||||
<string name="theme_name_mamimi">Мамімі</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d টি আইটেম</item>
|
||||
<item quantity="one">%1$d আইটেম</item>
|
||||
<item quantity="other">%1$d টি আইটেম</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
@@ -28,4 +28,12 @@
|
||||
<item quantity="one">মাত্র %1$d মিনিট আগে</item>
|
||||
<item quantity="other">%1$d মিনিট আগে</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
<plurals name="minutes">
|
||||
<item quantity="one">%1$d মিনিট</item>
|
||||
<item quantity="other">%1$d টি মিনিট</item>
|
||||
</plurals>
|
||||
<plurals name="hours">
|
||||
<item quantity="one">%1$d ঘন্টা</item>
|
||||
<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="favourites">পছন্দের গুলো</string>
|
||||
<string name="history">ইতিহাস</string>
|
||||
<string name="local_storage">লোকাল স্টোরেজ</string>
|
||||
@@ -13,7 +13,7 @@
|
||||
<string name="open_in_browser">ব্রাউজারে খুলুন</string>
|
||||
<string name="error_occurred">কিছু একটা সমস্যা হয়েছে</string>
|
||||
<string name="details">খুঁটিনাটি</string>
|
||||
<string name="chapters">পর্ব সমূহ</string>
|
||||
<string name="chapters">অধ্যায়</string>
|
||||
<string name="list">তালিকা</string>
|
||||
<string name="detailed_list">পুঙ্খানুপুঙ্খ তালিকা</string>
|
||||
<string name="grid">গ্রিড</string>
|
||||
@@ -71,7 +71,7 @@
|
||||
<string name="remove">সরিয়ে ফেলুন</string>
|
||||
<string name="_s_deleted_from_local_storage">\"%s\" লোকাল স্টোরেজ থেকে সরানো হয়েছে</string>
|
||||
<string name="save_page">পেজ সেভ করুন</string>
|
||||
<string name="page_saved">সেভ হয়েছে</string>
|
||||
<string name="page_saved">Page saved</string>
|
||||
<string name="standard">স্ট্যান্ডার্ড</string>
|
||||
<string name="search_on_s">%s এ খুঁজুন</string>
|
||||
<string name="delete_manga">মানগা ডিলিট করুন</string>
|
||||
@@ -95,11 +95,11 @@
|
||||
<string name="cannot_find_available_storage">সঞ্চয়স্থান উপলব্ধ নয়</string>
|
||||
<string name="ignore_ssl_errors">SSL ত্রুটি উপেক্ষা করুন</string>
|
||||
<string name="server_address">সার্ভার ঠিকানা</string>
|
||||
<string name="text_feed_holder">আপনি যা পড়ছেন তার নতুন অধ্যায় এখানে দেখানো হয়েছে</string>
|
||||
<string name="text_feed_holder">New chapters of what you are reading are shown here</string>
|
||||
<string name="favourites_category_empty">খালি বিভাগ</string>
|
||||
<string name="pause">বিরতি</string>
|
||||
<string name="remove_completed">সম্পূর্ণভাবে সরান</string>
|
||||
<string name="manga_save_location">ডাউনলোডের জন্য ফোল্ডার</string>
|
||||
<string name="manga_save_location">Downloads folder</string>
|
||||
<string name="suggestions_notifications_summary">কখনও কখনও প্রস্তাবিত মাঙ্গা সহ বিজ্ঞপ্তিগুলি দেখান৷</string>
|
||||
<string name="updates_feed_cleared">সাফ করা হয়েছে</string>
|
||||
<string name="list_mode">তালিকার ধরন</string>
|
||||
@@ -146,9 +146,34 @@
|
||||
<string name="repeat_password">পাসওয়ার্ড পুনরাবৃত্তি করুন</string>
|
||||
<string name="mirror_switching_summary">মিরর উপলব্ধ থাকলে ত্রুটির মাঙ্গা উত্সগুলির জন্য স্বয়ংক্রিয়ভাবে ডোমেনগুলি পরিবর্তন করুন৷</string>
|
||||
<string name="no_thanks">না ধন্যবাদ</string>
|
||||
<string name="text_local_holder_secondary">এটি অনলাইন উত্স থেকে সংরক্ষণ করুন বা ফাইল আমদানি করুন।</string>
|
||||
<string name="text_local_holder_secondary">Save something from an online catalog or import it from a file।</string>
|
||||
<string name="text_local_holder_primary">আগে কিছু সংরক্ষণ করুন</string>
|
||||
<string name="suggestion_manga">পরামর্শ: %s</string>
|
||||
<string name="text_empty_holder_primary">এখানে খালি…</string>
|
||||
<string name="done">সম্পন্ন</string>
|
||||
</resources>
|
||||
<string name="text_empty_holder_secondary_filtered">There are no manga matching the filters you selected</string>
|
||||
<string name="zoom_mode_fit_height">Fit to height</string>
|
||||
<string name="black_dark_theme">Black</string>
|
||||
<string name="pages_saved">Pages saved</string>
|
||||
<string name="zoom_mode_fit_width">Fit to width</string>
|
||||
<string name="zoom_mode_keep_start">Keep at start</string>
|
||||
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d of %2$d on</string>
|
||||
<string name="retry">পুনরায় চেষ্টা করুন</string>
|
||||
<string name="color_theme">কালার স্কিম</string>
|
||||
<string name="content_type_novel">উপন্যাস</string>
|
||||
<string name="content_type_manhua">মানহুয়া</string>
|
||||
<string name="prefetch_content">কন্টেন্ট প্রিলোড হচ্ছে</string>
|
||||
<string name="mark_as_current">বর্তমান হিসেবে চিহ্নিত করুন</string>
|
||||
<string name="compact">কম্প্যাক্ট</string>
|
||||
<string name="show_in_grid_view">গ্রিড ভিউতে দেখান</string>
|
||||
<string name="language">ভাষা</string>
|
||||
<string name="share_logs">লগ শেয়ার করুন</string>
|
||||
<string name="enable_logging">লগিং সক্ষম করুন</string>
|
||||
<string name="show_suspicious_content">সন্দেহজনক কন্টেন্ট দেখান</string>
|
||||
<string name="source_disabled">উৎস অক্ষম করা হয়েছে</string>
|
||||
<string name="delete_old_backups">পুরনো ব্যাকআপ মুছে ফেলুন</string>
|
||||
<string name="enable_logging_summary">ডিবাগের উদ্দেশ্যে কিছু অ্যাকশন রেকর্ড করুন। আপনি কী করছেন তা নিশ্চিত না হলে এটি চালু করবেন না</string>
|
||||
<string name="theme_name_dynamic">ডাইনামিক</string>
|
||||
<string name="theme_name_miku">মিকু</string>
|
||||
<string name="data_not_restored">ডেটা পুনরুদ্ধার করা হয়নি</string>
|
||||
</resources>
|
||||
|
||||
@@ -74,4 +74,4 @@
|
||||
<string name="delete_manga">Elimina manga</string>
|
||||
<string name="reader_settings">Configuració del lector</string>
|
||||
<string name="clear_search_history">Esborrar l\'historial de cerca</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -221,8 +221,7 @@
|
||||
<string name="bookmarks_removed">Záložky odstraněny</string>
|
||||
<string name="no_manga_sources">Žádné zdroje mang</string>
|
||||
<string name="no_manga_sources_text">Zapnout zdroje mang pro čtení online</string>
|
||||
<string name="categories_delete_confirm">Jste si jisti že chcete smazat zvolené oblíbené kategorie\?
|
||||
\nVšechny mangy v ní budou ztraceny a nelze jej vrátit zpět.</string>
|
||||
<string name="categories_delete_confirm">Jste si jisti že chcete smazat zvolené oblíbené kategorie? \nVšechny mangy v ní budou ztraceny a nelze jej vrátit zpět.</string>
|
||||
<string name="reorder">Přeskupit</string>
|
||||
<string name="empty">Prázdné</string>
|
||||
<string name="explore">Prozkoumat</string>
|
||||
|
||||
@@ -296,8 +296,7 @@
|
||||
<string name="no_bookmarks_summary">Du kannst beim Lesen von Mangas Lesezeichen erstellen</string>
|
||||
<string name="bookmarks_removed">Lesezeichen entfernt</string>
|
||||
<string name="random">Zufällig</string>
|
||||
<string name="categories_delete_confirm">Bist du sicher, dass du die ausgewählten Lieblingskategorien löschen möchtest\?
|
||||
\nAlle darin enthaltenen Manga gehen verloren und das kann nicht rückgängig gemacht werden.</string>
|
||||
<string name="categories_delete_confirm">Bist du sicher, dass du die ausgewählten Lieblingskategorien löschen möchtest? \nAlle darin enthaltenen Manga gehen verloren und das kann nicht rückgängig gemacht werden.</string>
|
||||
<string name="reorder">Neu anordnen</string>
|
||||
<string name="empty">Leer</string>
|
||||
<string name="explore">Erkunden</string>
|
||||
@@ -634,4 +633,4 @@
|
||||
<string name="recent_queries">Kürzliche Suchen</string>
|
||||
<string name="suggested_queries">Vorgeschlagene Suchen</string>
|
||||
<string name="authors">Autoren</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -443,8 +443,7 @@
|
||||
\n
|
||||
\nΠροσοχή: η πρόοδος σου θα χαθεί.</string>
|
||||
<string name="disable_battery_optimization_summary_downloads">Ενδέχεται να βοηθήσει με την εκκίνηση της λήψης αν αντιμετωπίζεις προβλήματα σχετικά με αυτήν</string>
|
||||
<string name="categories_delete_confirm">Είσαι σίγουρος πως θες να διαγράψεις τις επιλεγμένες αγαπημένες κατηγορίες;
|
||||
\nΌλα τα manga που ανήκουν σε αυτές θα χαθούν, και αυτό είναι μη αναστρέψιμο.</string>
|
||||
<string name="categories_delete_confirm">Είσαι σίγουρος πως θες να διαγράψεις τις επιλεγμένες αγαπημένες κατηγορίες; \nΌλα τα manga που ανήκουν σε αυτές θα χαθούν, και αυτό είναι μη αναστρέψιμο.</string>
|
||||
<string name="downloads_resumed">Οι λήψεις έχουν ξαναξεκινήσει</string>
|
||||
<string name="downloads_paused">Οι λήψεις παύθηκαν</string>
|
||||
<string name="remove_completed_downloads_confirm">Το ιστορικό λήψεων σου θα διαγραφεί</string>
|
||||
@@ -535,4 +534,4 @@
|
||||
<string name="appwidget_recent_description">Τα πρόσφατα διαβασμένα manga σου</string>
|
||||
<string name="clear_cookies_summary">Μπορεί να βοηθήσει σε περίπτωση κάποιων προβλημάτων. Όλες οι εξουσιοδοτήσεις θα ανακληθούν</string>
|
||||
<string name="category_hidden_done">Αυτή η κατηγορία αποκρύφτηκε από την αρχική οθόνη και είναι προσβάσιμη μέσω του Μενού → Διαχείριση κατηγοριών</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
16
app/src/main/res/values-en-rGB/strings.xml
Normal file
16
app/src/main/res/values-en-rGB/strings.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="favourites">Favourites</string>
|
||||
<string name="you_have_not_favourites_yet">No favourites yet</string>
|
||||
<string name="add_to_favourites">Favourite this</string>
|
||||
<string name="favourites_categories">Favourite categories</string>
|
||||
<string name="all_favourites">All favourites</string>
|
||||
<string name="backup_information">You can create backup of your history and favourites and restore it</string>
|
||||
<string name="empty_favourite_categories">No favourite categories</string>
|
||||
<string name="appwidget_shelf_description">Manga from your favourites</string>
|
||||
<string name="show_reading_indicators_summary">Show percentage read in history and favourites</string>
|
||||
<string name="categories_delete_confirm">Are you sure you want to delete the selected favourite categories?\nAll manga in it will be lost and this cannot be undone.</string>
|
||||
<string name="removed_from_favourites">Removed from favourites</string>
|
||||
<string name="migrate_confirmation">Manga \"%1$s\" from \"%2$s\" will be replaced with \"%3$s\" from \"%4$s\" in your history and favourites (if present)</string>
|
||||
<string name="not_in_favorites">Not in favourites</string>
|
||||
</resources>
|
||||
@@ -298,8 +298,7 @@
|
||||
<string name="manage">Gestionar</string>
|
||||
<string name="confirm_exit">Pulsa de nuevo «Atrás» para salir</string>
|
||||
<string name="folder_with_images">Carpeta con imágenes</string>
|
||||
<string name="categories_delete_confirm">¿Estás seguro de que quieres eliminar las categorías favoritas seleccionadas\?
|
||||
\nTodos los mangas en ella se perderán y esto no se puede deshacer.</string>
|
||||
<string name="categories_delete_confirm">¿Estás seguro de que quieres eliminar las categorías favoritas seleccionadas? \nTodos los mangas en ella se perderán y esto no se puede deshacer.</string>
|
||||
<string name="explore">Explorar</string>
|
||||
<string name="memory_usage_pattern">%1$s - %2$s</string>
|
||||
<string name="exit_confirmation_summary">Pulse dos veces «Atrás» para salir de la aplicación</string>
|
||||
@@ -769,4 +768,4 @@
|
||||
<string name="open_telegram_bot_summary">Presione para abrir el chat con Kotatsu Backup Bot</string>
|
||||
<string name="send_backups_telegram">Enviar copias de seguridad por Telegram</string>
|
||||
<string name="telegram_chat_id_summary">Ingrese el ID del chat donde se deben enviar las copias de seguridad</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -210,8 +210,7 @@
|
||||
<string name="text_file_not_supported">Vali kas ZIP või CBZ fail.</string>
|
||||
<string name="download">Lae alla</string>
|
||||
<string name="chapter_is_missing">See peatükk puudub</string>
|
||||
<string name="categories_delete_confirm">Kas sa oled kindel, et tahad kustutada valitud lemmikud kategooriad?
|
||||
\nKõik manga nende sees kaob ja seda tagasi ei saa.</string>
|
||||
<string name="categories_delete_confirm">Kas sa oled kindel, et tahad kustutada valitud lemmikud kategooriad? \nKõik manga nende sees kaob ja seda tagasi ei saa.</string>
|
||||
<string name="logged_in_as">Logitud sisse kui %s</string>
|
||||
<string name="suggestions_info">Kõik andmed analüüsitakse ainult lokaalselt selles seadmes ja neid ei saadeta kunagi kuhugi.</string>
|
||||
<string name="reader_info_pattern">Pt. %1$d/%2$d Lk. %3$d/%4$d</string>
|
||||
|
||||
@@ -280,4 +280,4 @@
|
||||
<string name="report">گزارش</string>
|
||||
<string name="status_planned">برنامهریزی شده</string>
|
||||
<string name="show_reading_indicators_summary">نشان دادن درصد خوانده شده در تاریخچه و پسندیدهها</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -24,4 +24,8 @@
|
||||
<item quantity="one">%1$d päivä sitten</item>
|
||||
<item quantity="other">%1$d päivää sitten</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="one">%1$d kuukausi sitten</item>
|
||||
<item quantity="other">%1$d kuukausia sitten</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -259,4 +259,4 @@
|
||||
<string name="show_all">Näytä kaikki</string>
|
||||
<string name="select_range">Valitse alue</string>
|
||||
<string name="not_found_404">Sisältöä ei löydy tai se on poistettu</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -318,8 +318,7 @@
|
||||
<string name="status_reading">Nagbabasa</string>
|
||||
<string name="pages_cache">Cache ng mga pahina</string>
|
||||
<string name="bookmarks">Mga bookmark</string>
|
||||
<string name="categories_delete_confirm">Sigurado ka bang gusto mong tanggalin ang mga napiling paboritong kategorya\?
|
||||
\nAng lahat ng manga sa loob nito ay mawawala at hindi na ito mababawi.</string>
|
||||
<string name="categories_delete_confirm">Sigurado ka bang gusto mong tanggalin ang mga napiling paboritong kategorya? \nAng lahat ng manga sa loob nito ay mawawala at hindi na ito mababawi.</string>
|
||||
<string name="bookmark_added">Idinagdag ang bookmark</string>
|
||||
<string name="detect_reader_mode_summary">Awtomatikong matukoy kung ang manga ay webtoon</string>
|
||||
<string name="disable_battery_optimization">Di paganahin ang pag-optimize ng baterya</string>
|
||||
|
||||
@@ -271,7 +271,7 @@
|
||||
<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 d\'<a href=%2$s>ouvrir le manga dans un navigateur web</a> pour vous assurer qu\'il est disponible sur sa source<br>2. Assurez-vous que vous utilisez la <a href=kotatsu://about>dernière version de Kotatsu</a><br>3. Si elle est disponible, envoyez un rapport d\'erreur aux développeurs.</string>
|
||||
<string name="confirm_exit">Appuyez à nouveau 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="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>
|
||||
@@ -797,4 +797,9 @@
|
||||
<string name="badges_in_lists">Badges dans les listes</string>
|
||||
<string name="no_write_permission_to_file">N\'a pas la permission d\'écrire un fichier</string>
|
||||
<string name="clear_browser_data">Effacer les données du navigateur</string>
|
||||
<string name="clear_browser_data_summary">Efface les données du navigateur telles que le cache et les cookies. Avertissement : l\'autorisation sur les sources de mangas peut devenir invalide</string>
|
||||
<string name="include_disabled_sources">Inclure les sources désactivées</string>
|
||||
<string name="nsfw_16">16+</string>
|
||||
<string name="exclude_nsfw_from_suggestions_summary">Les mangas pour adultes ne seront pas affichés dans les suggestions. Cette option peut fonctionner de manière inexacte avec certaines sources</string>
|
||||
<string name="suggestions_disabled_sources_summary">Afficher les suggestions de toutes les sources de mangas, y compris celles désactivées</string>
|
||||
</resources>
|
||||
|
||||
@@ -542,8 +542,7 @@
|
||||
<string name="allow_unstable_updates">अस्थिर अद्यतन की अनुमति दें</string>
|
||||
<string name="nothing_here">यहां कुछ नहीं है</string>
|
||||
<string name="allow_unstable_updates_summary">अस्थिर बिल्ड के बारे में सूचनाएं प्राप्त करें</string>
|
||||
<string name="categories_delete_confirm">क्या आप वाकई चयनित पसंदीदा श्रेणियां मिटाना चाहते हैं?
|
||||
\nइसमें मौजूद सारा मंगा नष्ट हो जाएगा और इसे पूर्ववत नहीं किया जा सकता।</string>
|
||||
<string name="categories_delete_confirm">क्या आप वाकई चयनित पसंदीदा श्रेणियां मिटाना चाहते हैं? \nइसमें मौजूद सारा मंगा नष्ट हो जाएगा और इसे पूर्ववत नहीं किया जा सकता।</string>
|
||||
<string name="manga_error_description_pattern">त्रुटि विवरण:<br><tt>%1$s</tt><br><br>1। यह सुनिश्चित करने के लिए कि यह अपने स्रोत पर उपलब्ध है <a href=%2$s>मंगा को वेब ब्राउज़र में खोलने का प्रयास करें</a><br>2। सुनिश्चित करें कि आप <a href=kotatsu://about>Kotatsu के नवीनतम संस्करण</a><br>3 का उपयोग कर रहे हैं। यदि यह उपलब्ध है, तो डेवलपर्स को एक त्रुटि रिपोर्ट भेजें।</string>
|
||||
<string name="folder_with_images_import_description">आप अभिलेखों या छवियों वाली एक निर्देशिका का चयन कर सकते हैं। प्रत्येक संग्रह (या उपनिर्देशिका) को एक अध्याय के रूप में पहचाना जाएगा।</string>
|
||||
<string name="images_procy_description">यदि संभव हो तो ट्रैफिक उपयोग को कम करने और छवि लोडिंग को तेज़ करने के लिए wsrv.nl सेवा का उपयोग करें</string>
|
||||
@@ -667,4 +666,4 @@
|
||||
<string name="pages_saved">सहेजे गए पृष्ठ</string>
|
||||
<string name="invalid_server_address_message">अमान्य सर्वर पता</string>
|
||||
<string name="retry">पुनः प्रयत्न करें</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -229,8 +229,7 @@
|
||||
<string name="no_manga_sources">Nema izvora mange</string>
|
||||
<string name="no_manga_sources_text">Omogućite izvore mange za čitanje mange na mreži</string>
|
||||
<string name="random">Nasumično</string>
|
||||
<string name="categories_delete_confirm">Jeste li sigurni da želite izbrisati odabrane omiljene kategorije?
|
||||
\nSve mange u njoj bit će izgubljene i to se ne može poništiti.</string>
|
||||
<string name="categories_delete_confirm">Jeste li sigurni da želite izbrisati odabrane omiljene kategorije? \nSve mange u njoj bit će izgubljene i to se ne može poništiti.</string>
|
||||
<string name="reader_info_pattern">Poglavlje %1$d/%2$d Stranica %3$d/%4$d</string>
|
||||
<string name="reader_info_bar">Prikaži informacijsku traku u čitaču</string>
|
||||
<string name="comics_archive">Arhiva stripova</string>
|
||||
|
||||
@@ -368,8 +368,7 @@
|
||||
<string name="show_reading_indicators_summary">A haladás (százalékosan) megjelenítése az előzményekben és kedvencekben</string>
|
||||
<string name="exclude_nsfw_from_history_summary">A NSFW jelzésű mangák soha nem kerülnek hozzáadásra az előzményekhez, és a haladásod nem lesz mentve</string>
|
||||
<string name="clear_cookies_summary">Segíthet néhány probléma esetén. Minden hitelesítés érvényét veszti</string>
|
||||
<string name="categories_delete_confirm">Biztosan törölni szeretnéd a kiválasztott kedvenc kategóriákat?
|
||||
\nMinden mangája elveszik, és ez nem visszavonható.</string>
|
||||
<string name="categories_delete_confirm">Biztosan törölni szeretnéd a kiválasztott kedvenc kategóriákat? \nMinden mangája elveszik, és ez nem visszavonható.</string>
|
||||
<string name="history_shortcuts">Aktuális manga hivatkozásai megjelenítése</string>
|
||||
<string name="history_shortcuts_summary">Tedd elérhetővé a legújabb mangákat az alkalmazás ikonjának hosszú megnyomásával</string>
|
||||
<string name="network_unavailable_hint">Kapcsold be a Wi-Fi-t vagy a mobilhálózatot, hogy online olvashass mangát</string>
|
||||
@@ -618,4 +617,4 @@
|
||||
<string name="screenshots_block_incognito">Blokkold inkognitó módban</string>
|
||||
<string name="crop_pages">Lap kivágása</string>
|
||||
<string name="image_server">Előnyben részesített kiszolgáló</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<string name="newest">Terbaru</string>
|
||||
<string name="by_rating">Nilai</string>
|
||||
<string name="sort_order">Urutkan</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="filter">Saring</string>
|
||||
<string name="theme">Tema</string>
|
||||
<string name="light">Terang</string>
|
||||
<string name="dark">Gelap</string>
|
||||
@@ -644,7 +644,7 @@
|
||||
<string name="suggested_queries">Pertanyaan yang disarankan</string>
|
||||
<string name="source_pinned">Sumber disematkan</string>
|
||||
<string name="recent_sources">Sumber sumbver terbaru</string>
|
||||
<string name="text_empty_holder_secondary_filtered">Tidak ada manga yang cocok dengan filter yang Anda pilih</string>
|
||||
<string name="text_empty_holder_secondary_filtered">Tidak ada manga sesuai filter yang ditemukan</string>
|
||||
<string name="image_server">Server gambar yang dipilih</string>
|
||||
<string name="crop_pages">Potong Halaman</string>
|
||||
<string name="pin">Sematkan</string>
|
||||
@@ -758,20 +758,49 @@
|
||||
<string name="rating">Nilai</string>
|
||||
<string name="source">Sumber</string>
|
||||
<string name="added_long_ago">Bahasa</string>
|
||||
<string name="popular_in_hour">Daftar</string>
|
||||
<string name="popular_in_hour">Populer Dalam Satu Jam Terakhir</string>
|
||||
<string name="stuck">Bahasa</string>
|
||||
<string name="error_connection_reset">Koneksi diatur ulang oleh host jarak jauh</string>
|
||||
<string name="incognito">Penyamaran</string>
|
||||
<string name="demographic_kodomo">Kodomo</string>
|
||||
<string name="incognito">Mode Penyamaran</string>
|
||||
<string name="demographic_kodomo">Anak</string>
|
||||
<string name="content_type_one_shot">Satu tembakan</string>
|
||||
<string name="content_type_doujinshi">Film Doujinshi</string>
|
||||
<string name="content_type_doujinshi">Doujinshi</string>
|
||||
<string name="content_type_artist_cg">Artis CG</string>
|
||||
<string name="reader_info_bar_transparent">Bilah informasi pembaca transparan</string>
|
||||
<string name="handle_links">Menangani tautan</string>
|
||||
<string name="handle_links">Tangani Tautan</string>
|
||||
<string name="handle_links_summary">Menangani tautan manga dari aplikasi eksternal (misalnya browser web). Anda mungkin juga perlu mengaktifkannya secara manual di pengaturan sistem aplikasi.</string>
|
||||
<string name="any">Setiap</string>
|
||||
<string name="author">Pengarang</string>
|
||||
<string name="plugin_incompatible_with_cause">Pastikan Anda menggunakan plugin dan Kotatsu versi terbaru</string>
|
||||
<string name="restoring_backup">Memulihkan Cadangan</string>
|
||||
<string name="backup_restored_background">Pencadangan akan dipulihkan di latar belakang</string>
|
||||
<string name="clear_browser_data">Hapus data peramban</string>
|
||||
<string name="error_details">Detail kesalahan</string>
|
||||
<string name="no_write_permission_to_file">Tidak memiliki izin untuk menulis file</string>
|
||||
<string name="error_disclaimer_manga">Cobalah membuka manga di peramban web untuk memastikan manga tersedia di sumbernya.</string>
|
||||
<string name="error_disclaimer_report">Anda dapat mengirimkan laporan bug kepada pengembang. Ini akan membantu kami menyelidiki dan memperbaiki masalah tersebut.</string>
|
||||
<string name="clear_browser_data_summary">Hapus data browser seperti cache dan cookie. Peringatan: Otorisasi dalam sumber manga mungkin menjadi tidak valid</string>
|
||||
<string name="include_disabled_sources">Sertakan sumber yang dinonaktifkan</string>
|
||||
<string name="exclude_nsfw_from_suggestions_summary">Manga dewasa tidak akan ditampilkan dalam saran. Pilihan ini mungkin tidak akurat pada beberapa</string>
|
||||
<string name="search_disabled_sources">Cari melalui sumber yang dinonaktifkan</string>
|
||||
<string name="content_type_game_cg">Permainan CG</string>
|
||||
<string name="unnamed_chapter">Bab tanpa nama</string>
|
||||
<string name="chapter_volume_number">Vol %1$sBab%2$s</string>
|
||||
<string name="chapter_number">Bab%s</string>
|
||||
<string name="simple">Simpel</string>
|
||||
<string name="reader_controls_in_bottom_bar">Kontrol pembaca di bilah bawah</string>
|
||||
<string name="chapters_and_pages">Bab dan halaman</string>
|
||||
<string name="pages_slider">Penggeser pengalih halaman</string>
|
||||
<string name="screen_rotation_locked">Rotasi layar dikunci</string>
|
||||
<string name="screen_rotation_unlocked">Rotasi layar telah dibuka kuncinya</string>
|
||||
<string name="error_disclaimer_app_outdated">Sepertinya versi Kotatsu Anda sudah kedaluwarsa. Harap instal versi terbaru untuk mendapatkan semua perbaikan yang tersedia.</string>
|
||||
<string name="nsfw_16">16+</string>
|
||||
<string name="link_to_manga_on_s">Tautan ke manga di%s</string>
|
||||
<string name="link_to_manga_in_app">Tautan ke manga di kotatsu</string>
|
||||
<string name="suggestions_disabled_sources_summary">Tampilkan saran dari semua sumber manga, termasuk yang dinonaktifkan</string>
|
||||
<string name="disable_captcha_notifications">Nonaktifkan notifikasi captcha</string>
|
||||
<string name="disable_captcha_notifications_summary">Anda tidak akan menerima pemberitahuan tentang penyelesaian CAPTCHA untuk sumber ini, tetapi hal ini dapat menyebabkan penghentian operasi latar belakang (memeriksa bab baru, memperoleh rekomendasi, dll.)</string>
|
||||
<string name="global_search">"Pencarian global"</string>
|
||||
<string name="search_everywhere">Cari dimana saja</string>
|
||||
<string name="badges_in_lists">Lencana dalam daftar</string>
|
||||
</resources>
|
||||
|
||||
@@ -328,8 +328,7 @@
|
||||
<string name="history_shortcuts">Mostra i collegamenti ai manga recenti</string>
|
||||
<string name="reader_control_ltr">Controllo ergonomico del lettore</string>
|
||||
<string name="brightness">Luminosità</string>
|
||||
<string name="categories_delete_confirm">Sei sicuro/a di voler eliminare le categorie preferite selezionate\?
|
||||
\n Tutti i manga in esso contenuti andranno persi e questo non può essere annullato.</string>
|
||||
<string name="categories_delete_confirm">Sei sicuro/a di voler eliminare le categorie preferite selezionate? \n Tutti i manga in esso contenuti andranno persi e questo non può essere annullato.</string>
|
||||
<string name="history_shortcuts_summary">Rendere disponibili i manga recenti premendo a lungo sull\'icona dell\'applicazione</string>
|
||||
<string name="reader_info_bar">Mostra la barra delle informazioni nel lettore</string>
|
||||
<string name="manga_error_description_pattern">Dettagli dell\'errore:<br><tt>%1$s</tt><br><br>1. Prova ad <a href=%2$s>aprire il manga in un browser web</a> per assicurarsi che sia disponibile sulla sua fonte<br>2. Controllare di stare usando la <a href=kotatsu://about>versione più recente di Kotatsu</a><br>3. Se è disponibile, inviare una segnalazione di errore agli sviluppatori.</string>
|
||||
@@ -714,7 +713,7 @@
|
||||
<string name="telegram_group">Gruppo Telegram</string>
|
||||
<string name="screen_orientation">Orientamento schermo</string>
|
||||
<string name="chapters_all">Tutti</string>
|
||||
<string name="save_manga_confirm">Salvare il manga selezionato? Potrebbe utilizzare traffico dati e spazio nella memoria interna.</string>
|
||||
<string name="save_manga_confirm">Salvare il manga selezionato? Potrebbe utilizzare traffico dati e spazio nella memoria interna</string>
|
||||
<string name="error_not_image">Formato non valido: prevista un\'immagine ma ricevuto %s</string>
|
||||
<string name="download_added">Download aggiunto</string>
|
||||
<string name="more_options">Più opzioni</string>
|
||||
@@ -801,4 +800,8 @@
|
||||
<string name="clear_browser_data_summary">Pulisci i dati del browser, come la cache e i cookie. Attenzione: l\'autorizzazione nelle fonti manga potrebbe diventare non valida</string>
|
||||
<string name="clear_browser_data">Pulisci dati del browser</string>
|
||||
<string name="no_write_permission_to_file">Non ha l\'autorizzazione a scrivere un file</string>
|
||||
<string name="nsfw_16">16+</string>
|
||||
<string name="include_disabled_sources">Includi fonti disabilitate</string>
|
||||
<string name="suggestions_disabled_sources_summary">Mostra suggerimenti da tutte le fonti manga, incluse quelle disabilite</string>
|
||||
<string name="exclude_nsfw_from_suggestions_summary">I manga per adulti non verranno mostrati nei suggerimenti. Questa opzione potrebbe non funzionare accuratamente con alcune fonti</string>
|
||||
</resources>
|
||||
|
||||
@@ -271,8 +271,7 @@
|
||||
<string name="not_found_404">コンテンツが見つからない、または削除された</string>
|
||||
<string name="manage">管理</string>
|
||||
<string name="random">ランダム</string>
|
||||
<string name="categories_delete_confirm">選択したお気に入りカテゴリを本当に削除してもよいですか?
|
||||
\nその中にあるマンガはすべて失われ、元に戻すことはできません。</string>
|
||||
<string name="categories_delete_confirm">選択したお気に入りカテゴリを本当に削除してもよいですか? \nその中にあるマンガはすべて失われ、元に戻すことはできません。</string>
|
||||
<string name="empty">空</string>
|
||||
<string name="explore">探索</string>
|
||||
<string name="exit_confirmation_summary">アプリを終了するには、戻るを2回押してください</string>
|
||||
@@ -463,4 +462,4 @@
|
||||
<string name="reader_zoom_buttons">ズームボタンを表示</string>
|
||||
<string name="zoom_out">ズームアウト</string>
|
||||
<string name="retry">リトライ</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -398,8 +398,7 @@
|
||||
<string name="invalid_port_number">Порттың нөмірі қате</string>
|
||||
<string name="suggestions_wifi_only_summary">Шектеулі желі қосылымы болса ұсынысты жаңартпау</string>
|
||||
<string name="webtoon_zoom_summary">Уебтүн режімінде ұлғайту ымына рұқсат ету</string>
|
||||
<string name="categories_delete_confirm">Таңдаулы санатты шынымен жойғыңыз келе ме\?
|
||||
\nІшіндегі бар маңга жойылады, сосын оны қайтарып ала алмайсыз.</string>
|
||||
<string name="categories_delete_confirm">Таңдаулы санатты шынымен жойғыңыз келе ме? \nІшіндегі бар маңга жойылады, сосын оны қайтарып ала алмайсыз.</string>
|
||||
<string name="frequency_once_per_month">Ай сайын</string>
|
||||
<string name="reader_info_pattern">%1$d/%2$d-тарау %3$d/%4$d-бет</string>
|
||||
<string name="contrast">Көреғарлық</string>
|
||||
@@ -538,4 +537,4 @@
|
||||
<string name="reader_actions">Оқымадағы әрекет</string>
|
||||
<string name="sync_auth">Синхрондау тіркелгісіне кіру</string>
|
||||
<string name="config_reset_confirm">Әдепкі баптауға қайтайық па? Әрекетті қайтаруға болмайды.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -129,4 +129,4 @@
|
||||
<string name="sources_pinned">ខ្ទាស់ប្រភពទាំងអស់</string>
|
||||
<string name="unpin">ដកខ្ទាស់</string>
|
||||
<string name="pin">ដាក់ខ្ទស់</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -269,8 +269,7 @@
|
||||
<string name="no_bookmarks_summary">만화를 읽는 도중 북마크를 추가할 수 있습니다</string>
|
||||
<string name="bookmarks_removed">북마크 제거됨</string>
|
||||
<string name="no_manga_sources_text">만화를 읽기 위해 만화 소스 사이트를 활성화 하세요</string>
|
||||
<string name="categories_delete_confirm">정말 선택된 즐겨 찾는 카테고리를 삭제하시겠습니까\?
|
||||
\n해당 카테고리의 모든 만화가 손실되며 취소할 수 없습니다.</string>
|
||||
<string name="categories_delete_confirm">정말 선택된 즐겨 찾는 카테고리를 삭제하시겠습니까? \n해당 카테고리의 모든 만화가 손실되며 취소할 수 없습니다.</string>
|
||||
<string name="empty">비어있음</string>
|
||||
<string name="confirm_exit">뒤로가기 버튼을 다시 눌러 나가기</string>
|
||||
<string name="exit_confirmation_summary">뒤로가기 버튼을 두 번 눌러 앱을 종료할 수 있습니다</string>
|
||||
@@ -337,4 +336,4 @@
|
||||
<string name="reset">초기화</string>
|
||||
<string name="text_unsaved_changes_prompt">저장되지 않은 변경 사항을 저장하거나 삭제하시겠습니까\?</string>
|
||||
<string name="manga_error_description_pattern">오류 세부정보:<br><tt>%1$s</tt><br><br>1. <a href=%2$s>웹 브라우저에서 만화를 열어</a> 소스에서 사용할 수 있는지 확인하세요<br>2. <a href=kotatsu://about>최신 버전의 Kotatsu</a><br>를 사용하고 있는지 확인하세요.3. 사용 가능한 경우 개발자에게 오류 보고서를 보냅니다.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -29,4 +29,4 @@
|
||||
<string name="search_manga">Ieškoti manga</string>
|
||||
<string name="remote_sources">Manga šaltiniai</string>
|
||||
<string name="manga_downloading_">Atsisiunčiama…</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -161,4 +161,4 @@
|
||||
<string name="captcha_solve">Risināt</string>
|
||||
<string name="clear_cookies">Attīrīt sīkdatnes</string>
|
||||
<string name="cookies_cleared">Visas sīkdatnes noņemtas</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
<string name="detailed_list">വിശദമായ ലിസ്റ്റ്</string>
|
||||
<string name="chapter_d_of_d">%2$d-ൻ്റെ %1$d അധ്യായം</string>
|
||||
<string name="try_again">വീണ്ടും ശ്രമിക്കുക</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -215,8 +215,7 @@
|
||||
<string name="no_manga_sources">Tiada sumber manga</string>
|
||||
<string name="no_manga_sources_text">Bolehkan sumber manga untuk membaca manga di dalam talian</string>
|
||||
<string name="random">Rawak</string>
|
||||
<string name="categories_delete_confirm">Adakah anda pasti anda mahu membuang kategori kegemaran yang dipilih?
|
||||
\nSemua manga di dalamnya akan hilang dan ini tidak boleh diundur semula.</string>
|
||||
<string name="categories_delete_confirm">Adakah anda pasti anda mahu membuang kategori kegemaran yang dipilih? \nSemua manga di dalamnya akan hilang dan ini tidak boleh diundur semula.</string>
|
||||
<string name="reorder">Susun semula</string>
|
||||
<string name="empty">Kosong</string>
|
||||
<string name="explore">Jelajah</string>
|
||||
@@ -303,4 +302,4 @@
|
||||
<string name="error_corrupted_file">Data tidak sah dikembalikan atau fail rosak</string>
|
||||
<string name="on_device">Pada peranti</string>
|
||||
<string name="directories">Panduan</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -289,4 +289,4 @@
|
||||
<string name="enable_logging">Skru på loggføring</string>
|
||||
<string name="storage_usage">Lagringsbruk</string>
|
||||
<string name="sync_auth_hint">Du kan logge med en eksisterende konto, eller opprette en ny</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user