Merge branch 'devel' into feature/mal

# Conflicts:
#	app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt
#	app/src/main/res/values/strings.xml
This commit is contained in:
Zakhar Timoshenko
2023-01-04 18:51:59 +03:00
75 changed files with 1487 additions and 300 deletions

View File

@@ -29,6 +29,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:localeConfig="@xml/locales"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
@@ -247,6 +248,9 @@
<meta-data
android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />
<meta-data
android:name="com.samsung.android.icon_container.has_icon_container"
android:value="@bool/com_samsung_android_icon_container_has_icon_container"/>
</application>

View File

@@ -31,6 +31,9 @@ abstract class CoroutineIntentService : BaseService() {
processIntent(startId, intent)
}
}
} catch (e: Throwable) {
e.printStackTraceDebug()
onError(startId, e)
} finally {
stopSelf(startId)
}

View File

@@ -2,20 +2,20 @@ package org.koitharu.kotatsu.base.ui.list
import android.app.Activity
import android.os.Bundle
import android.util.ArrayMap
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.collection.ArrayMap
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.Dispatchers
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
import kotlin.coroutines.EmptyCoroutineContext
private const val PROVIDER_NAME = "selection_decoration_sectioned"

View File

@@ -516,6 +516,6 @@ class FastScroller @JvmOverloads constructor(
interface SectionIndexer {
fun getSectionText(context: Context, position: Int): CharSequence
fun getSectionText(context: Context, position: Int): CharSequence?
}
}
}

View File

@@ -4,12 +4,12 @@ import android.graphics.Bitmap
import android.webkit.WebView
import android.webkit.WebViewClient
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.core.network.AndroidCookieJar
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
private const val CF_CLEARANCE = "cf_clearance"
class CloudFlareClient(
private val cookieJar: AndroidCookieJar,
private val cookieJar: MutableCookieJar,
private val callback: CloudFlareCallback,
private val targetUrl: String,
) : WebViewClient() {
@@ -42,4 +42,4 @@ class CloudFlareClient(
return cookieJar.loadForRequest(targetUrl.toHttpUrl())
.find { it.name == CF_CLEARANCE }?.value
}
}
}

View File

@@ -12,13 +12,13 @@ import androidx.core.view.isInvisible
import androidx.fragment.app.setFragmentResult
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.network.AndroidCookieJar
import org.koitharu.kotatsu.core.network.UserAgentInterceptor
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding
import org.koitharu.kotatsu.utils.ext.stringArgument
import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
@AndroidEntryPoint
class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), CloudFlareCallback {
@@ -27,7 +27,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
private val pendingResult = Bundle(1)
@Inject
lateinit var cookieJar: AndroidCookieJar
lateinit var cookieJar: MutableCookieJar
override fun onInflateView(
inflater: LayoutInflater,

View File

@@ -4,6 +4,7 @@ import android.app.Application
import android.content.Context
import android.provider.SearchRecentSuggestions
import android.text.Html
import android.util.AndroidRuntimeException
import androidx.collection.arraySetOf
import androidx.room.InvalidationTracker
import coil.ComponentRegistry
@@ -25,6 +26,10 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.network.*
import org.koitharu.kotatsu.core.network.cookies.AndroidCookieJar
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.network.cookies.PreferencesCookieJar
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
import org.koitharu.kotatsu.core.parser.MangaRepository
@@ -39,6 +44,7 @@ import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
import org.koitharu.kotatsu.settings.backup.BackupObserver
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.utils.IncognitoModeIndicator
import org.koitharu.kotatsu.utils.ext.connectivityManager
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
import org.koitharu.kotatsu.utils.image.CoilImageGetter
import org.koitharu.kotatsu.widget.WidgetUpdater
@@ -50,7 +56,7 @@ import javax.inject.Singleton
interface AppModule {
@Binds
fun bindCookieJar(androidCookieJar: AndroidCookieJar): CookieJar
fun bindCookieJar(androidCookieJar: MutableCookieJar): CookieJar
@Binds
fun bindMangaLoaderContext(mangaLoaderContextImpl: MangaLoaderContextImpl): MangaLoaderContext
@@ -60,6 +66,17 @@ interface AppModule {
companion object {
@Provides
@Singleton
fun provideCookieJar(
@ApplicationContext context: Context
): MutableCookieJar = try {
AndroidCookieJar()
} catch (e: AndroidRuntimeException) {
// WebView is not available
PreferencesCookieJar(context)
}
@Provides
@Singleton
fun provideOkHttpClient(
@@ -81,6 +98,12 @@ interface AppModule {
}.build()
}
@Provides
@Singleton
fun provideNetworkState(
@ApplicationContext context: Context
) = NetworkState(context.connectivityManager)
@Provides
@Singleton
fun provideMangaDatabase(

View File

@@ -14,11 +14,11 @@ abstract class MangaDao {
abstract suspend fun find(id: Long): MangaWithTags?
@Transaction
@Query("SELECT * FROM manga WHERE title LIKE :query OR alt_title LIKE :query LIMIT :limit")
@Query("SELECT * FROM manga WHERE (title LIKE :query OR alt_title LIKE :query) AND manga_id IN (SELECT manga_id FROM favourites UNION SELECT manga_id FROM history) LIMIT :limit")
abstract suspend fun searchByTitle(query: String, limit: Int): List<MangaWithTags>
@Transaction
@Query("SELECT * FROM manga WHERE (title LIKE :query OR alt_title LIKE :query) AND source = :source LIMIT :limit")
@Query("SELECT * FROM manga WHERE (title LIKE :query OR alt_title LIKE :query) AND source = :source AND manga_id IN (SELECT manga_id FROM favourites UNION SELECT manga_id FROM history) LIMIT :limit")
abstract suspend fun searchByTitle(query: String, source: String, limit: Int): List<MangaWithTags>
@Insert(onConflict = OnConflictStrategy.IGNORE)
@@ -47,4 +47,4 @@ abstract class MangaDao {
}
}
}
}
}

View File

@@ -1,9 +1,9 @@
package org.koitharu.kotatsu.core.exceptions.resolve
import android.util.ArrayMap
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.StringRes
import androidx.collection.ArrayMap
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.suspendCancellableCoroutine

View File

@@ -1,19 +1,17 @@
package org.koitharu.kotatsu.core.network
package org.koitharu.kotatsu.core.network.cookies
import android.webkit.CookieManager
import javax.inject.Inject
import javax.inject.Singleton
import androidx.annotation.WorkerThread
import okhttp3.Cookie
import okhttp3.HttpUrl
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
@Singleton
class AndroidCookieJar @Inject constructor() : CookieJar {
class AndroidCookieJar : MutableCookieJar {
private val cookieManager = CookieManager.getInstance()
@WorkerThread
override fun loadForRequest(url: HttpUrl): List<Cookie> {
val rawCookie = cookieManager.getCookie(url.toString()) ?: return emptyList()
return rawCookie.split(';').mapNotNull {
@@ -21,6 +19,7 @@ class AndroidCookieJar @Inject constructor() : CookieJar {
}
}
@WorkerThread
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
if (cookies.isEmpty()) {
return
@@ -31,7 +30,7 @@ class AndroidCookieJar @Inject constructor() : CookieJar {
}
}
suspend fun clear() = suspendCoroutine<Boolean> { continuation ->
override suspend fun clear() = suspendCoroutine<Boolean> { continuation ->
cookieManager.removeAllCookies(continuation::resume)
}
}

View File

@@ -0,0 +1,84 @@
package org.koitharu.kotatsu.core.network.cookies
import android.util.Base64
import okhttp3.Cookie
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
class CookieWrapper(
val cookie: Cookie,
) {
constructor(encodedString: String) : this(
ObjectInputStream(ByteArrayInputStream(Base64.decode(encodedString, Base64.NO_WRAP))).use {
val name = it.readUTF()
val value = it.readUTF()
val expiresAt = it.readLong()
val domain = it.readUTF()
val path = it.readUTF()
val secure = it.readBoolean()
val httpOnly = it.readBoolean()
val persistent = it.readBoolean()
val hostOnly = it.readBoolean()
Cookie.Builder().also { c ->
c.name(name)
c.value(value)
if (persistent) {
c.expiresAt(expiresAt)
}
if (hostOnly) {
c.hostOnlyDomain(domain)
} else {
c.domain(domain)
}
c.path(path)
if (secure) {
c.secure()
}
if (httpOnly) {
c.httpOnly()
}
}.build()
},
)
fun encode(): String {
val output = ByteArrayOutputStream()
ObjectOutputStream(output).use {
it.writeUTF(cookie.name)
it.writeUTF(cookie.value)
it.writeLong(cookie.expiresAt)
it.writeUTF(cookie.domain)
it.writeUTF(cookie.path)
it.writeBoolean(cookie.secure)
it.writeBoolean(cookie.httpOnly)
it.writeBoolean(cookie.persistent)
it.writeBoolean(cookie.hostOnly)
}
return Base64.encodeToString(output.toByteArray(), Base64.NO_WRAP)
}
fun isExpired() = cookie.expiresAt < System.currentTimeMillis()
fun key(): String {
return (if (cookie.secure) "https" else "http") + "://" + cookie.domain + cookie.path + "|" + cookie.name
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CookieWrapper
if (cookie != other.cookie) return false
return true
}
override fun hashCode(): Int {
return cookie.hashCode()
}
}

View File

@@ -0,0 +1,17 @@
package org.koitharu.kotatsu.core.network.cookies
import androidx.annotation.WorkerThread
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
interface MutableCookieJar : CookieJar {
@WorkerThread
override fun loadForRequest(url: HttpUrl): List<Cookie>
@WorkerThread
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)
suspend fun clear(): Boolean
}

View File

@@ -0,0 +1,89 @@
package org.koitharu.kotatsu.core.network.cookies
import android.content.Context
import androidx.annotation.WorkerThread
import androidx.collection.ArrayMap
import androidx.core.content.edit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Cookie
import okhttp3.HttpUrl
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
private const val PREFS_NAME = "cookies"
class PreferencesCookieJar(
context: Context,
) : MutableCookieJar {
private val cache = ArrayMap<String, CookieWrapper>()
private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
private var isLoaded = false
@WorkerThread
override fun loadForRequest(url: HttpUrl): List<Cookie> {
loadPersistent()
val expired = HashSet<String>()
val result = ArrayList<Cookie>()
for ((key, cookie) in cache) {
if (cookie.isExpired()) {
expired += key
} else if (cookie.cookie.matches(url)) {
result += cookie.cookie
}
}
if (expired.isNotEmpty()) {
cache.removeAll(expired)
removePersistent(expired)
}
return result
}
@WorkerThread
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
val wrapped = cookies.map { CookieWrapper(it) }
prefs.edit(commit = true) {
for (cookie in wrapped) {
val key = cookie.key()
cache[key] = cookie
if (cookie.cookie.persistent) {
putString(key, cookie.encode())
}
}
}
}
override suspend fun clear(): Boolean {
cache.clear()
withContext(Dispatchers.IO) {
prefs.edit(commit = true) { clear() }
}
return true
}
@Synchronized
private fun loadPersistent() {
if (!isLoaded) {
val map = prefs.all
cache.ensureCapacity(map.size)
for ((k, v) in map) {
val cookie = try {
CookieWrapper(v as String)
} catch (e: Exception) {
e.printStackTraceDebug()
continue
}
cache[k] = cookie
}
isLoaded = true
}
}
private fun removePersistent(keys: Collection<String>) {
prefs.edit(commit = true) {
for (key in keys) {
remove(key)
}
}
}
}

View File

@@ -0,0 +1,46 @@
package org.koitharu.kotatsu.core.os
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkRequest
import kotlinx.coroutines.flow.first
import org.koitharu.kotatsu.utils.MediatorStateFlow
import org.koitharu.kotatsu.utils.ext.isNetworkAvailable
class NetworkState(
private val connectivityManager: ConnectivityManager,
) : MediatorStateFlow<Boolean>(connectivityManager.isNetworkAvailable) {
private val callback = NetworkCallbackImpl()
override fun onActive() {
invalidate()
val request = NetworkRequest.Builder().build()
connectivityManager.registerNetworkCallback(request, callback)
}
override fun onInactive() {
connectivityManager.unregisterNetworkCallback(callback)
}
suspend fun awaitForConnection() {
if (value) {
return
}
first { it }
}
private fun invalidate() {
publishValue(connectivityManager.isNetworkAvailable)
}
private inner class NetworkCallbackImpl : NetworkCallback() {
override fun onAvailable(network: Network) = invalidate()
override fun onLost(network: Network) = invalidate()
override fun onUnavailable() = invalidate()
}
}

View File

@@ -1,78 +0,0 @@
package org.koitharu.kotatsu.core.os
import android.content.Context
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkRequest
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onSuccess
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.first
import org.koitharu.kotatsu.utils.ext.connectivityManager
import org.koitharu.kotatsu.utils.ext.isNetworkAvailable
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NetworkStateObserver @Inject constructor(
@ApplicationContext context: Context,
) : StateFlow<Boolean> {
private val connectivityManager = context.connectivityManager
override val replayCache: List<Boolean>
get() = listOf(value)
override val value: Boolean
get() = connectivityManager.isNetworkAvailable
override suspend fun collect(collector: FlowCollector<Boolean>): Nothing {
collector.emit(value)
while (true) {
observeImpl().collect(collector)
}
}
suspend fun awaitForConnection(): Unit {
if (value) {
return
}
first { it }
}
private fun observeImpl() = callbackFlow<Boolean> {
val request = NetworkRequest.Builder().build()
val callback = FlowNetworkCallback(this)
connectivityManager.registerNetworkCallback(request, callback)
awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
}
private inner class FlowNetworkCallback(
private val producerScope: ProducerScope<Boolean>,
) : NetworkCallback() {
private var prevValue = value
override fun onAvailable(network: Network) = update()
override fun onLost(network: Network) = update()
override fun onUnavailable() = update()
private fun update() {
val newValue = connectivityManager.isNetworkAvailable
if (newValue != prevValue) {
producerScope.trySendBlocking(newValue).onSuccess {
prevValue = newValue
}
}
}
}
}

View File

@@ -6,25 +6,25 @@ import android.util.Base64
import android.webkit.WebView
import androidx.core.os.LocaleListCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.koitharu.kotatsu.core.network.AndroidCookieJar
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.toList
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@Singleton
class MangaLoaderContextImpl @Inject constructor(
override val httpClient: OkHttpClient,
override val cookieJar: AndroidCookieJar,
override val cookieJar: MutableCookieJar,
@ApplicationContext private val androidContext: Context,
) : MangaLoaderContext() {

View File

@@ -52,10 +52,13 @@ class ZipOutput(
return if (entryNames.add(entry.name)) {
val zipEntry = ZipEntry(entry.name)
output.putNextEntry(zipEntry)
other.getInputStream(entry).use { input ->
input.copyTo(output)
try {
other.getInputStream(entry).use { input ->
input.copyTo(output)
}
} finally {
output.closeEntry()
}
output.closeEntry()
true
} else {
false

View File

@@ -228,18 +228,18 @@ class DetailsActivity :
}
}
private fun onHistoryChanged(info: HistoryInfo?) {
private fun onHistoryChanged(info: HistoryInfo) {
with(binding.buttonRead) {
if (info?.history != null) {
if (info.history != null) {
setText(R.string._continue)
setIconResource(R.drawable.ic_play)
setIconResource(if (info.isIncognitoMode) R.drawable.ic_incognito else R.drawable.ic_play)
} else {
setText(R.string.read)
setIconResource(R.drawable.ic_read)
setIconResource(if (info.isIncognitoMode) R.drawable.ic_incognito else R.drawable.ic_play)
}
}
val text = when {
info == null -> getString(R.string.loading_)
!info.isValid -> getString(R.string.loading_)
info.currentChapter >= 0 -> getString(R.string.chapter_d_of_d, info.currentChapter + 1, info.totalChapters)
info.totalChapters == 0 -> getString(R.string.no_chapters)
else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters)

View File

@@ -18,7 +18,6 @@ import coil.request.ImageRequest
import coil.util.CoilUtils
import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
@@ -27,9 +26,9 @@ import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
@@ -45,8 +44,20 @@ import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.*
import org.koitharu.kotatsu.utils.ext.computeSize
import org.koitharu.kotatsu.utils.ext.crossfade
import org.koitharu.kotatsu.utils.ext.drawableTop
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.ifNullOrEmpty
import org.koitharu.kotatsu.utils.ext.measureHeight
import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.resolveDp
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.utils.ext.textAndVisible
import org.koitharu.kotatsu.utils.ext.toFileOrNull
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import org.koitharu.kotatsu.utils.image.CoverSizeResolver
import javax.inject.Inject
@AndroidEntryPoint
class DetailsFragment :
@@ -75,7 +86,7 @@ class DetailsFragment :
binding.chipsTags.onChipClickListener = this
viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated)
viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)
viewModel.readingHistory.observe(viewLifecycleOwner, ::onHistoryChanged)
viewModel.historyInfo.observe(viewLifecycleOwner, ::onHistoryChanged)
viewModel.bookmarks.observe(viewLifecycleOwner, ::onBookmarksChanged)
viewModel.scrobblingInfo.observe(viewLifecycleOwner, ::onScrobblingInfoChanged)
viewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged)
@@ -123,12 +134,14 @@ class DetailsFragment :
drawableTop = ContextCompat.getDrawable(context, R.drawable.ic_state_finished)
}
}
MangaState.ONGOING -> {
infoLayout.textViewState.apply {
textAndVisible = resources.getString(R.string.state_ongoing)
drawableTop = ContextCompat.getDrawable(context, R.drawable.ic_state_ongoing)
}
}
else -> infoLayout.textViewState.isVisible = false
}
if (manga.source == MangaSource.LOCAL) {
@@ -178,8 +191,8 @@ class DetailsFragment :
}
}
private fun onHistoryChanged(history: MangaHistory?) {
binding.progressView.setPercent(history?.percent ?: PROGRESS_NONE, animate = true)
private fun onHistoryChanged(history: HistoryInfo) {
binding.progressView.setPercent(history.history?.percent ?: PROGRESS_NONE, animate = true)
}
private fun onLoadingStateChanged(isLoading: Boolean) {
@@ -229,6 +242,7 @@ class DetailsFragment :
),
)
}
R.id.textView_source -> {
startActivity(
MangaListActivity.newIntent(
@@ -237,6 +251,7 @@ class DetailsFragment :
),
)
}
R.id.imageView_cover -> {
startActivity(
ImageActivity.newIntent(v.context, manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }),
@@ -249,7 +264,7 @@ class DetailsFragment :
override fun onLongClick(v: View): Boolean {
when (v.id) {
R.id.button_read -> {
if (viewModel.readingHistory.value == null) {
if (viewModel.historyInfo.value?.history == null) {
return false
}
val menu = PopupMenu(v.context, v)
@@ -271,12 +286,14 @@ class DetailsFragment :
)
true
}
else -> false
}
}
menu.show()
return true
}
else -> return false
}
}

View File

@@ -13,10 +13,18 @@ import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
@@ -46,6 +54,7 @@ import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import java.io.IOException
class DetailsViewModel @AssistedInject constructor(
@Assisted intent: MangaIntent,
@@ -91,17 +100,18 @@ class DetailsViewModel @AssistedInject constructor(
val manga = delegate.manga.filterNotNull().asLiveData(viewModelScope.coroutineContext)
val favouriteCategories = favourite.asLiveData(viewModelScope.coroutineContext)
val newChaptersCount = newChapters.asLiveData(viewModelScope.coroutineContext)
@Deprecated("")
val readingHistory = history.asLiveData(viewModelScope.coroutineContext)
val isChaptersReversed = chaptersReversed.asLiveData(viewModelScope.coroutineContext)
val historyInfo = combine(
val historyInfo: LiveData<HistoryInfo> = combine(
delegate.manga,
history,
) { m, h ->
HistoryInfo(m, h)
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, null)
settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { isIncognitoModeEnabled },
) { m, h, im ->
HistoryInfo(m, h, im)
}.asFlowLiveData(
context = viewModelScope.coroutineContext + Dispatchers.Default,
defaultValue = HistoryInfo(null, null, false),
)
val bookmarks = delegate.manga.flatMapLatest {
if (it != null) bookmarksRepository.observeBookmarks(it) else flowOf(emptyList())

View File

@@ -42,7 +42,8 @@ class ChaptersAdapter(
}
}
override fun getSectionText(context: Context, position: Int): CharSequence {
return items[position].chapter.number.toString()
override fun getSectionText(context: Context, position: Int): CharSequence? {
val item = items.getOrNull(position) ?: return null
return item.chapter.number.toString()
}
}
}

View File

@@ -7,8 +7,12 @@ class HistoryInfo(
val totalChapters: Int,
val currentChapter: Int,
val history: MangaHistory?,
val isIncognitoMode: Boolean,
) {
val isValid: Boolean
get() = totalChapters >= 0
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@@ -18,6 +22,7 @@ class HistoryInfo(
if (totalChapters != other.totalChapters) return false
if (currentChapter != other.currentChapter) return false
if (history != other.history) return false
if (isIncognitoMode != other.isIncognitoMode) return false
return true
}
@@ -26,20 +31,21 @@ class HistoryInfo(
var result = totalChapters
result = 31 * result + currentChapter
result = 31 * result + (history?.hashCode() ?: 0)
result = 31 * result + isIncognitoMode.hashCode()
return result
}
}
@Suppress("FunctionName")
fun HistoryInfo(manga: Manga?, history: MangaHistory?): HistoryInfo? {
val chapters = manga?.chapters ?: return null
fun HistoryInfo(manga: Manga?, history: MangaHistory?, isIncognitoMode: Boolean): HistoryInfo {
val chapters = manga?.chapters
return HistoryInfo(
totalChapters = chapters.size,
currentChapter = if (history != null) {
totalChapters = chapters?.size ?: -1,
currentChapter = if (history != null && !chapters.isNullOrEmpty()) {
chapters.indexOfFirst { it.id == history.chapterId }
} else {
-1
},
history = history,
isIncognitoMode = isIncognitoMode,
)
}

View File

@@ -2,8 +2,10 @@ package org.koitharu.kotatsu.explore.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels
@@ -11,11 +13,12 @@ import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.reverseAsync
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.bookmarks.ui.BookmarksActivity
import org.koitharu.kotatsu.databinding.FragmentExploreBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
@@ -31,6 +34,7 @@ import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import javax.inject.Inject
@AndroidEntryPoint
class ExploreFragment :
@@ -67,6 +71,7 @@ class ExploreFragment :
}
viewModel.onError.observe(viewLifecycleOwner, ::onError)
viewModel.onOpenManga.observe(viewLifecycleOwner, ::onOpenManga)
viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone)
}
override fun onDestroyView() {
@@ -95,6 +100,7 @@ class ExploreFragment :
viewModel.openRandom()
return
}
else -> return
}
startActivity(intent)
@@ -105,6 +111,14 @@ class ExploreFragment :
startActivity(intent)
}
override fun onItemLongClick(item: ExploreItem.Source, view: View): Boolean {
val menu = PopupMenu(view.context, view)
menu.inflate(R.menu.popup_source)
menu.setOnMenuItemClickListener(SourceMenuListener(item))
menu.show()
return true
}
override fun onRetryClick(error: Throwable) = Unit
override fun onEmptyActionClick() = onManageClick(requireView())
@@ -124,6 +138,37 @@ class ExploreFragment :
startActivity(intent)
}
private fun onActionDone(action: ReversibleAction) {
val handle = action.handle
val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG
val snackbar = Snackbar.make(binding.recyclerView, action.stringResId, length)
if (handle != null) {
snackbar.setAction(R.string.undo) { handle.reverseAsync() }
}
snackbar.anchorView = (activity as? BottomNavOwner)?.bottomNav
snackbar.show()
}
private inner class SourceMenuListener(
private val sourceItem: ExploreItem.Source,
) : PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_settings -> {
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), sourceItem.source))
}
R.id.action_hide -> {
viewModel.hideSource(sourceItem.source)
}
else -> return false
}
return true
}
}
companion object {
fun newInstance() = ExploreFragment()

View File

@@ -4,11 +4,17 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.ReversibleHandle
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.explore.domain.ExploreRepository
import org.koitharu.kotatsu.explore.ui.model.ExploreItem
@@ -16,6 +22,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import javax.inject.Inject
@HiltViewModel
class ExploreViewModel @Inject constructor(
@@ -24,6 +31,7 @@ class ExploreViewModel @Inject constructor(
) : BaseViewModel() {
val onOpenManga = SingleLiveEvent<Manga>()
val onActionDone = SingleLiveEvent<ReversibleAction>()
val content: LiveData<List<ExploreItem>> = isLoading.asFlow().flatMapLatest { loading ->
if (loading) {
@@ -40,6 +48,16 @@ class ExploreViewModel @Inject constructor(
}
}
fun hideSource(source: MangaSource) {
launchJob(Dispatchers.Default) {
settings.hiddenSources += source.name
val rollback = ReversibleHandle {
settings.hiddenSources -= source.name
}
onActionDone.postCall(ReversibleAction(R.string.source_disabled, rollback))
}
}
private fun createContentFlow() = settings.observe()
.filter {
it == AppSettings.KEY_SOURCES_HIDDEN ||

View File

@@ -14,14 +14,14 @@ class HistoryListAdapter(
listener: MangaListListener
) : MangaListAdapter(coil, lifecycleOwner, listener), FastScroller.SectionIndexer {
override fun getSectionText(context: Context, position: Int): CharSequence {
override fun getSectionText(context: Context, position: Int): CharSequence? {
val list = items
for (i in (0..position).reversed()) {
val item = list[i]
val item = list.getOrNull(i) ?: continue
if (item is DateTimeAgo) {
return item.format(context.resources)
}
}
return ""
return null
}
}
}

View File

@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.utils.ext.copyToSuspending
import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.subdir
import org.koitharu.kotatsu.utils.ext.takeIfReadable
import org.koitharu.kotatsu.utils.ext.takeIfWriteable
import java.io.File
import java.io.InputStream
import javax.inject.Inject
@@ -18,9 +19,14 @@ import javax.inject.Singleton
@Singleton
class PagesCache @Inject constructor(@ApplicationContext context: Context) {
private val cacheDir = context.externalCacheDir ?: context.cacheDir
private val cacheDir = checkNotNull(findSuitableDir(context)) {
val dirs = (context.externalCacheDirs + context.cacheDir).joinToString(";") {
it.absolutePath
}
"Cannot find any suitable directory for PagesCache: [$dirs]"
}
private val lruCache = createDiskLruCacheSafe(
dir = cacheDir.subdir(CacheDir.PAGES.dir),
dir = cacheDir,
size = FileSize.MEGABYTES.convert(200, FileSize.BYTES),
)
@@ -29,7 +35,7 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
}
suspend fun put(url: String, inputStream: InputStream): File = withContext(Dispatchers.IO) {
val file = File(cacheDir, url.longHashCode().toString())
val file = File(cacheDir.parentFile, url.longHashCode().toString())
try {
file.outputStream().use { out ->
inputStream.copyToSuspending(out)
@@ -50,3 +56,10 @@ private fun createDiskLruCacheSafe(dir: File, size: Long): DiskLruCache {
DiskLruCache.create(dir, size)
}
}
private fun findSuitableDir(context: Context): File? {
val dirs = context.externalCacheDirs + context.cacheDir
return dirs.firstNotNullOfOrNull {
it.subdir(CacheDir.PAGES.dir).takeIfWriteable()
}
}

View File

@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.utils.AlphanumComparator
import org.koitharu.kotatsu.utils.ext.copyToSuspending
import org.koitharu.kotatsu.utils.ext.deleteAwait
import org.koitharu.kotatsu.utils.ext.longOf
@@ -58,7 +59,7 @@ class DirMangaImporter(
private suspend fun addPages(output: CbzMangaOutput, root: DocumentFile, path: String, state: State) {
var number = 0
for (file in root.listFiles()) {
for (file in root.listFiles().sortedWith(compareBy(AlphanumComparator()) { it.name.orEmpty() })) {
when {
file.isDirectory -> {
addPages(output, file, path + "/" + file.name, state)

View File

@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.github.AppUpdateRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.prefs.observeAsLiveData
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.parsers.model.Manga
@@ -34,9 +35,12 @@ class MainViewModel @Inject constructor(
val onOpenReader = SingleLiveEvent<Manga>()
val isResumeEnabled = historyRepository
.observeHasItems()
.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, false)
val isResumeEnabled = combine(
historyRepository.observeHasItems(),
settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { isIncognitoModeEnabled },
) { hasItems, incognito ->
hasItems && !incognito
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, false)
val isFeedAvailable = settings.observeAsLiveData(
context = viewModelScope.coroutineContext + Dispatchers.Default,

View File

@@ -313,7 +313,7 @@ class ReaderViewModel @AssistedInject constructor(
} ?: ReaderState(manga, preselectedBranch)
}
val branch = chapters[currentState.value?.chapterId ?: 0L].branch
val branch = chapters[currentState.value?.chapterId ?: 0L]?.branch
mangaData.value = manga.filterChapters(branch)
readerMode.postValue(mode)

View File

@@ -5,7 +5,7 @@ import androidx.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.LayoutPageInfoBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
@@ -14,12 +14,12 @@ abstract class BasePageHolder<B : ViewBinding>(
protected val binding: B,
loader: PageLoader,
settings: ReaderSettings,
networkStateObserver: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) : RecyclerView.ViewHolder(binding.root), PageHolderDelegate.Callback {
@Suppress("LeakingThis")
protected val delegate = PageHolderDelegate(loader, settings, this, networkStateObserver, exceptionResolver)
protected val delegate = PageHolderDelegate(loader, settings, this, networkState, exceptionResolver)
protected val bindingInfo = LayoutPageInfoBinding.bind(binding.root)
val context: Context

View File

@@ -5,7 +5,7 @@ import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
import org.koitharu.kotatsu.utils.ext.resetTransformations
@@ -16,7 +16,7 @@ import kotlin.coroutines.suspendCoroutine
abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
private val loader: PageLoader,
private val readerSettings: ReaderSettings,
private val networkState: NetworkStateObserver,
private val networkState: NetworkState,
private val exceptionResolver: ExceptionResolver,
) : RecyclerView.Adapter<H>() {
@@ -70,7 +70,7 @@ abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
parent: ViewGroup,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
): H

View File

@@ -17,10 +17,11 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import java.io.File
import java.io.IOException
@@ -28,7 +29,7 @@ class PageHolderDelegate(
private val loader: PageLoader,
private val readerSettings: ReaderSettings,
private val callback: Callback,
private val networkState: NetworkStateObserver,
private val networkState: NetworkState,
private val exceptionResolver: ExceptionResolver,
) : DefaultOnImageEventListener, Observer<ReaderSettings> {
@@ -138,6 +139,7 @@ class PageHolderDelegate(
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
e.printStackTraceDebug()
state = State.ERROR
error = e
callback.onError(e)

View File

@@ -3,22 +3,24 @@ package org.koitharu.kotatsu.reader.ui.pager.reversed
import android.graphics.PointF
import android.view.Gravity
import android.widget.FrameLayout
import androidx.lifecycle.LifecycleOwner
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
import org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder
class ReversedPageHolder(
owner: LifecycleOwner,
binding: ItemPageBinding,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) : PageHolder(binding, loader, settings, networkState, exceptionResolver) {
) : PageHolder(owner, binding, loader, settings, networkState, exceptionResolver) {
init {
(binding.textViewNumber.layoutParams as FrameLayout.LayoutParams)

View File

@@ -2,17 +2,19 @@ package org.koitharu.kotatsu.reader.ui.pager.reversed
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
class ReversedPagesAdapter(
private val lifecycleOwner: LifecycleOwner,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) : BaseReaderAdapter<ReversedPageHolder>(loader, settings, networkState, exceptionResolver) {
@@ -20,9 +22,10 @@ class ReversedPagesAdapter(
parent: ViewGroup,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) = ReversedPageHolder(
owner = lifecycleOwner,
binding = ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false),
loader = loader,
settings = settings,

View File

@@ -8,7 +8,7 @@ import android.view.ViewGroup
import androidx.core.view.children
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
@@ -26,7 +26,7 @@ import kotlin.math.absoluteValue
class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
@Inject
lateinit var networkStateObserver: NetworkStateObserver
lateinit var networkState: NetworkState
private var pagerAdapter: ReversedPagesAdapter? = null
@@ -39,10 +39,11 @@ class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
pagerAdapter = ReversedPagesAdapter(
viewModel.pageLoader,
viewModel.readerSettings,
networkStateObserver,
exceptionResolver,
lifecycleOwner = viewLifecycleOwner,
loader = viewModel.pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
with(binding.pager) {
adapter = pagerAdapter

View File

@@ -5,12 +5,13 @@ import android.graphics.PointF
import android.net.Uri
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
@@ -19,15 +20,17 @@ import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.utils.ext.*
open class PageHolder(
owner: LifecycleOwner,
binding: ItemPageBinding,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) : BasePageHolder<ItemPageBinding>(binding, loader, settings, networkState, exceptionResolver),
View.OnClickListener {
init {
binding.ssiv.bindToLifecycle(owner)
binding.ssiv.isEagerLoadingEnabled = !isLowRamDevice(context)
binding.ssiv.addOnImageEventListener(delegate)
@Suppress("LeakingThis")

View File

@@ -8,7 +8,7 @@ import android.view.ViewGroup
import androidx.core.view.children
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
@@ -25,7 +25,7 @@ import kotlin.math.absoluteValue
class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
@Inject
lateinit var networkStateObserver: NetworkStateObserver
lateinit var networkState: NetworkState
private var pagesAdapter: PagesAdapter? = null
@@ -38,10 +38,11 @@ class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
pagesAdapter = PagesAdapter(
viewModel.pageLoader,
viewModel.readerSettings,
networkStateObserver,
exceptionResolver,
lifecycleOwner = viewLifecycleOwner,
loader = viewModel.pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
with(binding.pager) {
adapter = pagesAdapter

View File

@@ -2,27 +2,30 @@ package org.koitharu.kotatsu.reader.ui.pager.standard
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
class PagesAdapter(
private val lifecycleOwner: LifecycleOwner,
loader: PageLoader,
settings: ReaderSettings,
networkStateObserver: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) : BaseReaderAdapter<PageHolder>(loader, settings, networkStateObserver, exceptionResolver) {
) : BaseReaderAdapter<PageHolder>(loader, settings, networkState, exceptionResolver) {
override fun onCreateViewHolder(
parent: ViewGroup,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) = PageHolder(
owner = lifecycleOwner,
binding = ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false),
loader = loader,
settings = settings,

View File

@@ -2,17 +2,19 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
class WebtoonAdapter(
private val lifecycleOwner: LifecycleOwner,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) : BaseReaderAdapter<WebtoonHolder>(loader, settings, networkState, exceptionResolver) {
@@ -20,9 +22,10 @@ class WebtoonAdapter(
parent: ViewGroup,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) = WebtoonHolder(
owner = lifecycleOwner,
binding = ItemPageWebtoonBinding.inflate(
LayoutInflater.from(parent.context),
parent,

View File

@@ -3,12 +3,13 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.net.Uri
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.davemorrissey.labs.subscaleview.decoder.SkiaPooledImageRegionDecoder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
@@ -22,10 +23,11 @@ import org.koitharu.kotatsu.utils.ext.setProgressCompat
import org.koitharu.kotatsu.utils.ext.showCompat
class WebtoonHolder(
owner: LifecycleOwner,
binding: ItemPageWebtoonBinding,
loader: PageLoader,
settings: ReaderSettings,
networkState: NetworkStateObserver,
networkState: NetworkState,
exceptionResolver: ExceptionResolver,
) : BasePageHolder<ItemPageWebtoonBinding>(binding, loader, settings, networkState, exceptionResolver),
View.OnClickListener {
@@ -34,6 +36,7 @@ class WebtoonHolder(
private val goneOnInvisibleListener = GoneOnInvisibleListener(bindingInfo.progressBar)
init {
binding.ssiv.bindToLifecycle(owner)
binding.ssiv.regionDecoderFactory = SkiaPooledImageRegionDecoder.Factory()
binding.ssiv.addOnImageEventListener(delegate)
bindingInfo.buttonRetry.setOnClickListener(this)

View File

@@ -7,7 +7,7 @@ import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
@@ -22,7 +22,7 @@ import javax.inject.Inject
class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
@Inject
lateinit var networkStateObserver: NetworkStateObserver
lateinit var networkState: NetworkState
private val scrollInterpolator = AccelerateDecelerateInterpolator()
private var webtoonAdapter: WebtoonAdapter? = null
@@ -35,10 +35,11 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
webtoonAdapter = WebtoonAdapter(
viewModel.pageLoader,
viewModel.readerSettings,
networkStateObserver,
exceptionResolver,
lifecycleOwner = viewLifecycleOwner,
loader = viewModel.pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
with(binding.recyclerView) {
setHasFixedSize(true)

View File

@@ -12,7 +12,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.network.AndroidCookieJar
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.local.data.CacheDir
@@ -45,7 +45,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
lateinit var malRepository: MALRepository
@Inject
lateinit var cookieJar: AndroidCookieJar
lateinit var cookieJar: MutableCookieJar
@Inject
lateinit var shortcutsUpdater: ShortcutsUpdater

View File

@@ -86,7 +86,6 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
}.onSuccess { username ->
preference.title = getString(R.string.logged_in_as, username)
}.onFailure { error ->
preference.isEnabled = error is AuthRequiredException
when {
error is AuthRequiredException -> Unit
ExceptionResolver.canResolve(error) -> {

View File

@@ -12,7 +12,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow
@@ -46,14 +46,14 @@ class ShelfViewModel @Inject constructor(
private val favouritesRepository: FavouritesRepository,
private val trackingRepository: TrackingRepository,
private val settings: AppSettings,
networkStateObserver: NetworkStateObserver,
networkState: NetworkState,
) : BaseViewModel(), ListExtraProvider {
val onActionDone = SingleLiveEvent<ReversibleAction>()
val content: LiveData<List<ListModel>> = combine(
settings.observeAsFlow(AppSettings.KEY_SHELF_SECTIONS) { shelfSections },
networkStateObserver,
networkState,
repository.observeShelfContent(),
) { sections, isConnected, content ->
mapList(content, sections, isConnected)

View File

@@ -46,9 +46,9 @@ class ShelfAdapter(
.addDelegate(errorStateListAD(listener))
}
override fun getSectionText(context: Context, position: Int): CharSequence {
val item = items.getOrNull(position) as? ShelfSectionModel
return item?.getTitle(context.resources) ?: ""
override fun getSectionText(context: Context, position: Int): CharSequence? {
val item = items.getOrNull(position) as? ShelfSectionModel ?: return null
return item.getTitle(context.resources)
}
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {

View File

@@ -5,16 +5,17 @@ import android.accounts.AccountManager
import android.content.ContentResolver
import android.content.Context
import android.os.Bundle
import android.util.ArrayMap
import androidx.collection.ArrayMap
import androidx.room.InvalidationTracker
import androidx.room.withTransaction
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.MangaDatabase
@@ -22,6 +23,9 @@ import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SyncController @Inject constructor(

View File

@@ -1,14 +1,14 @@
package org.koitharu.kotatsu.utils
import android.util.ArrayMap
import java.util.*
import kotlin.coroutines.coroutineContext
import kotlin.coroutines.resume
import androidx.collection.ArrayMap
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.isActive
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.LinkedList
import kotlin.coroutines.coroutineContext
import kotlin.coroutines.resume
class CompositeMutex<T : Any> : Set<T> {
@@ -27,7 +27,7 @@ class CompositeMutex<T : Any> : Set<T> {
}
override fun isEmpty(): Boolean {
return data.isEmpty()
return data.isEmpty
}
override fun iterator(): Iterator<T> {
@@ -59,7 +59,7 @@ class CompositeMutex<T : Any> : Set<T> {
private suspend fun waitForRemoval(element: T) {
val list = data[element] ?: return
suspendCancellableCoroutine<Unit> { continuation ->
suspendCancellableCoroutine { continuation ->
list.add(continuation)
continuation.invokeOnCancellation {
list.remove(continuation)

View File

@@ -0,0 +1,39 @@
package org.koitharu.kotatsu.utils
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.concurrent.atomic.AtomicInteger
abstract class MediatorStateFlow<T>(initialValue: T) : StateFlow<T> {
private val delegate = MutableStateFlow(initialValue)
private val collectors = AtomicInteger(0)
final override val replayCache: List<T>
get() = delegate.replayCache
final override val value: T
get() = delegate.value
final override suspend fun collect(collector: FlowCollector<T>): Nothing {
try {
if (collectors.getAndIncrement() == 0) {
onActive()
}
delegate.collect(collector)
} finally {
if (collectors.decrementAndGet() == 0) {
onInactive()
}
}
}
protected fun publishValue(v: T) {
delegate.value = v
}
abstract fun onActive()
abstract fun onInactive()
}

View File

@@ -5,12 +5,16 @@ import android.content.Context
import android.content.Intent
import android.speech.RecognizerIntent
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.os.ConfigurationCompat
import java.util.Locale
class VoiceInputContract : ActivityResultContract<String?, String?>() {
override fun createIntent(context: Context, input: String?): Intent {
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
val locale = ConfigurationCompat.getLocales(context.resources.configuration).get(0) ?: Locale.getDefault()
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, locale.toLanguageTag())
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, input)
return intent
}
@@ -23,4 +27,4 @@ class VoiceInputContract : ActivityResultContract<String?, String?>() {
null
}
}
}
}

View File

@@ -23,6 +23,8 @@ fun File.subdir(name: String) = File(this, name).also {
fun File.takeIfReadable() = takeIf { it.exists() && it.canRead() }
fun File.takeIfWriteable() = takeIf { it.exists() && it.canWrite() }
fun ZipFile.readText(entry: ZipEntry) = getInputStream(entry).bufferedReader().use {
it.readText()
}
@@ -74,4 +76,4 @@ private fun computeSizeInternal(file: File): Long {
} else {
return file.length()
}
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_settings"
android:title="@string/settings" />
<item
android:id="@+id/action_hide"
android:title="@string/hide" />
</menu>

View File

@@ -70,7 +70,7 @@
<string name="list">Liste</string>
<string name="chapters">Kapitel</string>
<string name="details">Einzelheiten</string>
<string name="network_error">Keine Verbindung zum Internet möglich</string>
<string name="network_error">Netzwerkfehler</string>
<string name="error_occurred">Es ist ein Fehler aufgetreten</string>
<string name="history">Verlauf</string>
<string name="favourites">Favoriten</string>
@@ -372,4 +372,23 @@
<string name="import_completed">Import abgeschlossen</string>
<string name="import_completed_hint">Du kannst die Originaldatei aus dem Speicher löschen, um Platz zu sparen</string>
<string name="import_will_start_soon">Import wird bald beginnen</string>
<string name="server_error">Serverseitiger Fehler (%1$d). Bitte versuchen Sie es später noch einmal</string>
<string name="compact">Kompakt</string>
<string name="contrast">Kontrast</string>
<string name="network_unavailable_hint">Schalten Sie Wi-Fi oder ein mobiles Netzwerk ein, um Manga online zu lesen</string>
<string name="clear_new_chapters_counters">Auch klare Informationen über neue Kapitel</string>
<string name="text_unsaved_changes_prompt">Ungespeicherte Änderungen speichern oder verwerfen\?</string>
<string name="discard">Verwerfen</string>
<string name="reset">Zurücksetzen</string>
<string name="brightness">Helligkeit</string>
<string name="color_correction_hint">Die gewählten Farbeinstellungen werden für diesen Manga in Erinnerung bleiben</string>
<string name="color_correction">Farbkorrektur</string>
<string name="error_no_space_left">Kein Platz mehr auf dem Gerät</string>
<string name="different_languages">Verschiedene Sprachen</string>
<string name="network_unavailable">Netzwerk ist nicht verfügbar</string>
<string name="webtoon_zoom_summary">Vergrößerungs-/Verkleinerungsgesten im Webtoon-Modus zulassen (beta)</string>
<string name="reader_control_ltr">Ergonomische Leserkontrolle</string>
<string name="reader_control_ltr_summary">Tippe auf den rechten Rand oder drücke die rechte Taste, um immer zur nächsten Seite zu wechseln</string>
<string name="reader_slider">Seitenwechsel-Schieberegler anzeigen</string>
<string name="source_disabled">Quelle deaktiviert</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="days_ago">
<item quantity="one">%1$d μέρα πριν</item>
<item quantity="other">%1$d μέρες πριν</item>
</plurals>
<plurals name="items">
<item quantity="one">%1$dστοιχείο</item>
<item quantity="other">%1$dστοιχεία</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$dνέο κεφάλαιο</item>
<item quantity="other">%1$dνέα κεφάλαια</item>
</plurals>
<plurals name="chapters_from_x">
<item quantity="one">%1$dκεφάλαιο%2$d</item>
<item quantity="other">%1$dκεφάλαια%2$d</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">%1$dώρα πριν</item>
<item quantity="other">%1$dώρες πριν</item>
</plurals>
<plurals name="chapters">
<item quantity="one">%1$dκεφάλαιο</item>
<item quantity="other">%1$dκεφάλαια</item>
</plurals>
<plurals name="pages">
<item quantity="one">Σύνολο%1$dσελίδα</item>
<item quantity="other">Σύνολο%1$dσελίδες</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">%1$dλεπτό πριν</item>
<item quantity="other">%1$d λεπτά πριν</item>
</plurals>
</resources>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="local_storage">Εσωτερικός χώρος</string>
<string name="close_menu">Κλείσιμο μενού</string>
<string name="open_menu">Άνοιγμα μενού</string>
<string name="favourites">Αγαπημένα</string>
<string name="history">Ιστορικό</string>
<string name="error_occurred">Προέκυψε σφάλμα</string>
<string name="try_again">Επανάληψη</string>
<string name="grid">Πλέγμα</string>
<string name="list_mode">Εμφάνιση ως λίστα</string>
<string name="settings">Ρυθμίσεις</string>
<string name="remote_sources">Απομακρυσμένες πηγές</string>
<string name="computing_">Επεξεργασία…</string>
<string name="close">Κλείσιμο</string>
<string name="clear_history">Εκκαθάριση ιστορικού</string>
<string name="nothing_found">Δεν βρέθηκε τίποτα</string>
<string name="history_is_empty">Κενό ιστορικό</string>
<string name="read">Διάβασε</string>
<string name="add_to_favourites">Προσθήκη στα αγαπημένα</string>
<string name="add_new_category">Νέα κατηγορία</string>
<string name="save">Αποθήκευση</string>
<string name="share">Κοινοποιήση</string>
<string name="create_shortcut">Δημιουργία συντόμευσης…</string>
<string name="share_s">Κοινοποίηση %s</string>
<string name="search">Αναζήτηση</string>
<string name="search_manga">Αναζήτηση μάνγκα</string>
<string name="manga_downloading_">Λήψη…</string>
<string name="download_complete">Κατεβασμένο</string>
<string name="downloads">Λήψεις</string>
<string name="updated">Ενημερωμένο</string>
<string name="newest">Νεότερο</string>
<string name="by_rating">Βαθμολογία</string>
<string name="filter">Φίλτρο</string>
<string name="dark">Σκοτεινό</string>
<string name="automatic">Όπως στο σύστημα</string>
<string name="clear">Εκκαθάριση</string>
<string name="text_clear_history_prompt">Να διαγράψετε μόνιμα όλο το ιστορικό ανάγνωσης;</string>
<string name="remove">Διαγραφή</string>
<string name="save_page">Αποθήκευση σελίδας</string>
<string name="page_saved">Αποθηκευμένα</string>
<string name="share_image">Κοινή χρήση εικόνας</string>
<string name="_import">Εισαγωγή</string>
<string name="delete">Διαγραφή</string>
<string name="text_file_not_supported">Επιλέξτε ένα αρχείο ZIP ή CBZ.</string>
<string name="no_description">Χωρίς περιγραφή</string>
<string name="history_and_cache">Ιστορικό και μνήμη cache</string>
<string name="clear_pages_cache">Εκκαθάριση μνήμης cache της σελίδας</string>
<string name="cache">Προσωρινή Μνήμη</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="standard">Τυπικό</string>
<string name="webtoon">Μάνχγουα</string>
<string name="search_on_s">Αναζήτηση στο %s</string>
<string name="delete_manga">Διαγραφή μάνγκα</string>
<string name="text_delete_local_manga">Μόνιμη διαγραφή του \"%s\" από τη συσκευή;</string>
<string name="reader_settings">Ρυθμίσεις λειτουργίας ανάγνωσης</string>
<string name="switch_pages">Αλλαγή σελίδων</string>
<string name="network_error">Αδυναμία σύνδεσης στο ίντερνετ</string>
<string name="chapters">Κεφάλαια</string>
<string name="details">Πληροφορίες</string>
<string name="list">Λίστα</string>
<string name="detailed_list">Λεπτομερής λίστα</string>
<string name="loading_">Φόρτωση…</string>
<string name="chapter_d_of_d">Κεφάλαιο%1$d από %2$d</string>
<string name="you_have_not_favourites_yet">Δεν υπάρχουν αγαπημένα</string>
<string name="add">Προσθήκη</string>
<string name="enter_category_name">Εισαγωγή ονόματος κατηγορίας</string>
<string name="processing_">Επεξεργασία…</string>
<string name="by_name">Όνομα</string>
<string name="popular">Δημοφιλή</string>
<string name="sort_order">Τρόπος Ταξινόμησης</string>
<string name="_s_removed_from_history">Το \"%s\" αφαιρέθηκε από το ιστορικό</string>
<string name="theme">Θέμα</string>
<string name="light">Φωτεινό</string>
<string name="pages">Σελίδες</string>
<string name="wait_for_loading_finish">Περιμένετε να ολοκληρωθεί η φόρτωση…</string>
<string name="_s_deleted_from_local_storage">Το \"%s\" διαγράφηκε από τον τοπικό χώρο αποθήκευσης</string>
<string name="operation_not_supported">Αυτή η λειτουργία δεν υποστηρίζεται</string>
<string name="read_mode">Λειτουργία ανάγνωσης</string>
<string name="grid_size">Μέγεθος πλέγματος</string>
</resources>

View File

@@ -6,7 +6,7 @@
<string name="favourites">Favoritos</string>
<string name="history">Historial</string>
<string name="error_occurred">Ocurrió un error</string>
<string name="network_error">No se pudo conectar a Internet</string>
<string name="network_error">Error en la red</string>
<string name="details">Detalles</string>
<string name="chapters">Capítulos</string>
<string name="list">Lista</string>
@@ -266,7 +266,7 @@
<string name="suggestions_excluded_genres">Excluir géneros</string>
<string name="suggestions_excluded_genres_summary">Especifica los géneros que no quieres ver en las sugerencias</string>
<string name="removal_completed">Remoción completada</string>
<string name="batch_manga_save_confirm">¿Estás seguro de que quieres descargar todos los manga seleccionados con todos sus capítulos\? Esta acción puede consumir mucho tráfico y almacenamiento</string>
<string name="batch_manga_save_confirm">¿Descargar todos los mangas seleccionados y sus capítulos\? Esto puede consumir mucho tráfico y almacenamiento.</string>
<string name="text_delete_local_manga_batch">¿Eliminar elementos seleccionados del dispositivo de forma permanente\?</string>
<string name="hide">Ocultar</string>
<string name="download_slowdown">Ralentización de la descarga</string>
@@ -375,7 +375,7 @@
<string name="manga_error_description_pattern">Detalles del error:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Intenta &lt;a href=%2$s&gt;abrir el manga en un navegador web&lt;/a&gt; para asegurarte de que está disponible en tu fuente&lt;br&gt;2. Si está disponible, envía un informe de error a los desarrolladores.</string>
<string name="history_shortcuts_summary">Hacer que los mangas recientes estén disponibles mediante una pulsación larga en el icono de la aplicación</string>
<string name="color_correction_hint">Los ajustes de color elegidos serán recordados para este manga</string>
<string name="feed">Feed</string>
<string name="feed">Fuente</string>
<string name="downloading_manga">Descargando manga</string>
<string name="history_shortcuts">Mostrar los accesos directos a los mangas recientes</string>
<string name="reader_control_ltr_summary">Tocando el borde derecho o pulsando la tecla derecha se pasa siempre a la página siguiente</string>
@@ -384,10 +384,17 @@
<string name="brightness">Brillo</string>
<string name="contrast">Contraste</string>
<string name="reset">Restablecer</string>
<string name="text_unsaved_changes_prompt">Tienes cambios sin guardar. ¿Quieres guardarlos o descartarlos\?</string>
<string name="text_unsaved_changes_prompt">¿Guardar o descartar los cambios no guardados\?</string>
<string name="discard">Descartar</string>
<string name="error_no_space_left">Sin espacio en dispositivo</string>
<string name="webtoon_zoom">Zoom de webtoon</string>
<string name="webtoon_zoom_summary">Permitir el gesto de acercamiento/alejamiento en modo webtoon (beta)</string>
<string name="reader_slider">Mostrar el deslizador de cambio de página</string>
<string name="server_error">Error del servidor (%1$d). Vuelva a intentarlo más tarde</string>
<string name="clear_new_chapters_counters">Información clara sobre los nuevos capítulos</string>
<string name="different_languages">Diferentes idiomas</string>
<string name="network_unavailable">La red no está disponible</string>
<string name="compact">Compacta</string>
<string name="network_unavailable_hint">Enciende la Wi-Fi o la red móvil para leer los mangas en línea</string>
<string name="source_disabled">Fuente desactivada</string>
</resources>

View File

@@ -202,7 +202,7 @@
<string name="list">Liste</string>
<string name="chapters">Chapitres</string>
<string name="details">Détails</string>
<string name="network_error">Impossible de se connecter à Internet</string>
<string name="network_error">Erreur réseau</string>
<string name="error_occurred">Une erreur s\'est produite</string>
<string name="history">Historique</string>
<string name="favourites">Favoris</string>
@@ -395,4 +395,5 @@
<string name="compact">Compact</string>
<string name="server_error">Erreur côté serveur (%1$d). Veuillez réessayer plus tard</string>
<string name="clear_new_chapters_counters">Effacer aussi les informations sur les nouveaux chapitres</string>
<string name="source_disabled">Source désactivée</string>
</resources>

View File

@@ -102,15 +102,15 @@
<string name="notification_sound">Suara pemberitahuan</string>
<string name="categories_">Kategori…</string>
<string name="rename">Ubah Nama</string>
<string name="category_delete_confirm">"Hapus kategori \"%s\" dari favorit Anda\?
\nSemua manga disana akan hilang."</string>
<string name="category_delete_confirm">Hapus kategori \"%s\" dari favorit Anda\?
\nSemua manga di situ akan hilang.</string>
<string name="text_empty_holder_primary">Sepi juga di sini…</string>
<string name="text_categories_holder">Anda bisa menggunakan kategori untuk mengelola favorit Anda. Tekan «+» untuk membuat kategori</string>
<string name="text_history_holder_primary">Apa yang Anda baca akan ditampilkan di sini</string>
<string name="text_history_holder_secondary">Cari apa untuk di baca di bilah samping.</string>
<string name="text_local_holder_primary">Simpan sesuatu dulu</string>
<string name="manga_shelf">Rak</string>
<string name="recent_manga">Terbaru</string>
<string name="recent_manga">Baru-baru ini</string>
<string name="pages_animation">Animasi halaman</string>
<string name="manga_save_location">Folder untuk unduhan</string>
<string name="not_available">Tidak tersedia</string>
@@ -298,7 +298,7 @@
<string name="status_completed">Selesai</string>
<string name="canceled">Dibatalkan</string>
<string name="sync_title">Sinkronisasi data Anda</string>
<string name="enter_email_text">Masukkan email Anda untuk melanjutkan</string>
<string name="enter_email_text">Masukkan surel Anda untuk melanjutkan</string>
<string name="tracking">Pelacakan</string>
<string name="logout">Keluar</string>
<string name="sync">Sinkronisasi</string>
@@ -329,7 +329,7 @@
<string name="exit_confirmation_summary">Tekan Kembali dua kali untuk keluar dari aplikasi</string>
<string name="exit_confirmation">Konfirmasi keluar</string>
<string name="pages_cache">Tembolok halaman</string>
<string name="other_cache">Cache lainnya</string>
<string name="other_cache">Tembolok lainnya</string>
<string name="storage_usage">Penggunaan penyimpanan</string>
<string name="available">Tersedia</string>
<string name="incognito_mode">Mode Incognito</string>
@@ -356,14 +356,14 @@
<string name="crash_text">Ada sesuatu yang salah. Mohon untuk mengirim laporan kutu (bug) ke pengembang untuk membantu kami memperbaikinya.</string>
<string name="report">Lapor</string>
<string name="exclude_nsfw_from_history_summary">Manga yang ditandai sebagai NSFW tidak akan ditambahkan ke riwayat dan progres Anda tidak akan disimpan</string>
<string name="clear_cookies_summary">Bisa membantu dalam beberapa masalah. Seluruh otorisasi akan menjadi tidak valid.</string>
<string name="clear_cookies_summary">Bisa membantu dalam beberapa masalah. Seluruh otorisasi akan menjadi tidak valid</string>
<string name="manage">Kelola</string>
<string name="no_manga_sources_text">Aktifkan sumber manga untuk membaca manga daring</string>
<string name="categories_delete_confirm">Apakah Anda yakin ingin menghapus kategori favorit yang dipilih\?
\n Semua manga di sana akan hilang dan ini tidak bisa diurungkan.</string>
<string name="reorder">Atur Ulang</string>
<string name="saved_manga">Manga tersimpan</string>
<string name="confirm_exit">Tekan lagi untuk keluar</string>
<string name="confirm_exit">Tekan Kembali lagi untuk keluar</string>
<string name="no_chapters">Tidak ada bab</string>
<string name="history_shortcuts">Tampilkan pintasan manga baru-baru ini</string>
<string name="history_shortcuts_summary">Buat manga baru-baru ini tersedia dengan menekan panjang pada ikon aplikasi</string>
@@ -372,4 +372,5 @@
<string name="gestures_only">Hanya gestur</string>
<string name="dns_over_https">DNS melalui HTTPS</string>
<string name="status_dropped">Istirahat</string>
<string name="off_short">Mati</string>
</resources>

View File

@@ -320,4 +320,6 @@
<string name="invalid_domain_message">Dominio non valido</string>
<string name="select_range">Seleziona l\'intervallo</string>
<string name="not_found_404">Contenuto non trovato o rimosso</string>
<string name="compact">Compatto</string>
<string name="source_disabled">Fonte disabilitata</string>
</resources>

View File

@@ -63,7 +63,7 @@
<string name="dark">ダークテーマ</string>
<string name="pages">ページ</string>
<string name="theme">テーマ</string>
<string name="network_error">インターネットに接続出来ませんでした</string>
<string name="network_error">ネットワークエラー</string>
<string name="enter_category_name">カテゴリー名を入力してください</string>
<string name="updated">アップデート</string>
<string name="cache">キャッシュ</string>
@@ -395,4 +395,5 @@
<string name="network_unavailable_hint">Wi-Fiまたはモバイルネットワークをオンにして、オンラインでマンガを読むことができます</string>
<string name="webtoon_zoom">Webtoonズーム</string>
<string name="webtoon_zoom_summary">ウェブトゥーンモードでズームイン/ズームアウトのジェスチャーを可能にする(ベータ版)</string>
<string name="compact">コンパクト</string>
</resources>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sort_order">정렬 기준</string>
<string name="_import">불러오기</string>
<string name="network_error">네트워크 오류</string>
<string name="list">목록</string>
<string name="save">저장</string>
<string name="share">공유하기</string>
<string name="share_s">%s 공유</string>
<string name="search">검색하기</string>
<string name="warning">경고</string>
<string name="internal_storage">내부 저장소</string>
<string name="external_storage">외부 저장소</string>
<string name="domain">도메인</string>
<string name="app_update_available">새 버전이 존재합니다</string>
<string name="open_in_browser">웹 브라우저에서 열기</string>
<string name="save_manga">저장</string>
<string name="notifications">알림</string>
<string name="read_from_start">처음부터 읽기</string>
<string name="light_indicator">LED 표시</string>
<string name="vibration">진동</string>
<string name="rename">이름 바꾸기</string>
<string name="category_delete_confirm">즐겨찾기에서 \"%s\" 카테고리를 제거하시겠습니까\?
\n포함된 모든 만화가 지워집니다.</string>
<string name="remove_category">지우기</string>
<string name="text_search_holder_secondary">쿼리를 재구성하십시오.</string>
<string name="text_history_holder_secondary">사이드 메뉴에서 만화를 탐색해보세요.</string>
<string name="text_shelf_holder_primary">만화가 여기에 표시됩니다</string>
<string name="text_shelf_holder_secondary">«탐색» 섹션에서 만화를 탐색해보세요</string>
<string name="pages_animation">페이지 전환 효과</string>
<string name="cannot_find_available_storage">사용 가능한 저장소 없음</string>
<string name="done">완료</string>
<string name="favourites_category_empty">빈 카테고리</string>
<string name="updates">업데이트</string>
<string name="new_version_s">새 버전: %s</string>
<string name="waiting_for_network">네트워크 연결을 기다리는 중…</string>
<string name="clear_updates_feed">업데이트 피드 지우기</string>
<string name="close_menu">메뉴 닫기</string>
<string name="open_menu">메뉴 열기</string>
<string name="local_storage">내장 메모리</string>
<string name="favourites">즐겨찾기</string>
<string name="remove">지우기</string>
<string name="settings">설정</string>
<string name="loading_">불러오는 중…</string>
<string name="close">닫기</string>
<string name="try_again">다시 시도</string>
<string name="you_have_not_favourites_yet">즐겨찾기가 비어있음</string>
<string name="filter">필터링</string>
<string name="light">밝게</string>
<string name="dark">어둡게</string>
<string name="pages">페이지</string>
<string name="read">지금 읽기</string>
<string name="by_name">이름 순</string>
<string name="popular">인기 순</string>
<string name="chapter_d_of_d">%2$d화 중 %1$d화</string>
<string name="downloads">다운로드</string>
<string name="by_rating">평점 순</string>
<string name="save_page">페이지 저장</string>
<string name="page_saved">저장됨</string>
<string name="share_image">이미지 공유하기</string>
<string name="text_file_not_supported">ZIP 혹은 CBZ 파일을 선택하세요.</string>
<string name="history_and_cache">기록 및 캐시</string>
<string name="cache">캐시</string>
<string name="delete_manga">만화 제거</string>
<string name="volume_buttons">볼륨 키</string>
<string name="nothing_found">결과 없음</string>
<string name="add_to_favourites">즐겨찾기 추가</string>
<string name="download_complete">다운로드 완료</string>
<string name="add_new_category">새 카테고리</string>
<string name="search_manga">만화를 검색하세요</string>
<string name="manga_downloading_">다운로드 중…</string>
<string name="processing_">처리중…</string>
<string name="updated">최근 업데이트 순</string>
<string name="newest">최근 발간 순</string>
<string name="automatic">시스템 설정</string>
<string name="delete">지우기</string>
<string name="wait_for_loading_finish">잠시만 기다려주세요…</string>
<string name="text_file_sizes">바이트|kB|MB|GB|TB</string>
<string name="clear_pages_cache">페이지 캐시 지우기</string>
<string name="read_mode">읽기 모드</string>
<string name="grid_size">격자 크기</string>
<string name="search_on_s">%s에서 검색</string>
<string name="text_delete_local_manga">장치에서 \"%s\"를 영구적으로 삭제하시겠습니까\?</string>
<string name="switch_pages">페이지 전환</string>
<string name="taps_on_edges">가장자리 탭</string>
<string name="webtoon">웹툰</string>
<string name="clear_search_history">검색 기록 지우기</string>
<string name="reader_settings">읽기 모드</string>
<string name="network_consumption_warning">이 동작은 많은 데이터 사용을</string>
<string name="clear_thumbs_cache">썸네일 캐시 지우기</string>
<string name="dont_ask_again">다시 묻지 않음</string>
<string name="cancelling_">취소 중…</string>
<string name="error">오류</string>
<string name="application_update">업데이트 확인</string>
<string name="show_notification_app_update">업데이트 가능 시 알림 설정</string>
<string name="large_manga_save_confirm">이 만화에는 %s가 있습니다. 모두 저장하시겠습니까\?</string>
<string name="favourites_categories">즐겨찾기 카테고리</string>
<string name="download">다운로드</string>
<string name="notifications_settings">알림 설정</string>
<string name="notification_sound">알림음</string>
<string name="categories_">카테고리…</string>
<string name="text_history_holder_primary">읽은 내용이 여기에 표시됩니다</string>
<string name="not_available">사용할 수 없음</string>
<string name="all_favourites">모든 즐겨찾기</string>
<string name="read_later">나중에 읽기</string>
<string name="search_results">검색 결과</string>
<string name="size_s">크기: %s</string>
</resources>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="new_chapters">
<item quantity="one">%1$d nowy rozdział</item>
<item quantity="few">%1$d nowe rozdziały</item>
<item quantity="many">%1$d nowych rozdziałów</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">%1$d minutę temu</item>
<item quantity="few">%1$d minuty temu</item>
<item quantity="many">%1$d minut temu</item>
</plurals>
<plurals name="pages">
<item quantity="one">Łącznie %1$d strona</item>
<item quantity="few">Łącznie %1$d strony</item>
<item quantity="many">Łącznie %1$d stron</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">%1$d godzinę temu</item>
<item quantity="few">%1$d godziny temu</item>
<item quantity="many">%1$d godzin temu</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">%1$d dzień temu</item>
<item quantity="few">%1$d dni temu</item>
<item quantity="many">%1$d dni temu</item>
</plurals>
<plurals name="items">
<item quantity="one">%1$d przedmiot</item>
<item quantity="few">%1$d przedmioty</item>
<item quantity="many">%1$d przedmiotów</item>
</plurals>
<plurals name="chapters_from_x">
<item quantity="one">%1$d rozdział z %2$d</item>
<item quantity="few">%1$d rozdziały z %2$d</item>
<item quantity="many">%1$d rozdziałów z %2$d</item>
</plurals>
<plurals name="chapters">
<item quantity="one">%1$d rozdział</item>
<item quantity="few">%1$d rozdziały</item>
<item quantity="many">%1$d rozdziałów</item>
</plurals>
</resources>

View File

@@ -0,0 +1,397 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="favourites">Ulubione</string>
<string name="history">Historia</string>
<string name="error_occurred">Napotkano błąd</string>
<string name="details">Szczegółowy</string>
<string name="chapters">Rozdziały</string>
<string name="list">Lista</string>
<string name="detailed_list">Lista szczegółowa</string>
<string name="grid">Siatka</string>
<string name="list_mode">Tryb listy</string>
<string name="settings">Ustawienia</string>
<string name="loading_">Ładowanie…</string>
<string name="chapter_d_of_d">Rozdział %1$d z %2$d</string>
<string name="close">Zamknij</string>
<string name="clear_history">Wyczyść historię</string>
<string name="add">Dodaj</string>
<string name="save">Zapisz</string>
<string name="share">Udostępnij</string>
<string name="search">Szukaj</string>
<string name="search_manga">Szukaj mang</string>
<string name="manga_downloading_">Pobieranie…</string>
<string name="download_complete">Pobrano</string>
<string name="downloads">Pobrane</string>
<string name="by_name">Nazwa</string>
<string name="popular">Popularność</string>
<string name="newest">Najnowsze</string>
<string name="by_rating">Ocena</string>
<string name="filter">Filtry</string>
<string name="light">Jasny</string>
<string name="dark">Ciemny</string>
<string name="pages">Strony</string>
<string name="clear">Wyczyść</string>
<string name="remove">Usuń</string>
<string name="share_image">Udostępnij zdjęcie</string>
<string name="delete">Usuń</string>
<string name="no_description">Brak opisu</string>
<string name="read_mode">Tryb czytania</string>
<string name="network_error">Błąd sieci</string>
<string name="computing_">Obliczanie…</string>
<string name="try_again">Spróbuj ponownie</string>
<string name="nothing_found">Nic nie znaleziono</string>
<string name="history_is_empty">Brak historii</string>
<string name="read">Czytaj</string>
<string name="you_have_not_favourites_yet">Brak ulubionych</string>
<string name="add_to_favourites">Dodaj do ulubionych</string>
<string name="add_new_category">Nowa kategoria</string>
<string name="create_shortcut">Stwórz skrót</string>
<string name="share_s">Udostępnij %s</string>
<string name="processing_">Przetwarzanie…</string>
<string name="updated">Zaktualizowane</string>
<string name="_s_removed_from_history">„%s” usunięte z historii</string>
<string name="save_page">Zapisz stronę</string>
<string name="page_saved">Zapisano</string>
<string name="vibration">Wibracje</string>
<string name="manga_shelf">Biblioteka</string>
<string name="recent_manga">Ostatnie</string>
<string name="black_dark_theme">Tryb czarny</string>
<string name="preparing_">Przygotowywanie…</string>
<string name="file_not_found">Plik nieznaleziony</string>
<string name="yesterday">Wczoraj</string>
<string name="long_ago">Dawno temu</string>
<string name="group">Grupa</string>
<string name="today">Dzisiaj</string>
<string name="sign_in">Zaloguj</string>
<string name="next">Dalej</string>
<string name="confirm">Potwierdź</string>
<string name="welcome">Witaj</string>
<string name="state_finished">Skończone</string>
<string name="state_ongoing">W trakcie</string>
<string name="screenshots_allow">Zezwól</string>
<string name="suggestions">Proponowane</string>
<string name="suggestions_enable">Włącz propozycje</string>
<string name="enabled">Włączone</string>
<string name="disabled">Wyłączone</string>
<string name="never">Nigdy</string>
<string name="always">Zawsze</string>
<string name="search_chapters">Znajdź rozdział</string>
<string name="percent_string_pattern">%1$s%%</string>
<string name="appearance">Wygląd</string>
<string name="hide">Schowaj</string>
<string name="sync">Synchronizacja</string>
<string name="sync_title">Synchronizuj swoje dane</string>
<string name="name">Nazwa</string>
<string name="edit">Edytuj</string>
<string name="logout">Wyloguj</string>
<string name="undo">Cofnij</string>
<string name="send">Wyślij</string>
<string name="status_planned">Planowane</string>
<string name="status_reading">Czytane</string>
<string name="status_re_reading">Czytane ponownie</string>
<string name="status_completed">Skończone</string>
<string name="show_all">Pokaż wszystkie</string>
<string name="select_range">Wybierz zakres</string>
<string name="clear_all_history">Wyczyść całą historię</string>
<string name="last_2_hours">Ostatnie 2 godziny</string>
<string name="history_cleared">Historia wyczyszczona</string>
<string name="manage">Zarządzaj</string>
<string name="random">Losowe</string>
<string name="empty">Puste</string>
<string name="changelog">Lista zmian</string>
<string name="explore">Przeglądaj</string>
<string name="available">Dostępne</string>
<string name="options">Ustawienia</string>
<string name="source_disabled">Źródło wyłączone</string>
<string name="compact">Kompaktowy</string>
<string name="server_error">Błąd po stronie serwera (%1$d). Sprónuj ponownie później</string>
<string name="network_unavailable">Sieć niedostępna</string>
<string name="different_languages">Inne języki</string>
<string name="discard">Odrzuć</string>
<string name="brightness">Jasność</string>
<string name="contrast">Kontrast</string>
<string name="color_correction">Korekcja kolorów</string>
<string name="seconds_pattern">%ss</string>
<string name="off_short">Wyłącz</string>
<string name="automatic_scroll">Automatyczne przewijanie</string>
<string name="no_chapters">Brak rozdziałów</string>
<string name="incognito_mode">Tryb incognito</string>
<string name="downloading_manga">Pobieranie mangi</string>
<string name="removed_from_favourites">Usunięto z ulubionych</string>
<string name="enter_email_text">Wprowadź swój email aby kontynuować</string>
<string name="storage_usage">Wykorzystana pamięć</string>
<string name="saved_manga">Zapisane mangi</string>
<string name="no_bookmarks_yet">Brak zakładek</string>
<string name="no_bookmarks_summary">Możesz tworzyć zakładki w trakcie czytania mangi</string>
<string name="bookmarks_removed">Zakładki usunięte</string>
<string name="appwidget_recent_description">Twoje ostatnio czytane mangi</string>
<string name="disable_all">Wyłącz wszystkie</string>
<string name="disable_battery_optimization">Wyłącz optymalizację baterii</string>
<string name="detect_reader_mode">Autowykrywanie trybu czytania</string>
<string name="removed_from_history">Usunięte z historii</string>
<string name="bookmark_added">Dodano zakładkę</string>
<string name="bookmark_removed">Usunięto zakładkę</string>
<string name="bookmarks">Zakładki</string>
<string name="bookmark_remove">Usuń zakładkę</string>
<string name="bookmark_add">Dodaj zakładkę</string>
<string name="empty_favourite_categories">Brak ulubionych kategorii</string>
<string name="edit_category">Edytuj kategorię</string>
<string name="notifications_enable">Włącz powiadomienia</string>
<string name="back">Wróć</string>
<string name="account_already_exists">Konto już istnieje</string>
<string name="canceled">Anulowano</string>
<string name="download_slowdown">Zwolnienie pobierania</string>
<string name="chapters_empty">Brak rozdziałów w tej mandze</string>
<string name="various_languages">Różne języki</string>
<string name="only_using_wifi">Tylko na Wi-Fi</string>
<string name="screenshots_block_all">Zawsze blokuj</string>
<string name="date_format">Format daty</string>
<string name="genres">Gatunki</string>
<string name="find_genre">Znajdź gatunek</string>
<string name="read_more">Czytaj więcej</string>
<string name="other">Inne</string>
<string name="captcha_solve">Rozwiąż</string>
<string name="captcha_required">Wymagane CAPTCHA</string>
<string name="silent">Cichy</string>
<string name="tap_to_try_again">Dotknij aby spróbować ponownie</string>
<string name="just_now">Teraz</string>
<string name="data_restored">Przywrócone</string>
<string name="zoom_mode_fit_width">Dopasuj do szerokości</string>
<string name="zoom_mode_fit_height">Dopasuj do wysokości</string>
<string name="zoom_mode_fit_center">Dopasuj do środka</string>
<string name="create_category">Nowa kategoria</string>
<string name="no_update_available">Brak nowych aktualizacji</string>
<string name="check_for_updates">Sprawdź dostępność aktualizacji</string>
<string name="checking_for_updates">Sprawdzanie aktualizacji…</string>
<string name="app_version">Wersja %s</string>
<string name="about">O aplikacji</string>
<string name="categories_">Kategorie…</string>
<string name="rename">Zmień nazwę</string>
<string name="remove_category">Usuń</string>
<string name="text_empty_holder_primary">Jest tu dosyć pusto…</string>
<string name="favourites_categories">Ulubione kategorie</string>
<string name="light_indicator">Powiadomienie LED</string>
<string name="new_chapters">Nowe rozdziały</string>
<string name="close_menu">Zamknij kartę</string>
<string name="open_menu">Otwórz kartę</string>
<string name="local_storage">Pamięć wewnętrzna</string>
<string name="text_shelf_holder_primary">Tutaj będą wyświetlane Twoje mangi</string>
<string name="text_shelf_holder_secondary">Znajdź materiały do czytania w zakładce „Przeglądaj”</string>
<string name="text_feed_holder">W tym miejscu pojawią się powiadomienia o nowych rozdziałach z mang które czytasz</string>
<string name="pages_cache">Strony w pamięci podręcznej</string>
<string name="pages_animation">Animacja przewracania strony</string>
<string name="other_cache">Inne rzeczy w pamięci podręcznej</string>
<string name="open_in_browser">Otwórz w przeglądarce</string>
<string name="show_pages_numbers">Numerowane strony</string>
<string name="notifications">Powiadomienia</string>
<string name="notification_sound">Dźwięk powiadomień</string>
<string name="notifications_settings">Ustawienia powiadomień</string>
<string name="remote_sources">Zewnętrzne źródła</string>
<string name="theme">Motyw</string>
<string name="automatic">Systemowy</string>
<string name="history_and_cache">Historia i pamięć podręczna</string>
<string name="clear_pages_cache">Wyczyść pamięć podręczną stron</string>
<string name="cache">Pamięć podręczna</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="grid_size">Wielkość siatki</string>
<string name="search_on_s">Szukaj na %s</string>
<string name="delete_manga">Usuń mangę</string>
<string name="_continue">Dalej</string>
<string name="dont_ask_again">Nie pytaj ponownie</string>
<string name="cancelling_">Anulowanie…</string>
<string name="error">Błąd</string>
<string name="search_history_cleared">Wyczyszczone</string>
<string name="internal_storage">Pamięć wewnętrzna</string>
<string name="external_storage">Pamięć zewnętrzna</string>
<string name="domain">Domena</string>
<string name="application_update">Sprawdź dostępność nowej wersji aplikacji</string>
<string name="app_update_available">Nowa wersja aplikacji jest dostępna</string>
<string name="show_notification_app_update">Pokaż powiadomienie gdy nowa wersja jest dostępna</string>
<string name="large_manga_save_confirm">Ta manga ma %s. Zapisać wszystko?</string>
<string name="save_manga">Zapisz</string>
<string name="download">Pobierz</string>
<string name="read_from_start">Czytaj od początku</string>
<string name="category_delete_confirm">Usunąć kategorię „%s” z Twoich ulubionych? Wszystkie mangi w niej będą z niej usunięte.</string>
<string name="text_categories_holder">Możesz użyć kategorii do organizowania swoich ulubionych. Kliknij «+» aby stworzyć kategorię</string>
<string name="text_local_holder_primary">Najpierw coś zapisz</string>
<string name="not_available">Niedostępne</string>
<string name="done">Zapisz</string>
<string name="all_favourites">Wszystkie ulubione</string>
<string name="favourites_category_empty">Pusta kategoria</string>
<string name="read_later">Czytaj później</string>
<string name="updates">Aktualizacje</string>
<string name="new_version_s">Nowa wersja: %s</string>
<string name="size_s">Wielkość: %s</string>
<string name="waiting_for_network">Czekanie na sieć…</string>
<string name="rotate_screen">Obróć ekran</string>
<string name="update">Odśwież</string>
<string name="track_sources">Szukaj aktualizacji</string>
<string name="dont_check">Nie sprawdzaj</string>
<string name="enter_password">Wprowadź hasło</string>
<string name="wrong_password">Złe hasło</string>
<string name="protect_application">Chroń aplikację</string>
<string name="protect_application_summary">Pytaj o hasło przy starcie Kotatsu</string>
<string name="repeat_password">Wprowadź ponownie hasło</string>
<string name="black_dark_theme_summary">Zużywa mniej prądu na ekranach AMOLED</string>
<string name="backup_restore">Kopia zapasowa i przywracanie</string>
<string name="create_backup">Utwórz kopię zapasową danych</string>
<string name="restore_backup">Przywróć z kopii zapasowej</string>
<string name="nsfw">18+</string>
<string name="enabled_d_of_d">%1$d na %2$d włączone</string>
<string name="enter_category_name">Wprowadź nazwę kategorii</string>
<string name="standard">Standardowy</string>
<string name="webtoon">Webtoon</string>
<string name="reader_settings">Ustawienia czytnika</string>
<string name="switch_pages">Zmiana strony</string>
<string name="volume_buttons">Przyciski głośności</string>
<string name="warning">Uwaga</string>
<string name="taps_on_edges">Dotknięcie krawędzi</string>
<string name="updates_feed_cleared">Wyczyszczone</string>
<string name="scale_mode">Tryb skalowania</string>
<string name="clear_cookies">Wyczyść ciasteczka</string>
<string name="cookies_cleared">Wszystkie ciasteczka wyczyszczone</string>
<string name="search_only_on_s">Szukaj tylko na %s</string>
<string name="about_app_translation_summary">Przetłumacz tą aplikację</string>
<string name="about_app_translation">Tłumaczenie</string>
<string name="error_empty_name">Musisz wpisać nazwę</string>
<string name="available_sources">Dostępne źródła</string>
<string name="dynamic_theme">Motyw dynamiczny</string>
<string name="gestures_only">Tylko gesty</string>
<string name="cannot_find_available_storage">Brak dostępnej pamięci</string>
<string name="other_storage">Inny</string>
<string name="search_results">Wyniki wyszukiwania</string>
<string name="related">Szukaj podobnych</string>
<string name="data_restored_success">Wszystkie dane zostały przywrócone</string>
<string name="data_restored_with_errors">Dane zostały przywrócone, ale z błędami</string>
<string name="reverse">Od tyłu</string>
<string name="text_downloads_holder">Brak aktywnych pobrań</string>
<string name="system_default">Domyślny</string>
<string name="screenshots_policy">Polityka zrzutów ekranu</string>
<string name="suggestions_excluded_genres">Wyklucz gatunki</string>
<string name="suggestions_excluded_genres_summary">Określ gatunki, których nie chcesz widzieć w sugestiach</string>
<string name="logged_in_as">Zalogowano jako %s</string>
<string name="onboard_text">Wybierz języki, w których chcesz czytać mangi. Możesz zmienić to później w ustawieniach.</string>
<string name="report">Zgłoś</string>
<string name="data_deletion">Usuwanie danych</string>
<string name="invalid_domain_message">Nieważna domena</string>
<string name="reorder">Zmień kolejność</string>
<string name="exit_confirmation">Potwierdzenie wyjścia</string>
<string name="memory_usage_pattern">%s - %s</string>
<string name="reader_info_pattern">Rozdz. %1$d/%2$d Str. %3$d/%4$d</string>
<string name="network_unavailable_hint">Włącz Wi-Fi lub sieć komórkową, aby czytać mangę online</string>
<string name="_import">Importuj</string>
<string name="text_file_not_supported">Wybierz plik ZIP lub CBZ.</string>
<string name="restart">Uruchom ponownie</string>
<string name="clear_search_history">Wyczyść historię wyszukiwania</string>
<string name="operation_not_supported">Ta operacja nie jest obsługiwana</string>
<string name="wait_for_loading_finish">Poczekaj na zakończenie ładowania…</string>
<string name="sort_order">Tryb sortowania</string>
<string name="content">Treści</string>
<string name="filter_load_error">Nie można załadować listy gatunków</string>
<string name="status_on_hold">Wstrzymane</string>
<string name="status_dropped">Porzucone</string>
<string name="use_fingerprint">Użyj odcisku palca, jeśli jest dostępny</string>
<string name="appwidget_shelf_description">Mangi z Twoich ulubionych</string>
<string name="show_reading_indicators">Pokaż wskaźniki postępu czytania</string>
<string name="show_reading_indicators_summary">Pokaż procent przeczytania w historii i ulubionych</string>
<string name="exclude_nsfw_from_history_summary">Manga oznaczona jako NSFW nigdy nie zostanie dodana do historii, a Twoje postępy nie zostaną zapisane</string>
<string name="dns_over_https">DNS przez HTTPS</string>
<string name="default_mode">Tryb domyślny</string>
<string name="text_clear_history_prompt">Trwale wyczyścić całą historię czytania?</string>
<string name="_s_deleted_from_local_storage">„%s” usunięte z pamięci lokalnej</string>
<string name="clear_updates_feed">Wyczyść tablicę aktualizacji</string>
<string name="feed">Tablica</string>
<string name="text_delete_local_manga">Usunąć trwale „%s” z urządzenia?</string>
<string name="network_consumption_warning">Może to spowodować przeniesienie dużej ilości danych</string>
<string name="clear_thumbs_cache">Wyczyść pamięć podręczną miniatur</string>
<string name="text_search_holder_secondary">Spróbuj przeformułować zapytanie.</string>
<string name="text_history_holder_primary">To co czytasz będzie wyświetlane tutaj</string>
<string name="text_history_holder_secondary">Znajdź to, co warto przeczytać, w menu bocznym.</string>
<string name="text_local_holder_secondary">Zapisz ze źródeł online lub zaimportuj pliki.</string>
<string name="manga_save_location">Folder pobranych</string>
<string name="feed_will_update_soon">Aktualizacja tablicy rozpocznie się wkrótce</string>
<string name="passwords_mismatch">Niezgodne hasła</string>
<string name="update_check_failed">Nie można wyszukać aktualizacji</string>
<string name="right_to_left">Od prawej do lewej</string>
<string name="zoom_mode_keep_start">Trzymaj na starcie</string>
<string name="report_github">Utwórz problem na GitHubie</string>
<string name="backup_information">Możesz utworzyć kopię zapasową swojej historii i ulubionych oraz przywrócić ją</string>
<string name="reader_mode_hint">Wybrana konfiguracja zostanie zapamiętana dla tej mangi</string>
<string name="chapters_checking_progress">Sprawdzanie nowych rozdziałów: %1$d z %2$d</string>
<string name="clear_feed">Wyczyść tablicę</string>
<string name="text_clear_updates_feed_prompt">Wyczyścić trwale całą historię aktualizacji?</string>
<string name="check_for_new_chapters">Szukanie nowych rozdziałów</string>
<string name="auth_required">Zaloguj się, aby wyświetlić tę zawartość</string>
<string name="default_s">Domyślnie: %s</string>
<string name="_and_x_more">…i jeszcze %1$d</string>
<string name="protect_application_subtitle">Wprowadź hasło, aby uruchomić aplikację</string>
<string name="password_length_hint">Hasło musi mieć co najmniej 4 znaki</string>
<string name="text_clear_search_history_prompt">Trwale usunąć wszystkie ostatnie zapytania wyszukiwania?</string>
<string name="backup_saved">Zapisano kopię zapasową</string>
<string name="tracker_warning">Systemy niektórych urządzeń inaczej się zachowują. Może to zakłócać wykonywanie zadań w tle.</string>
<string name="queued">W kolejce</string>
<string name="chapter_is_missing_text">Pobierz lub przeczytaj ten brakujący rozdział online.</string>
<string name="chapter_is_missing">Brak rozdziału</string>
<string name="about_feedback">Komentarz</string>
<string name="about_feedback_4pda">Temat na 4PDA</string>
<string name="auth_complete">Uprawniony</string>
<string name="auth_not_supported_by">Logowanie na %s nie jest obsługiwane</string>
<string name="text_clear_cookies_prompt">Zostaniesz wylogowany ze wszystkich źródeł</string>
<string name="exclude_nsfw_from_history">Wyklucz mangi NSFW z historii</string>
<string name="enabled_sources">Wykorzystane źródła</string>
<string name="dynamic_theme_summary">Stosuje motyw utworzony na podstawie schematu kolorów Twojej tapety</string>
<string name="importing_progress">Importowanie mangi: %1$d z %2$d</string>
<string name="screenshots_block_nsfw">Zablokuj na NSFW</string>
<string name="suggestions_summary">Proponuj mangi na podstawie Twoich preferencji</string>
<string name="suggestions_info">Wszystkie dane są analizowane lokalnie na tym urządzeniu. Twoje dane osobowe nie są przekazywane do żadnych usług</string>
<string name="text_suggestion_holder">Zacznij czytać mangę, a otrzymasz spersonalizowane sugestie</string>
<string name="exclude_nsfw_from_suggestions">Nie proponuj mang NSFW</string>
<string name="reset_filter">Zresetuj filtr</string>
<string name="preload_pages">Ładuj wstępnie strony</string>
<string name="suggestions_updating">Aktualizowanie sugestii</string>
<string name="text_delete_local_manga_batch">Trwale usunąć wybrane elementy z urządzenia?</string>
<string name="removal_completed">Usuwanie zakończone</string>
<string name="batch_manga_save_confirm">Pobrać wszystkie wybrane mangi i ich rozdziały? Może to zużyć dużo danych i pamięci.</string>
<string name="parallel_downloads">Pobieranie równoległe</string>
<string name="download_slowdown_summary">Pomaga uniknąć blokowania Twojego adresu IP</string>
<string name="local_manga_processing">Przetwarzanie zapisanej mangi</string>
<string name="chapters_will_removed_background">Rozdziały zostaną usunięte w tle. Może to zająć trochę czasu</string>
<string name="email_enter_hint">Wpisz swój adres e-mail, aby kontynuować</string>
<string name="new_sources_text">Dostępne są nowe źródła mang</string>
<string name="check_new_chapters_title">Sprawdzaj dostępność nowych rozdziałów i informuj o nich</string>
<string name="show_notification_new_chapters_on">Będziesz otrzymywać powiadomienia o aktualizacjach mang, które czytasz</string>
<string name="show_notification_new_chapters_off">Nie będziesz otrzymywać powiadomień, ale nowe rozdziały będą podświetlane na listach</string>
<string name="tracking">Śledzenie</string>
<string name="detect_reader_mode_summary">Automatycznie wykryj, czy manga to webtoon</string>
<string name="disable_battery_optimization_summary">Pomaga w sprawdzaniu aktualizacji w tle</string>
<string name="crash_text">Coś poszło nie tak. Zgłoś błąd programistom, aby pomóc nam go naprawić.</string>
<string name="clear_cookies_summary">Może pomóc w przypadku niektórych problemów. Wszystkie autoryzacje zostaną unieważnione</string>
<string name="no_manga_sources">Brak źródeł mang</string>
<string name="no_manga_sources_text">Włącz źródła mang do czytania mang online</string>
<string name="categories_delete_confirm">Czy na pewno chcesz usunąć wybrane ulubione kategorie? Wszystkie w nich mangi zostaną usunięte i nie będzie można tego cofnąć.</string>
<string name="confirm_exit">Naciśnij ponownie Wstecz, aby wyjść</string>
<string name="exit_confirmation_summary">Naciśnij dwukrotnie przycisk Wstecz, aby wyjść z aplikacji</string>
<string name="removed_from_s">Usunięto z „%s”</string>
<string name="not_found_404">Treść nie została znaleziona lub została usunięta</string>
<string name="app_update_available_s">Dostępna aktualizacja aplikacji: %s</string>
<string name="reader_info_bar">Pokaż pasek informacji w czytniku</string>
<string name="comics_archive">Archiwum komiksów</string>
<string name="folder_with_images">Folder z obrazami</string>
<string name="importing_manga">Importowanie mangi</string>
<string name="import_completed">Importowanie zakończone</string>
<string name="import_completed_hint">Możesz usunąć oryginalny plik z pamięci, aby zaoszczędzić miejsce</string>
<string name="import_will_start_soon">Import rozpocznie się wkrótce</string>
<string name="color_correction_hint">Wybrane ustawienia kolorów zostaną zapamiętane dla tej mangi</string>
<string name="history_shortcuts">Pokaż ostatnie skróty do mang</string>
<string name="history_shortcuts_summary">Pokaż ostatnie mangi po długim naciśnięciu ikony aplikacji</string>
<string name="reader_control_ltr_summary">Stuknięcie w prawą krawędź lub naciśnięcie prawego klawisza zawsze powoduje przejście do następnej strony</string>
<string name="reader_control_ltr">Ergonomiczne sterowanie czytnikiem</string>
<string name="text_unsaved_changes_prompt">Zapisać czy odrzucić niezapisane zmiany?</string>
<string name="error_no_space_left">Brak miejsca w urządzeniu</string>
<string name="reader_slider">Pokaż suwak przełączania stron</string>
<string name="webtoon_zoom">Powiększanie webtoon</string>
<string name="webtoon_zoom_summary">Zezwalaj na gest powiększania/pomniejszania w trybie webtoon (beta)</string>
<string name="clear_new_chapters_counters">Wyczyść też informacje o nowych rozdziałach</string>
<string name="reset">Resetuj</string>
<string name="manga_error_description_pattern">Szczegóły błędu:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Spróbuj &lt;a href=%2$s&gt;otworzyć mangę w przeglądarce internetowej&lt;/a&gt; aby upewnić się, że jest dostępna w źródle&lt;br&gt;2. Jeśli jest dostępna, wyślij raport o błędzie do programistów.</string>
</resources>

View File

@@ -1,44 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="pages">
<item quantity="one">Всего %1$d страница</item>
<item quantity="few">Всего %1$d страницы</item>
<item quantity="many">Всего %1$d страниц</item>
</plurals>
<plurals name="items">
<item quantity="one">%1$d элемент</item>
<item quantity="few">%1$d элемента</item>
<item quantity="many">%1$d элементов</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d новая глава</item>
<item quantity="few">%1$d новых главы</item>
<item quantity="many">%1$d новых глав</item>
</plurals>
<plurals name="chapters">
<item quantity="one">%1$d глава</item>
<item quantity="few">%1$d главы</item>
<item quantity="many">%1$d глав</item>
</plurals>
<plurals name="chapters_from_x">
<item quantity="one">%1$d глава из %2$d</item>
<item quantity="few">%1$d главы из %2$d</item>
<item quantity="many">%1$d глав из %2$d</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">%1$d минуту назад</item>
<item quantity="few">%1$d минуты назад</item>
<item quantity="many">%1$d минут назад</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">%1$d час назад</item>
<item quantity="few">%1$d часа назад</item>
<item quantity="many">%1$d часов назад</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">%1$d день назад</item>
<item quantity="few">%1$d дня назад</item>
<item quantity="many">%1$d дней назад</item>
</plurals>
<plurals name="pages">
<item quantity="one">Всего %1$d страница</item>
<item quantity="few">Всего %1$d страницы</item>
<item quantity="many">Всего %1$d страниц</item>
</plurals>
<plurals name="items">
<item quantity="one">%1$d элемент</item>
<item quantity="few">%1$d элемента</item>
<item quantity="many">%1$d элементов</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d новая глава</item>
<item quantity="few">%1$d новые главы</item>
<item quantity="many">%1$d новых глав</item>
<item quantity="other">%1$d новых глав</item>
</plurals>
<plurals name="chapters">
<item quantity="one">%1$d глава</item>
<item quantity="few">%1$d главы</item>
<item quantity="many">%1$d глав</item>
</plurals>
<plurals name="chapters_from_x">
<item quantity="one">%1$d глава из %2$d</item>
<item quantity="few">%1$d главы из %2$d</item>
<item quantity="many">%1$d глав из %2$d</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">%1$d минуту назад</item>
<item quantity="few">%1$d минуты назад</item>
<item quantity="many">%1$d минут назад</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">%1$d час назад</item>
<item quantity="few">%1$d часа назад</item>
<item quantity="many">%1$d часов назад</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">%1$d день назад</item>
<item quantity="few">%1$d дня назад</item>
<item quantity="many">%1$d дней назад</item>
</plurals>
</resources>

View File

@@ -6,7 +6,7 @@
<string name="favourites">Избранное</string>
<string name="history">История</string>
<string name="error_occurred">Произошла ошибка</string>
<string name="network_error">Не удалось подключиться к интернету</string>
<string name="network_error">Ошибка сети</string>
<string name="details">Подробности</string>
<string name="chapters">Главы</string>
<string name="list">Список</string>
@@ -396,4 +396,5 @@
<string name="clear_new_chapters_counters">Также очистить информацию о новых главах</string>
<string name="server_error">Внутренняя ошибка сервера (%1$d). Повторите попытку позже</string>
<string name="compact">Компактно</string>
<string name="source_disabled">Источник отключен</string>
</resources>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="pages">
<item quantity="one">Тотално %1$d странa</item>
<item quantity="few">Тотално %1$d странице</item>
<item quantity="other">Тотално %1$d странице</item>
</plurals>
<plurals name="items">
<item quantity="one">%1$d ставке</item>
<item quantity="few">%1$d ставки</item>
<item quantity="other">%1$d ставка</item>
</plurals>
<plurals name="chapters_from_x">
<item quantity="one">%1$d поглавља од %2$d</item>
<item quantity="few">%1$d поглавља од %2$d</item>
<item quantity="other">%1$d поглавља од %2$d</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">пре %1$d минута</item>
<item quantity="few">пре %1$d минута</item>
<item quantity="other">пре %1$d минута</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">пре %1$d сата</item>
<item quantity="few">пре %1$d сата</item>
<item quantity="other">пре %1$d сата</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">пре %1$d дана</item>
<item quantity="few">пре %1$d дана</item>
<item quantity="other">пре %1$d дана</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d нова поглавља</item>
<item quantity="few">%1$d нових поглавља</item>
<item quantity="other">%1$d нових поглавља</item>
</plurals>
<plurals name="chapters">
<item quantity="one">%1$d поглављe</item>
<item quantity="few">%1$d поглавља</item>
<item quantity="other">%1$d поглавља</item>
</plurals>
</resources>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="local_storage">Локално складиште</string>
<string name="close_menu">Затвори мени</string>
<string name="error_occurred">Грешка се појавила</string>
<string name="open_menu">Отвори мени</string>
<string name="favourites">Фаворити</string>
<string name="history">Историја</string>
<string name="network_error">Неуспешно повезивање са интернетом</string>
<string name="details">Детаљи</string>
<string name="chapters">Поглавља</string>
<string name="list">Листа</string>
<string name="detailed_list">Детаљна листа</string>
<string name="grid">Табла</string>
</resources>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="network_error">İnternete bağlı olduğunuzdan emin olunuz</string>
<string name="network_error">Ağ hatası</string>
<string name="close_menu">Menüyü kapat</string>
<string name="open_menu">Menüyü aç</string>
<string name="local_storage">Dahili Depolama</string>
@@ -395,4 +395,5 @@
<string name="saved_manga">Kaydedilen mangalar</string>
<string name="history_shortcuts_summary">Uygulama simgesine uzun basarak son mangaları kullanılabilir hale getirin</string>
<string name="reader_control_ltr_summary">Sağ kenara dokunulduğunda veya sağ tuşa basıldığında her zaman bir sonraki sayfaya geçilir</string>
<string name="source_disabled">Kaynak devre dışı</string>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="com_samsung_android_icon_container_has_icon_container">false</bool>
</resources>

View File

@@ -15,4 +15,10 @@
<plurals name="days_ago">
<item quantity="other">%1$d ngày trước</item>
</plurals>
<plurals name="chapters_from_x">
<item quantity="other">%1$d chương từ %2$d</item>
</plurals>
<plurals name="pages">
<item quantity="other">Tổng %1$d trang</item>
</plurals>
</resources>

View File

@@ -5,7 +5,7 @@
<string name="favourites">喜欢</string>
<string name="history">历史</string>
<string name="error_occurred">发生了一个错误</string>
<string name="network_error">未能连接到互联</string>
<string name="network_error">络错误</string>
<string name="chapters">章节</string>
<string name="list">列表</string>
<string name="data_restored_with_errors">数据被恢复了,但有错误</string>
@@ -31,7 +31,7 @@
<string name="clear_cookies">清除cookies</string>
<string name="chapters_checking_progress">检查新的章节: %1$d/%2$d</string>
<string name="error_empty_name">你必须输入一个名称</string>
<string name="new_sources_text">新的漫画</string>
<string name="new_sources_text">新的漫画源可用</string>
<string name="suggestions_summary">根据你的喜好推荐漫画</string>
<string name="suggestions_info">所有的数据都在这个设备上进行本地分析. 您的个人数据不会被转移到任何服务机构</string>
<string name="never">从不</string>
@@ -48,7 +48,7 @@
<string name="detailed_list">详细列表</string>
<string name="grid">网格</string>
<string name="list_mode">列表模式</string>
<string name="remote_sources">远程</string>
<string name="remote_sources">远程源</string>
<string name="loading_">加载中…</string>
<string name="computing_">计算中…</string>
<string name="chapter_d_of_d">%1$d/%2$d章节</string>
@@ -230,7 +230,7 @@
<string name="about_feedback_4pda">关于4PDA主题</string>
<string name="auth_complete">授权</string>
<string name="auth_not_supported_by">不支持在%s上登录</string>
<string name="text_clear_cookies_prompt">你将被从所有来源中注销</string>
<string name="text_clear_cookies_prompt">你将退出登录所有来源</string>
<string name="genres">类型</string>
<string name="state_ongoing">连载中</string>
<string name="state_finished">已完结</string>
@@ -395,4 +395,5 @@
<string name="clear_new_chapters_counters">同样清除新章节信息</string>
<string name="server_error">服务器端错误 (%1$d)。请稍后再试</string>
<string name="compact">紧凑</string>
<string name="source_disabled">已禁用图源</string>
</resources>

View File

@@ -3,4 +3,5 @@
<bool name="is_tablet">false</bool>
<bool name="light_status_bar">true</bool>
<bool name="light_navigation_bar">false</bool>
</resources>
<bool name="com_samsung_android_icon_container_has_icon_container">true</bool>
</resources>

View File

@@ -7,7 +7,7 @@
<string name="favourites">Favourites</string>
<string name="history">History</string>
<string name="error_occurred">An error occurred</string>
<string name="network_error">Could not connect to the Internet</string>
<string name="network_error">Network error</string>
<string name="details">Details</string>
<string name="chapters">Chapters</string>
<string name="list">List</string>
@@ -399,4 +399,5 @@
<string name="clear_new_chapters_counters">Also clear information about new chapters</string>
<string name="compact">Compact</string>
<string name="mal" translatable="false">MyAnimeList</string>
<string name="source_disabled">Source disabled</string>
</resources>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config
xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en" />
<locale android:name="ar" />
<locale android:name="be" />
<locale android:name="bn" />
<locale android:name="de" />
<locale android:name="el" />
<locale android:name="es" />
<locale android:name="fa" />
<locale android:name="fi" />
<locale android:name="fr" />
<locale android:name="in" />
<locale android:name="it" />
<locale android:name="ja" />
<locale android:name="nb-rNO" />
<locale android:name="pl" />
<locale android:name="pt" />
<locale android:name="pt-rBR" />
<locale android:name="ru" />
<locale android:name="si" />
<locale android:name="sr" />
<locale android:name="sv" />
<locale android:name="tr" />
<locale android:name="uk" />
<locale android:name="zh-rCN" />
<locale android:name="zh-rTW" />
</locale-config>