Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
503bff292c | ||
|
|
0aa78c0d7e | ||
|
|
8e1d02f356 | ||
|
|
1e90d5541b | ||
|
|
04c7ca7291 | ||
|
|
8d52cab6d8 | ||
|
|
efa13df106 | ||
|
|
8bc29ac331 | ||
|
|
7991f9ca97 | ||
|
|
eb1eee1681 | ||
|
|
b3f748c000 | ||
|
|
58a9f7b25a | ||
|
|
fc1d704f6f | ||
|
|
c2c3b0f757 | ||
|
|
8d519dd80f | ||
|
|
3b5a9cd2b4 | ||
|
|
95f4d39893 | ||
|
|
f3f269c7fa | ||
|
|
40f262b0ef | ||
|
|
0f68be9663 | ||
|
|
0b8afe9c40 | ||
|
|
754ccc4197 | ||
|
|
ef691b1aed | ||
|
|
1bd916371a | ||
|
|
cd40dab8a4 | ||
|
|
d2ed8a1ace | ||
|
|
024e3c11ee | ||
|
|
23ba302df8 | ||
|
|
34e54e43e0 | ||
|
|
07a8de6225 | ||
|
|
a3df6f799c | ||
|
|
d5722790ef | ||
|
|
8bf540abbe | ||
|
|
5241fa0d13 | ||
|
|
87e0c931a2 | ||
|
|
a51412801a | ||
|
|
a6c188d647 | ||
|
|
831632cb8f | ||
|
|
ad59bf50f4 | ||
|
|
6fe6c05327 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,3 +26,4 @@
|
||||
.cxx
|
||||
/.idea/deviceManager.xml
|
||||
/.kotlin/
|
||||
/.idea/AndroidProjectSystem.xml
|
||||
|
||||
@@ -18,8 +18,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 692
|
||||
versionName = '7.7'
|
||||
versionCode = 699
|
||||
versionName = '7.7.7'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||
ksp {
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
android:allowBackup="true"
|
||||
android:backupAgent="org.koitharu.kotatsu.settings.backup.AppBackupAgent"
|
||||
android:dataExtractionRules="@xml/backup_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:enableOnBackInvokedCallback="@bool/is_predictive_back_enabled"
|
||||
android:fullBackupContent="@xml/backup_content"
|
||||
android:fullBackupOnly="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@@ -209,6 +209,7 @@
|
||||
<activity android:name="org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.sync.ui.SyncAuthActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/sync" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity"
|
||||
@@ -279,6 +280,10 @@
|
||||
<service
|
||||
android:name="org.koitharu.kotatsu.local.ui.LocalIndexUpdateService"
|
||||
android:label="@string/local_manga_processing" />
|
||||
<service
|
||||
android:name="org.koitharu.kotatsu.local.ui.ImportService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:label="@string/importing_manga" />
|
||||
<service
|
||||
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetService"
|
||||
android:label="@string/manga_shelf"
|
||||
|
||||
@@ -49,7 +49,7 @@ fun Manga.toEntity() = MangaEntity(
|
||||
publicUrl = publicUrl,
|
||||
source = source.name,
|
||||
largeCoverUrl = largeCoverUrl,
|
||||
coverUrl = coverUrl,
|
||||
coverUrl = coverUrl.orEmpty(),
|
||||
altTitle = altTitle,
|
||||
rating = rating,
|
||||
isNsfw = isNsfw,
|
||||
|
||||
@@ -14,7 +14,7 @@ data class MangaEntity(
|
||||
@ColumnInfo(name = "url") val url: String,
|
||||
@ColumnInfo(name = "public_url") val publicUrl: String,
|
||||
@ColumnInfo(name = "rating") val rating: Float, // normalized value [0..1] or -1
|
||||
@ColumnInfo(name = "nsfw") val isNsfw: Boolean,
|
||||
@ColumnInfo(name = "nsfw") val isNsfw: Boolean, // TODO change to contentRating
|
||||
@ColumnInfo(name = "cover_url") val coverUrl: String,
|
||||
@ColumnInfo(name = "large_cover_url") val largeCoverUrl: String?,
|
||||
@ColumnInfo(name = "state") val state: String?,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
class CaughtException(cause: Throwable) : RuntimeException("${cause.javaClass.simpleName}(${cause.message})", cause)
|
||||
class CaughtException(
|
||||
override val cause: Throwable
|
||||
) : RuntimeException("${cause.javaClass.simpleName}(${cause.message})", cause)
|
||||
|
||||
@@ -3,5 +3,5 @@ package org.koitharu.kotatsu.core.exceptions
|
||||
import okio.IOException
|
||||
|
||||
class NoDataReceivedException(
|
||||
url: String,
|
||||
val url: String,
|
||||
) : IOException("No data has been received from $url")
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
import okio.IOException
|
||||
|
||||
class WrapperIOException(override val cause: Exception) : IOException(cause)
|
||||
@@ -5,6 +5,7 @@ import android.graphics.BitmapFactory
|
||||
import android.graphics.ImageDecoder
|
||||
import android.os.Build
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
@@ -32,19 +33,21 @@ object BitmapDecoderCompat {
|
||||
}
|
||||
|
||||
@Blocking
|
||||
fun decode(stream: InputStream, type: MediaType?): Bitmap {
|
||||
fun decode(stream: InputStream, type: MediaType?, isMutable: Boolean = false): Bitmap {
|
||||
val format = type?.subtype
|
||||
if (format == FORMAT_AVIF) {
|
||||
return decodeAvif(stream.toByteBuffer())
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
return checkBitmapNotNull(BitmapFactory.decodeStream(stream), format)
|
||||
val opts = BitmapFactory.Options()
|
||||
opts.inMutable = isMutable
|
||||
return checkBitmapNotNull(BitmapFactory.decodeStream(stream, null, opts), format)
|
||||
}
|
||||
val byteBuffer = stream.toByteBuffer()
|
||||
return if (AvifDecoder.isAvifImage(byteBuffer)) {
|
||||
decodeAvif(byteBuffer)
|
||||
} else {
|
||||
ImageDecoder.decodeBitmap(ImageDecoder.createSource(byteBuffer))
|
||||
ImageDecoder.decodeBitmap(ImageDecoder.createSource(byteBuffer), DecoderConfigListener(isMutable))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,4 +77,18 @@ object BitmapDecoderCompat {
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
private class DecoderConfigListener(
|
||||
private val isMutable: Boolean,
|
||||
) : ImageDecoder.OnHeaderDecodedListener {
|
||||
|
||||
override fun onHeaderDecoded(
|
||||
decoder: ImageDecoder,
|
||||
info: ImageDecoder.ImageInfo,
|
||||
source: ImageDecoder.Source
|
||||
) {
|
||||
decoder.isMutableRequired = isMutable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@Parcelize
|
||||
data class ParcelableManga(
|
||||
val manga: Manga,
|
||||
private val withDescription: Boolean = true,
|
||||
) : Parcelable {
|
||||
|
||||
companion object : Parceler<ParcelableManga> {
|
||||
@@ -27,7 +28,7 @@ data class ParcelableManga(
|
||||
ParcelCompat.writeBoolean(parcel, isNsfw)
|
||||
parcel.writeString(coverUrl)
|
||||
parcel.writeString(largeCoverUrl)
|
||||
parcel.writeString(description)
|
||||
parcel.writeString(description.takeIf { withDescription })
|
||||
parcel.writeParcelable(ParcelableMangaTags(tags), flags)
|
||||
parcel.writeSerializable(state)
|
||||
parcel.writeString(author)
|
||||
@@ -52,6 +53,7 @@ data class ParcelableManga(
|
||||
chapters = null,
|
||||
source = MangaSource(parcel.readString()),
|
||||
),
|
||||
withDescription = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class AppProxySelector(
|
||||
if (type == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY
|
||||
}
|
||||
if (address.isNullOrEmpty() || port == 0) {
|
||||
if (address.isNullOrEmpty() || port < 0 || port > 0xFFFF) {
|
||||
throw ProxyConfigException()
|
||||
}
|
||||
cachedProxy?.let {
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
package org.koitharu.kotatsu.core.network
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.Response
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrapperIOException
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders.CONTENT_ENCODING
|
||||
|
||||
class GZipInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val newRequest = chain.request().newBuilder()
|
||||
newRequest.addHeader(CONTENT_ENCODING, "gzip")
|
||||
return try {
|
||||
override fun intercept(chain: Interceptor.Chain): Response = try {
|
||||
val request = chain.request()
|
||||
if (request.body is MultipartBody) {
|
||||
chain.proceed(request)
|
||||
} else {
|
||||
val newRequest = request.newBuilder()
|
||||
newRequest.addHeader(CONTENT_ENCODING, "gzip")
|
||||
chain.proceed(newRequest.build())
|
||||
} catch (e: NullPointerException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
throw WrapperIOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ package org.koitharu.kotatsu.core.parser
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.webkit.WebView
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -17,6 +15,7 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||
import okio.Buffer
|
||||
import org.koitharu.kotatsu.core.image.BitmapDecoderCompat
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||
import org.koitharu.kotatsu.core.prefs.SourceSettings
|
||||
@@ -31,7 +30,6 @@ import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.util.map
|
||||
import org.koitharu.kotatsu.parsers.util.mimeType
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
@@ -80,15 +78,13 @@ class MangaLoaderContextImpl @Inject constructor(
|
||||
|
||||
override fun redrawImageResponse(response: Response, redraw: (image: Bitmap) -> Bitmap): Response {
|
||||
return response.map { body ->
|
||||
val opts = BitmapFactory.Options()
|
||||
opts.inMutable = true
|
||||
BitmapFactory.decodeStream(body.byteStream(), null, opts)?.use { bitmap ->
|
||||
BitmapDecoderCompat.decode(body.byteStream(), body.contentType(), isMutable = true).use { bitmap ->
|
||||
(redraw(BitmapWrapper.create(bitmap)) as BitmapWrapper).use { result ->
|
||||
Buffer().also {
|
||||
result.compressTo(it.outputStream())
|
||||
}.asResponseBody("image/jpeg".toMediaType())
|
||||
}
|
||||
} ?: throw ImageDecodeException(response.request.url.toString(), response.mimeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class ExternalPluginContentSource(
|
||||
publicUrl = details.publicUrl.ifEmpty { manga.publicUrl },
|
||||
rating = maxOf(details.rating, manga.rating),
|
||||
isNsfw = details.isNsfw,
|
||||
coverUrl = details.coverUrl.ifEmpty { manga.coverUrl },
|
||||
coverUrl = details.coverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||
tags = details.tags + manga.tags,
|
||||
state = details.state ?: manga.state,
|
||||
author = details.author.ifNullOrEmpty { manga.author },
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.find
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
||||
import java.io.File
|
||||
import java.net.Proxy
|
||||
@@ -412,10 +413,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
get() = prefs.getString(KEY_PROXY_PORT, null)?.toIntOrNull() ?: 0
|
||||
|
||||
val proxyLogin: String?
|
||||
get() = prefs.getString(KEY_PROXY_LOGIN, null)?.takeUnless { it.isEmpty() }
|
||||
get() = prefs.getString(KEY_PROXY_LOGIN, null)?.nullIfEmpty()
|
||||
|
||||
val proxyPassword: String?
|
||||
get() = prefs.getString(KEY_PROXY_PASSWORD, null)?.takeUnless { it.isEmpty() }
|
||||
get() = prefs.getString(KEY_PROXY_PASSWORD, null)?.nullIfEmpty()
|
||||
|
||||
var localListOrder: SortOrder
|
||||
get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.settings.utils.validation.DomainValidator
|
||||
|
||||
class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig {
|
||||
@@ -38,7 +39,7 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig
|
||||
|
||||
is ConfigKey.ShowSuspiciousContent -> prefs.getBoolean(key.key, key.defaultValue)
|
||||
is ConfigKey.SplitByTranslations -> prefs.getBoolean(key.key, key.defaultValue)
|
||||
is ConfigKey.PreferredImageServer -> prefs.getString(key.key, key.defaultValue)?.takeUnless(String::isEmpty)
|
||||
is ConfigKey.PreferredImageServer -> prefs.getString(key.key, key.defaultValue)?.nullIfEmpty()
|
||||
} as T
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.storage.StorageManager
|
||||
import android.provider.DocumentsContract
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.removeSuffix
|
||||
import java.io.File
|
||||
import java.lang.reflect.Array as ArrayReflect
|
||||
@@ -80,7 +81,7 @@ private fun getVolumePathForAndroid11AndAbove(volumeId: String, context: Context
|
||||
private fun getVolumeIdFromTreeUri(treeUri: Uri): String? {
|
||||
val docId = DocumentsContract.getTreeDocumentId(treeUri)
|
||||
val split = docId.split(":".toRegex())
|
||||
return split.firstOrNull()?.takeUnless { it.isEmpty() }
|
||||
return split.firstOrNull()?.nullIfEmpty()
|
||||
}
|
||||
|
||||
private fun getDocumentPathFromTreeUri(treeUri: Uri): String? {
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.parsers.util.ellipsize
|
||||
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
|
||||
import java.util.UUID
|
||||
|
||||
inline fun <C : CharSequence?> C?.ifNullOrEmpty(defaultValue: () -> C): C {
|
||||
inline fun <C : R, R : CharSequence?> C?.ifNullOrEmpty(defaultValue: () -> R): R {
|
||||
return if (this.isNullOrEmpty()) defaultValue() else this
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.exceptions.ProxyConfigException
|
||||
import org.koitharu.kotatsu.core.exceptions.SyncApiException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrapperIOException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.io.NullOutputStream
|
||||
@@ -41,17 +42,21 @@ import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredExcept
|
||||
import java.io.ObjectOutputStream
|
||||
import java.net.ConnectException
|
||||
import java.net.NoRouteToHostException
|
||||
import java.net.SocketException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import java.util.Locale
|
||||
|
||||
private const val MSG_NO_SPACE_LEFT = "No space left on device"
|
||||
private const val MSG_CONNECTION_RESET = "Connection reset"
|
||||
private const val IMAGE_FORMAT_NOT_SUPPORTED = "Image format not supported"
|
||||
|
||||
fun Throwable.getDisplayMessage(resources: Resources): String = getDisplayMessageOrNull(resources)
|
||||
?: resources.getString(R.string.error_occurred)
|
||||
|
||||
private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = when (this) {
|
||||
is CaughtException -> cause.getDisplayMessageOrNull(resources)
|
||||
is WrapperIOException -> cause.getDisplayMessageOrNull(resources)
|
||||
is ScrobblerAuthRequiredException -> resources.getString(
|
||||
R.string.scrobbler_auth_required,
|
||||
resources.getString(scrobbler.titleResId),
|
||||
@@ -117,7 +122,7 @@ private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = w
|
||||
is HttpException -> getHttpDisplayMessage(response.code, resources)
|
||||
is HttpStatusException -> getHttpDisplayMessage(statusCode, resources)
|
||||
|
||||
else -> getDisplayMessage(message, resources) ?: message
|
||||
else -> mapDisplayMessage(message, resources) ?: message
|
||||
}.takeUnless { it.isNullOrBlank() }
|
||||
|
||||
@DrawableRes
|
||||
@@ -139,7 +144,9 @@ fun Throwable.getCauseUrl(): String? = when (this) {
|
||||
is ParseException -> url
|
||||
is NotFoundException -> url
|
||||
is TooManyRequestExceptions -> url
|
||||
is CaughtException -> cause?.getCauseUrl()
|
||||
is CaughtException -> cause.getCauseUrl()
|
||||
is WrapperIOException -> cause.getCauseUrl()
|
||||
is NoDataReceivedException -> url
|
||||
is CloudFlareBlockedException -> url
|
||||
is CloudFlareProtectedException -> url
|
||||
is HttpStatusException -> url
|
||||
@@ -154,10 +161,11 @@ private fun getHttpDisplayMessage(statusCode: Int, resources: Resources): String
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun getDisplayMessage(msg: String?, resources: Resources): String? = when {
|
||||
private fun mapDisplayMessage(msg: String?, resources: Resources): String? = when {
|
||||
msg.isNullOrEmpty() -> null
|
||||
msg.contains(MSG_NO_SPACE_LEFT) -> resources.getString(R.string.error_no_space_left)
|
||||
msg.contains(IMAGE_FORMAT_NOT_SUPPORTED) -> resources.getString(R.string.error_corrupted_file)
|
||||
msg == MSG_CONNECTION_RESET -> resources.getString(R.string.error_connection_reset)
|
||||
msg == FILTER_MULTIPLE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_genres_not_supported)
|
||||
msg == FILTER_MULTIPLE_STATES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_states_not_supported)
|
||||
msg == SEARCH_NOT_SUPPORTED -> resources.getString(R.string.error_search_not_supported)
|
||||
@@ -171,7 +179,10 @@ fun Throwable.isReportable(): Boolean {
|
||||
return true
|
||||
}
|
||||
if (this is CaughtException) {
|
||||
return cause?.isReportable() == true
|
||||
return cause.isReportable()
|
||||
}
|
||||
if (this is WrapperIOException) {
|
||||
return cause.isReportable()
|
||||
}
|
||||
if (ExceptionResolver.canResolve(this)) {
|
||||
return false
|
||||
@@ -184,6 +195,7 @@ fun Throwable.isReportable(): Boolean {
|
||||
|| this is WrongPasswordException
|
||||
|| this is TooManyRequestExceptions
|
||||
|| this is HttpStatusException
|
||||
|| this is SocketException
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ class DetailsActivity :
|
||||
startActivity(
|
||||
ImageActivity.newIntent(
|
||||
v.context,
|
||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } ?: return,
|
||||
manga.source,
|
||||
),
|
||||
scaleUpActivityOptionsOf(v),
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.setTextColorAttr
|
||||
import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
fun pageThumbnailAD(
|
||||
@@ -36,7 +37,7 @@ fun pageThumbnailAD(
|
||||
AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
|
||||
|
||||
bind {
|
||||
val data: Any = item.page.preview?.takeUnless { it.isEmpty() } ?: item.page.toMangaPage()
|
||||
val data: Any = item.page.preview?.nullIfEmpty() ?: item.page.toMangaPage()
|
||||
binding.imageViewThumb.newImageRequest(lifecycleOwner, data)?.run {
|
||||
defaultPlaceholders(context)
|
||||
size(thumbSize)
|
||||
|
||||
@@ -199,7 +199,7 @@ class DownloadWorker @AssistedInject constructor(
|
||||
format = task.format ?: settings.preferredDownloadFormat,
|
||||
)
|
||||
val coverUrl = mangaDetails.largeCoverUrl.ifNullOrEmpty { mangaDetails.coverUrl }
|
||||
if (coverUrl.isNotEmpty()) {
|
||||
if (!coverUrl.isNullOrEmpty()) {
|
||||
downloadFile(coverUrl, destination, repo.source).let { file ->
|
||||
output.addCover(file, MimeTypeMap.getFileExtensionFromUrl(coverUrl))
|
||||
file.deleteAwait()
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.favourites.domain.model
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
|
||||
data class Cover(
|
||||
val url: String,
|
||||
val url: String?,
|
||||
val source: String,
|
||||
) {
|
||||
val mangaSource by lazy { MangaSource(source) }
|
||||
|
||||
@@ -65,7 +65,7 @@ class FavoriteSheet : BaseAdaptiveSheet<SheetFavoriteCategoriesBinding>(), OnLis
|
||||
fun show(fm: FragmentManager, manga: Collection<Manga>) = FavoriteSheet().withArgs(1) {
|
||||
putParcelableArrayList(
|
||||
KEY_MANGA_LIST,
|
||||
manga.mapTo(ArrayList(manga.size), ::ParcelableManga),
|
||||
manga.mapTo(ArrayList(manga.size)) { ParcelableManga(it, withDescription = false) },
|
||||
)
|
||||
}.showDistinct(fm, TAG)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ class FavoriteSheetViewModel @Inject constructor(
|
||||
settings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled },
|
||||
) { categories, _, tracker ->
|
||||
mapList(categories, tracker)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(header))
|
||||
}.withErrorHandling()
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(header))
|
||||
|
||||
fun setChecked(categoryId: Long, isChecked: Boolean) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.model.YEAR_MIN
|
||||
import org.koitharu.kotatsu.parsers.util.ifZero
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
@@ -267,7 +268,7 @@ class FilterCoordinator @Inject constructor(
|
||||
}
|
||||
|
||||
fun setQuery(value: String?) {
|
||||
val newQuery = value?.trim()?.takeUnless { it.isEmpty() }
|
||||
val newQuery = value?.trim()?.nullIfEmpty()
|
||||
currentListFilter.update { oldValue ->
|
||||
if (capabilities.isSearchWithFiltersSupported || newQuery == null) {
|
||||
oldValue.copy(query = newQuery)
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.badge.BadgeUtils
|
||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
|
||||
@CheckResult
|
||||
fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {
|
||||
@@ -34,7 +35,7 @@ private fun View.bindBadgeImpl(
|
||||
if (counter > 0) {
|
||||
badgeDrawable.number = counter
|
||||
} else {
|
||||
badgeDrawable.text = text?.takeUnless { it.isEmpty() }
|
||||
badgeDrawable.text = text?.nullIfEmpty()
|
||||
}
|
||||
badgeDrawable.isVisible = true
|
||||
badgeDrawable.align(this)
|
||||
|
||||
@@ -7,7 +7,7 @@ data class MangaCompactListModel(
|
||||
override val id: Long,
|
||||
override val title: String,
|
||||
val subtitle: String,
|
||||
override val coverUrl: String,
|
||||
override val coverUrl: String?,
|
||||
override val manga: Manga,
|
||||
override val counter: Int,
|
||||
override val progress: ReadingProgress?,
|
||||
|
||||
@@ -8,7 +8,7 @@ data class MangaDetailedListModel(
|
||||
override val id: Long,
|
||||
override val title: String,
|
||||
val subtitle: String?,
|
||||
override val coverUrl: String,
|
||||
override val coverUrl: String?,
|
||||
override val manga: Manga,
|
||||
override val counter: Int,
|
||||
override val progress: ReadingProgress?,
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
data class MangaGridModel(
|
||||
override val id: Long,
|
||||
override val title: String,
|
||||
override val coverUrl: String,
|
||||
override val coverUrl: String?,
|
||||
override val manga: Manga,
|
||||
override val counter: Int,
|
||||
override val progress: ReadingProgress?,
|
||||
|
||||
@@ -11,7 +11,7 @@ sealed class MangaListModel : ListModel {
|
||||
abstract val id: Long
|
||||
abstract val manga: Manga
|
||||
abstract val title: String
|
||||
abstract val coverUrl: String
|
||||
abstract val coverUrl: String?
|
||||
abstract val counter: Int
|
||||
abstract val isFavorite: Boolean
|
||||
abstract val progress: ReadingProgress?
|
||||
|
||||
@@ -100,7 +100,7 @@ class PreviewFragment : BaseFragment<FragmentPreviewBinding>(), View.OnClickList
|
||||
R.id.imageView_cover -> startActivity(
|
||||
ImageActivity.newIntent(
|
||||
v.context,
|
||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } ?: return,
|
||||
manga.source,
|
||||
),
|
||||
scaleUpActivityOptionsOf(v),
|
||||
|
||||
@@ -152,7 +152,8 @@ class LocalMangaRepository @Inject constructor(
|
||||
"Manga is not stored on local storage"
|
||||
}.manga
|
||||
LocalMangaUtil(subject).deleteChapters(ids)
|
||||
localStorageChanges.emit(LocalManga(subject))
|
||||
val updated = getDetails(subject)
|
||||
localStorageChanges.emit(LocalManga(updated))
|
||||
}
|
||||
|
||||
suspend fun getRemoteManga(localManga: Manga): Manga? {
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.util.ext.subdir
|
||||
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
||||
import org.koitharu.kotatsu.core.util.ext.takeIfWriteable
|
||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
||||
import java.io.File
|
||||
|
||||
@@ -26,15 +26,14 @@ import org.koitharu.kotatsu.core.util.ext.longHashCode
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.toListSorted
|
||||
import org.koitharu.kotatsu.local.data.MangaIndex
|
||||
import org.koitharu.kotatsu.local.data.hasZipExtension
|
||||
import org.koitharu.kotatsu.local.data.isZipArchive
|
||||
import org.koitharu.kotatsu.local.data.output.LocalMangaOutput.Companion.ENTRY_NAME_INDEX
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.parsers.util.toCamelCase
|
||||
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
||||
import java.io.File
|
||||
|
||||
@@ -60,26 +59,31 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
val mangaInfo = index?.getMangaInfo()
|
||||
if (mangaInfo != null) {
|
||||
val coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it } ?: fileSystem.findFirstImage(rootPath)
|
||||
mangaInfo.copyInternal(
|
||||
mangaInfo.copy(
|
||||
source = LocalMangaSource,
|
||||
url = rootFile.toUri().toString(),
|
||||
coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() }.orEmpty(),
|
||||
largeCoverUrl = null,
|
||||
chapters = if (withDetails) {
|
||||
mangaInfo.chapters?.map { c ->
|
||||
c.copyInternal(
|
||||
url = index.getChapterFileName(c.id)?.toPath()?.let {
|
||||
uri.child(it, resolve = false).toString()
|
||||
} ?: uri.toString(),
|
||||
source = LocalMangaSource,
|
||||
)
|
||||
mangaInfo.chapters?.mapNotNull { c ->
|
||||
val path = index.getChapterFileName(c.id)?.toPath()
|
||||
if (path != null && !fileSystem.exists(rootPath / path)) {
|
||||
null
|
||||
} else {
|
||||
c.copy(
|
||||
url = path?.let {
|
||||
uri.child(it, resolve = false).toString()
|
||||
} ?: uri.toString(),
|
||||
source = LocalMangaSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
} else {
|
||||
val title = rootFile.nameWithoutExtension.replace("_", " ").toCamelCase()
|
||||
val title = rootFile.name.fileNameToTitle()
|
||||
val coverEntry = fileSystem.findFirstImage(rootPath)
|
||||
val mimeTypeMap = MimeTypeMap.getSingleton()
|
||||
Manga(
|
||||
@@ -94,10 +98,12 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
chapters = if (withDetails) {
|
||||
val chapters = fileSystem.listRecursively(rootPath)
|
||||
.mapNotNullTo(HashSet()) { path ->
|
||||
if (path != coverEntry && fileSystem.isRegularFile(path) && mimeTypeMap.isImage(path)) {
|
||||
path.parent
|
||||
} else {
|
||||
null
|
||||
when {
|
||||
path == coverEntry -> null
|
||||
!fileSystem.isRegularFile(path) -> null
|
||||
mimeTypeMap.isImage(path) -> path.parent
|
||||
hasZipExtension(path.name) -> path
|
||||
else -> null
|
||||
}
|
||||
}.sortedWith(compareBy(AlphanumComparator()) { x -> x.toString() })
|
||||
chapters.mapIndexed { i, p ->
|
||||
@@ -108,7 +114,7 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
}.toString().removePrefix(Path.DIRECTORY_SEPARATOR)
|
||||
MangaChapter(
|
||||
id = "$i$s".longHashCode(),
|
||||
name = s.ifEmpty { title },
|
||||
name = s.fileNameToTitle().ifEmpty { title },
|
||||
number = 0f,
|
||||
volume = 0,
|
||||
source = LocalMangaSource,
|
||||
@@ -267,43 +273,8 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
Path.DIRECTORY_SEPARATOR + this
|
||||
}.toPath()
|
||||
|
||||
private fun Manga.copyInternal(
|
||||
url: String = this.url,
|
||||
coverUrl: String = this.coverUrl,
|
||||
largeCoverUrl: String? = this.largeCoverUrl,
|
||||
chapters: List<MangaChapter>? = this.chapters,
|
||||
source: MangaSource = this.source,
|
||||
): Manga = Manga(
|
||||
id = id,
|
||||
title = title,
|
||||
altTitle = altTitle,
|
||||
url = url,
|
||||
publicUrl = publicUrl,
|
||||
rating = rating,
|
||||
isNsfw = isNsfw,
|
||||
coverUrl = coverUrl,
|
||||
tags = tags,
|
||||
state = state,
|
||||
author = author,
|
||||
largeCoverUrl = largeCoverUrl,
|
||||
description = description,
|
||||
chapters = chapters,
|
||||
source = source,
|
||||
)
|
||||
|
||||
private fun MangaChapter.copyInternal(
|
||||
url: String = this.url,
|
||||
source: MangaSource = this.source,
|
||||
) = MangaChapter(
|
||||
id = id,
|
||||
name = name,
|
||||
number = number,
|
||||
volume = volume,
|
||||
url = url,
|
||||
scanlator = scanlator,
|
||||
uploadDate = uploadDate,
|
||||
branch = branch,
|
||||
source = source,
|
||||
)
|
||||
private fun String.fileNameToTitle() = substringBeforeLast('.')
|
||||
.replace('_', ' ')
|
||||
.replaceFirstChar { it.uppercase() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.koitharu.kotatsu.local.domain
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.fold
|
||||
@@ -13,7 +12,6 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
@@ -26,7 +24,6 @@ class DeleteReadChaptersUseCase @Inject constructor(
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
@LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(manga: Manga): Int {
|
||||
@@ -37,7 +34,6 @@ class DeleteReadChaptersUseCase @Inject constructor(
|
||||
}
|
||||
val task = getDeletionTask(localManga) ?: return 0
|
||||
localMangaRepository.deleteChapters(task.manga.manga, task.chaptersIds)
|
||||
emitUpdate(localManga)
|
||||
return task.chaptersIds.size
|
||||
}
|
||||
|
||||
@@ -62,7 +58,6 @@ class DeleteReadChaptersUseCase @Inject constructor(
|
||||
}.buffer().map {
|
||||
runCatchingCancellable {
|
||||
localMangaRepository.deleteChapters(it.manga.manga, it.chaptersIds)
|
||||
emitUpdate(it.manga)
|
||||
it.chaptersIds.size
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
@@ -88,11 +83,6 @@ class DeleteReadChaptersUseCase @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun emitUpdate(subject: LocalManga) {
|
||||
val updated = localMangaRepository.getDetails(subject.manga)
|
||||
localStorageChanges.emit(subject.copy(manga = updated))
|
||||
}
|
||||
|
||||
private suspend fun getAllChapters(manga: LocalManga): List<MangaChapter> = runCatchingCancellable {
|
||||
val remoteManga = checkNotNull(localMangaRepository.getRemoteManga(manga.manga))
|
||||
checkNotNull(mangaRepositoryFactory.create(remoteManga.source).getDetails(remoteManga).chapters)
|
||||
|
||||
@@ -152,7 +152,8 @@ class ImportService : CoroutineIntentService() {
|
||||
private const val CHANNEL_ID = "importing"
|
||||
private const val FOREGROUND_NOTIFICATION_ID = 37
|
||||
|
||||
fun start(context: Context, uris: Iterable<Uri>): Boolean = try {
|
||||
fun start(context: Context, uris: Collection<Uri>): Boolean = try {
|
||||
require(uris.isNotEmpty())
|
||||
for (uri in uris) {
|
||||
val intent = Intent(context, ImportService::class.java)
|
||||
intent.putExtra(DATA_URI, uri.toString())
|
||||
|
||||
@@ -185,11 +185,8 @@ class PageLoader @Inject constructor(
|
||||
prefetchLock.withLock {
|
||||
while (prefetchQueue.isNotEmpty()) {
|
||||
val page = prefetchQueue.pollFirst() ?: return@launch
|
||||
if (cache.get(page.url) == null) {
|
||||
synchronized(tasks) {
|
||||
tasks[page.id] = loadPageAsyncImpl(page, skipCache = false, isPrefetch = true)
|
||||
}
|
||||
return@launch
|
||||
synchronized(tasks) {
|
||||
tasks[page.id] = loadPageAsyncImpl(page, skipCache = false, isPrefetch = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,12 +199,14 @@ class PageLoader @Inject constructor(
|
||||
): ProgressDeferred<Uri, Float> {
|
||||
val progress = MutableStateFlow(PROGRESS_UNDEFINED)
|
||||
val deferred = loaderScope.async {
|
||||
if (!skipCache) {
|
||||
cache.get(page.url)?.let { return@async it.toUri() }
|
||||
}
|
||||
counter.incrementAndGet()
|
||||
try {
|
||||
loadPageImpl(page, progress, isPrefetch)
|
||||
loadPageImpl(
|
||||
page = page,
|
||||
progress = progress,
|
||||
isPrefetch = isPrefetch,
|
||||
skipCache = skipCache,
|
||||
)
|
||||
} finally {
|
||||
if (counter.decrementAndGet() == 0) {
|
||||
onIdle()
|
||||
@@ -231,9 +230,13 @@ class PageLoader @Inject constructor(
|
||||
page: MangaPage,
|
||||
progress: MutableStateFlow<Float>,
|
||||
isPrefetch: Boolean,
|
||||
skipCache: Boolean,
|
||||
): Uri = semaphore.withPermit {
|
||||
val pageUrl = getPageUrl(page)
|
||||
check(pageUrl.isNotBlank()) { "Cannot obtain full image url for $page" }
|
||||
if (!skipCache) {
|
||||
cache.get(pageUrl)?.let { return it.toUri() }
|
||||
}
|
||||
val uri = Uri.parse(pageUrl)
|
||||
return when {
|
||||
uri.isZipUri() -> if (uri.scheme == URI_SCHEME_ZIP) {
|
||||
|
||||
@@ -209,6 +209,9 @@ class ReaderInfoBarView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun Drawable.drawWithOutline(canvas: Canvas) {
|
||||
if (bounds.isEmpty) {
|
||||
return
|
||||
}
|
||||
var requiredScale = (bounds.width() + paint.strokeWidth * 2f) / bounds.width().toFloat()
|
||||
setTint(colorOutline)
|
||||
canvas.withScale(requiredScale, requiredScale, bounds.exactCenterX(), bounds.exactCenterY()) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.format
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
||||
import javax.inject.Inject
|
||||
import com.google.android.material.R as materialR
|
||||
@@ -137,7 +138,7 @@ class ColorFilterConfigActivity :
|
||||
}
|
||||
|
||||
private fun loadPreview(page: MangaPage) {
|
||||
val data: Any = page.preview?.takeUnless { it.isEmpty() } ?: page
|
||||
val data: Any = page.preview?.nullIfEmpty() ?: page
|
||||
ImageRequest.Builder(this@ColorFilterConfigActivity)
|
||||
.data(data)
|
||||
.scale(Scale.FILL)
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.scrobbling.common.data
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import org.jsoup.internal.StringUtil.StringJoiner
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
|
||||
|
||||
@@ -31,7 +32,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) {
|
||||
ScrobblerUser(
|
||||
id = lines[0].toLong(),
|
||||
nickname = lines[1],
|
||||
avatar = lines[2].takeUnless(String::isEmpty),
|
||||
avatar = lines[2].nullIfEmpty(),
|
||||
service = ScrobblerService.valueOf(lines[3]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import okhttp3.internal.closeQuietly
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.parsers.util.mimeType
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||
@@ -34,7 +35,7 @@ class KitsuInterceptor(private val storage: ScrobblerStorage) : Interceptor {
|
||||
}
|
||||
if (response.mimeType?.toMediaTypeOrNull()?.subtype == SUBTYPE_HTML) {
|
||||
val message = runCatchingCancellable {
|
||||
response.parseHtml().title().takeUnless { it.isEmpty() }
|
||||
response.parseHtml().title().nullIfEmpty()
|
||||
}.onFailure {
|
||||
response.closeQuietly()
|
||||
}.getOrNull() ?: "Invalid response (${response.code})"
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.settings
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.preference.Preference
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -10,12 +11,16 @@ import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchMenuProvider
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchViewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RootSettingsFragment : BasePreferenceFragment(0) {
|
||||
|
||||
private val viewModel: RootSettingsViewModel by viewModels()
|
||||
private val activityViewModel: SettingsSearchViewModel by activityViewModels()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_root)
|
||||
@@ -41,6 +46,8 @@ class RootSettingsFragment : BasePreferenceFragment(0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
addMenuProvider(SettingsSearchMenuProvider(activityViewModel))
|
||||
addMenuProvider(SettingsMenuProvider(view.context))
|
||||
}
|
||||
|
||||
override fun setTitle(title: CharSequence?) {
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.search.SettingsItem
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchFragment
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchMenuProvider
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchViewModel
|
||||
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
|
||||
@@ -76,8 +75,6 @@ class SettingsActivity :
|
||||
}
|
||||
viewModel.isSearchActive.observe(this, ::toggleSearchMode)
|
||||
viewModel.onNavigateToPreference.observeEvent(this, ::navigateToPreference)
|
||||
addMenuProvider(SettingsSearchMenuProvider(viewModel))
|
||||
addMenuProvider(SettingsMenuProvider(this))
|
||||
}
|
||||
|
||||
override fun onPreferenceStartFragment(
|
||||
@@ -174,8 +171,9 @@ class SettingsActivity :
|
||||
}
|
||||
|
||||
private fun navigateToPreference(item: SettingsItem) {
|
||||
val args = Bundle(1)
|
||||
args.putString(ARG_PREF_KEY, item.key)
|
||||
val args = Bundle(1).apply {
|
||||
putString(ARG_PREF_KEY, item.key)
|
||||
}
|
||||
openFragment(item.fragmentClass, args, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,6 @@ class SyncSettingsFragment : BasePreferenceFragment(R.string.sync_settings), Fra
|
||||
|
||||
private fun bindHostSummary() {
|
||||
val preference = findPreference<Preference>(SyncSettings.KEY_SYNC_URL) ?: return
|
||||
preference.summary = syncSettings.syncURL
|
||||
preference.summary = syncSettings.syncUrl
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.AsyncListDiffer.ListListener
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||
@@ -17,7 +19,9 @@ import org.koitharu.kotatsu.databinding.FragmentSearchSuggestionBinding
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SettingsSearchFragment : BaseFragment<FragmentSearchSuggestionBinding>(), OnListItemClickListener<SettingsItem> {
|
||||
class SettingsSearchFragment : BaseFragment<FragmentSearchSuggestionBinding>(),
|
||||
OnListItemClickListener<SettingsItem>,
|
||||
ListListener<SettingsItem> {
|
||||
|
||||
private val viewModel: SettingsSearchViewModel by activityViewModels()
|
||||
|
||||
@@ -29,6 +33,7 @@ class SettingsSearchFragment : BaseFragment<FragmentSearchSuggestionBinding>(),
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
val adapter = BaseListAdapter<SettingsItem>()
|
||||
.addDelegate(ListItemType.NAV_ITEM, settingsItemAD(this))
|
||||
adapter.addListListener(this)
|
||||
binding.root.adapter = adapter
|
||||
binding.root.setHasFixedSize(true)
|
||||
viewModel.content.observe(viewLifecycleOwner, adapter)
|
||||
@@ -45,4 +50,13 @@ class SettingsSearchFragment : BaseFragment<FragmentSearchSuggestionBinding>(),
|
||||
}
|
||||
|
||||
override fun onItemClick(item: SettingsItem, view: View) = viewModel.navigateToPreference(item)
|
||||
|
||||
override fun onCurrentListChanged(
|
||||
previousList: List<SettingsItem?>,
|
||||
currentList: List<SettingsItem?>
|
||||
) {
|
||||
if (currentList.size != previousList.size) {
|
||||
(viewBinding?.root?.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,12 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.settings.AppearanceSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.DownloadsSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.NetworkSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.ProxySettingsFragment
|
||||
import org.koitharu.kotatsu.settings.ReaderSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.ServicesSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.SuggestionsSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.backup.PeriodicalBackupSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment
|
||||
@@ -39,6 +42,30 @@ class SettingsSearchHelper @Inject constructor(
|
||||
preferenceManager.inflateTo(result, R.xml.pref_tracker, emptyList(), TrackerSettingsFragment::class.java)
|
||||
preferenceManager.inflateTo(result, R.xml.pref_services, emptyList(), ServicesSettingsFragment::class.java)
|
||||
preferenceManager.inflateTo(result, R.xml.pref_about, emptyList(), AboutSettingsFragment::class.java)
|
||||
preferenceManager.inflateTo(
|
||||
result,
|
||||
R.xml.pref_backup_periodic,
|
||||
listOf(context.getString(R.string.data_and_privacy)),
|
||||
PeriodicalBackupSettingsFragment::class.java,
|
||||
)
|
||||
preferenceManager.inflateTo(
|
||||
result,
|
||||
R.xml.pref_proxy,
|
||||
listOf(context.getString(R.string.proxy)),
|
||||
ProxySettingsFragment::class.java,
|
||||
)
|
||||
preferenceManager.inflateTo(
|
||||
result,
|
||||
R.xml.pref_suggestions,
|
||||
listOf(context.getString(R.string.suggestions)),
|
||||
SuggestionsSettingsFragment::class.java,
|
||||
)
|
||||
preferenceManager.inflateTo(
|
||||
result,
|
||||
R.xml.pref_sources,
|
||||
listOf(context.getString(R.string.remote_sources)),
|
||||
SourcesSettingsFragment::class.java,
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -22,18 +22,20 @@ class SettingsSearchMenuProvider(
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
super.onPrepareMenu(menu)
|
||||
val currentQuery = viewModel.currentQuery
|
||||
if (currentQuery.isNotEmpty()) {
|
||||
if (viewModel.isSearchActive.value) {
|
||||
val menuItem = menu.findItem(R.id.action_search)
|
||||
menuItem.expandActionView()
|
||||
val searchView = menuItem.actionView as SearchView
|
||||
searchView.setQuery(currentQuery, false)
|
||||
searchView.setQuery(viewModel.currentQuery, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = false
|
||||
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean = true
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
viewModel.startSearch()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
viewModel.discardSearch()
|
||||
|
||||
@@ -18,30 +18,43 @@ class SettingsSearchViewModel @Inject constructor(
|
||||
private val searchHelper: SettingsSearchHelper,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val query = MutableStateFlow("")
|
||||
private val query = MutableStateFlow<String?>(null)
|
||||
private val allSettings by lazy {
|
||||
searchHelper.inflatePreferences()
|
||||
}
|
||||
|
||||
val content = query.map { q ->
|
||||
allSettings.filter { it.title.contains(q, ignoreCase = true) }
|
||||
if (q == null) {
|
||||
emptyList()
|
||||
} else {
|
||||
allSettings.filter { it.title.contains(q, ignoreCase = true) }
|
||||
}
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())
|
||||
|
||||
val isSearchActive = query.map {
|
||||
it.isNotEmpty()
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)
|
||||
it != null
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, query.value != null)
|
||||
|
||||
val onNavigateToPreference = MutableEventFlow<SettingsItem>()
|
||||
val currentQuery: String
|
||||
get() = query.value
|
||||
get() = query.value.orEmpty()
|
||||
|
||||
fun onQueryChanged(value: String) {
|
||||
query.value = value
|
||||
if (query.value != null) {
|
||||
query.value = value
|
||||
}
|
||||
}
|
||||
|
||||
fun discardSearch() = onQueryChanged("")
|
||||
fun discardSearch() {
|
||||
query.value = null
|
||||
}
|
||||
|
||||
fun startSearch() {
|
||||
query.value = query.value.orEmpty()
|
||||
}
|
||||
|
||||
fun navigateToPreference(item: SettingsItem) {
|
||||
discardSearch()
|
||||
onNavigateToPreference.call(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class SyncAuthenticator(
|
||||
private fun tryRefreshToken() = runCatching {
|
||||
runBlocking {
|
||||
authApi.authenticate(
|
||||
syncSettings.syncURL,
|
||||
syncSettings.syncUrl,
|
||||
account.name,
|
||||
accountManager.getPassword(account),
|
||||
)
|
||||
|
||||
@@ -27,13 +27,9 @@ class SyncSettings(
|
||||
|
||||
@get:WorkerThread
|
||||
@set:WorkerThread
|
||||
var syncURL: String
|
||||
var syncUrl: String
|
||||
get() = account?.let {
|
||||
val result = accountManager.getUserData(it, KEY_SYNC_URL)
|
||||
if (!result.startsWith("http://") && !result.startsWith("https://")) {
|
||||
return "http://$result"
|
||||
}
|
||||
return result
|
||||
accountManager.getUserData(it, KEY_SYNC_URL)?.withHttpSchema()
|
||||
}.ifNullOrEmpty { defaultSyncUrl }
|
||||
set(value) {
|
||||
account?.let {
|
||||
@@ -43,6 +39,12 @@ class SyncSettings(
|
||||
|
||||
companion object {
|
||||
|
||||
private fun String.withHttpSchema(): String = if (!startsWith("http://") && !startsWith("https://")) {
|
||||
"http://$this"
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
const val KEY_SYNC_URL = "host"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class SyncHelper @AssistedInject constructor(
|
||||
.addInterceptor(SyncInterceptor(context, account))
|
||||
.build()
|
||||
private val baseUrl: String by lazy {
|
||||
settings.syncURL
|
||||
settings.syncUrl
|
||||
}
|
||||
private val defaultGcPeriod: Long // gc period if sync enabled
|
||||
get() = TimeUnit.DAYS.toMillis(4)
|
||||
|
||||
@@ -52,7 +52,7 @@ class SyncHostDialogFragment : AlertDialogFragment<PreferenceDialogAutocompletet
|
||||
binding.message.setText(R.string.sync_host_description)
|
||||
val entries = binding.root.resources.getStringArray(R.array.sync_url_list)
|
||||
val editText = binding.edit
|
||||
editText.setText(arguments?.getString(KEY_SYNC_URL).ifNullOrEmpty { syncSettings.syncURL })
|
||||
editText.setText(arguments?.getString(KEY_SYNC_URL).ifNullOrEmpty { syncSettings.syncUrl })
|
||||
editText.threshold = 0
|
||||
editText.setAdapter(ArrayAdapter(binding.root.context, android.R.layout.simple_spinner_dropdown_item, entries))
|
||||
binding.dropdown.setOnClickListener {
|
||||
@@ -69,7 +69,7 @@ class SyncHostDialogFragment : AlertDialogFragment<PreferenceDialogAutocompletet
|
||||
if (!result.startsWith("https://") && !result.startsWith("http://")) {
|
||||
scheme = "http://"
|
||||
}
|
||||
syncSettings.syncURL = "$scheme$result"
|
||||
syncSettings.syncUrl = "$scheme$result"
|
||||
parentFragmentManager.setFragmentResult(REQUEST_KEY, bundleOf(KEY_SYNC_URL to "$scheme$result"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.tracker.domain
|
||||
|
||||
import android.util.Log
|
||||
import coil3.request.CachePolicy
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.core.parser.CachingMangaRepository
|
||||
@@ -11,6 +13,7 @@ import org.koitharu.kotatsu.core.util.ext.toInstantOrNull
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.findById
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
||||
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
||||
@@ -45,8 +48,9 @@ class CheckNewChaptersUseCase @Inject constructor(
|
||||
runCatchingCancellable {
|
||||
repository.updateTracks()
|
||||
val details = getFullManga(manga)
|
||||
val chapters = details.chapters ?: return@withLock
|
||||
val track = repository.getTrackOrNull(manga) ?: return@withLock
|
||||
val branch = checkNotNull(details.chapters?.findById(currentChapterId)).branch
|
||||
val chapters = details.getChapters(branch)
|
||||
val chapterIndex = chapters.indexOfFirst { x -> x.id == currentChapterId }
|
||||
val lastNewChapterIndex = chapters.size - track.newChapters
|
||||
val lastChapter = chapters.lastOrNull()
|
||||
@@ -70,7 +74,7 @@ class CheckNewChaptersUseCase @Inject constructor(
|
||||
|
||||
private suspend fun invokeImpl(track: MangaTracking): MangaUpdates = runCatchingCancellable {
|
||||
val details = getFullManga(track.manga)
|
||||
compare(track, details, getBranch(details))
|
||||
compare(track, details, getBranch(details, track.lastChapterId))
|
||||
}.getOrElse { error ->
|
||||
MangaUpdates.Failure(
|
||||
manga = track.manga,
|
||||
@@ -80,9 +84,17 @@ class CheckNewChaptersUseCase @Inject constructor(
|
||||
repository.saveUpdates(updates)
|
||||
}
|
||||
|
||||
private suspend fun getBranch(manga: Manga): String? {
|
||||
val history = historyRepository.getOne(manga)
|
||||
return manga.getPreferredBranch(history)
|
||||
private suspend fun getBranch(manga: Manga, trackChapterId: Long): String? {
|
||||
historyRepository.getOne(manga)?.let {
|
||||
manga.chapters?.findById(it.chapterId)
|
||||
}?.let {
|
||||
return it.branch
|
||||
}
|
||||
manga.chapters?.findById(trackChapterId)?.let {
|
||||
return it.branch
|
||||
}
|
||||
// fallback
|
||||
return manga.getPreferredBranch(null)
|
||||
}
|
||||
|
||||
private suspend fun getFullManga(manga: Manga): Manga = when {
|
||||
@@ -111,25 +123,29 @@ class CheckNewChaptersUseCase @Inject constructor(
|
||||
private fun compare(track: MangaTracking, manga: Manga, branch: String?): MangaUpdates.Success {
|
||||
if (track.isEmpty()) {
|
||||
// first check or manga was empty on last check
|
||||
return MangaUpdates.Success(manga, emptyList(), isValid = false)
|
||||
return MangaUpdates.Success(manga, branch, emptyList(), isValid = false)
|
||||
}
|
||||
val chapters = requireNotNull(manga.getChapters(branch))
|
||||
if (BuildConfig.DEBUG && chapters.findById(track.lastChapterId) == null) {
|
||||
Log.e("Tracker", "Chapter ${track.lastChapterId} not found")
|
||||
}
|
||||
val newChapters = chapters.takeLastWhile { x -> x.id != track.lastChapterId }
|
||||
return when {
|
||||
newChapters.isEmpty() -> {
|
||||
MangaUpdates.Success(
|
||||
manga = manga,
|
||||
branch = branch,
|
||||
newChapters = emptyList(),
|
||||
isValid = chapters.lastOrNull()?.id == track.lastChapterId,
|
||||
)
|
||||
}
|
||||
|
||||
newChapters.size == chapters.size -> {
|
||||
MangaUpdates.Success(manga, emptyList(), isValid = false)
|
||||
MangaUpdates.Success(manga, branch, emptyList(), isValid = false)
|
||||
}
|
||||
|
||||
else -> {
|
||||
MangaUpdates.Success(manga, newChapters, isValid = true)
|
||||
MangaUpdates.Success(manga, branch, newChapters, isValid = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,6 @@ class TrackingRepository @Inject constructor(
|
||||
}
|
||||
|
||||
private fun TrackEntity.mergeWith(updates: MangaUpdates): TrackEntity {
|
||||
val chapters = updates.manga.chapters.orEmpty()
|
||||
return when (updates) {
|
||||
is MangaUpdates.Failure -> TrackEntity(
|
||||
mangaId = mangaId,
|
||||
@@ -230,7 +229,7 @@ class TrackingRepository @Inject constructor(
|
||||
|
||||
is MangaUpdates.Success -> TrackEntity(
|
||||
mangaId = mangaId,
|
||||
lastChapterId = chapters.lastOrNull()?.id ?: NO_ID,
|
||||
lastChapterId = updates.manga.getChapters(updates.branch).lastOrNull()?.id ?: NO_ID,
|
||||
newChapters = if (updates.isValid) newChapters + updates.newChapters.size else 0,
|
||||
lastCheckTime = System.currentTimeMillis(),
|
||||
lastChapterDate = updates.lastChapterDate().ifZero { lastChapterDate },
|
||||
|
||||
@@ -11,6 +11,7 @@ sealed interface MangaUpdates {
|
||||
|
||||
data class Success(
|
||||
override val manga: Manga,
|
||||
val branch: String?,
|
||||
val newChapters: List<MangaChapter>,
|
||||
val isValid: Boolean,
|
||||
) : MangaUpdates {
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
data class FeedItem(
|
||||
val id: Long,
|
||||
val imageUrl: String,
|
||||
val imageUrl: String?,
|
||||
val title: String,
|
||||
val manga: Manga,
|
||||
val count: Int,
|
||||
|
||||
@@ -30,7 +30,7 @@ class WidgetUpdater @Inject constructor(
|
||||
private fun updateWidgets(cls: Class<*>) {
|
||||
val intent = Intent(context, cls)
|
||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
val ids = AppWidgetManager.getInstance(context)
|
||||
val ids = (AppWidgetManager.getInstance(context) ?: return)
|
||||
.getAppWidgetIds(ComponentName(context, cls))
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
||||
context.sendBroadcast(intent)
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?colorSecondaryContainer"
|
||||
android:clipToOutline="true"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintDimensionRatio="H,13:18"
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?colorSecondaryContainer"
|
||||
android:clipToOutline="true"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintDimensionRatio="H,13:18"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?colorSecondaryContainer"
|
||||
android:clipToOutline="true"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintDimensionRatio="H,13:18"
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:clipToOutline="true"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintDimensionRatio="H,13:18"
|
||||
|
||||
@@ -659,4 +659,7 @@
|
||||
<string name="external_source">خارجي/إضافي</string>
|
||||
<string name="plugin_incompatible">مكون إضافي غير متوافق أو خطأ داخلي. تأكد من استخدام أحدث إصدار من المكون الإضافي وKotatsu</string>
|
||||
<string name="invalid_server_address_message">mangatime.org</string>
|
||||
</resources>
|
||||
<string name="retry">حاول مجدداً</string>
|
||||
<string name="pages_saved">الصفحات المحفوظة</string>
|
||||
<string name="too_many_requests_message_retry">هنالك الكثير من الطلبات. حاول مرة أخرى بعد%s</string>
|
||||
</resources>
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
<string name="status_on_hold">Pozastaveno</string>
|
||||
<string name="status_dropped">Zahozeno</string>
|
||||
<string name="disable_all">Vypnout vše</string>
|
||||
<string name="use_fingerprint">Pokud lze, použijte otisk prstu</string>
|
||||
<string name="use_fingerprint">Pokud lze, použijte biometrii</string>
|
||||
<string name="appwidget_shelf_description">Manga z vašich oblíbených</string>
|
||||
<string name="appwidget_recent_description">Vaše nedávno čtená manga</string>
|
||||
<string name="report">Hlášení</string>
|
||||
@@ -251,7 +251,7 @@
|
||||
<string name="manga_error_description_pattern">Podrobnosti chyby:<br><tt>%1$s</tt><br><br>1. Zkuste <a href=%2$s>otveřít mangu v prohlížeči</a> abyste se ujistili že je dostupná na zdroji<br>2. Ujistěte se že používáte <a href=kotatsu://about>nejnovější verzi Kotatsu</a><br>3. Pokud je dostupná, pošlete hlášení o chybě vývojářům.</string>
|
||||
<string name="history_shortcuts">Zobrazovat zkratky nedávných mang</string>
|
||||
<string name="history_shortcuts_summary">Udělejte nedávné mangy dostupné dlouhým kliknutím na ikonu aplikace</string>
|
||||
<string name="reader_control_ltr_summary">Kliknutí do pravého rohu nebo stisknutí pravého tlačítka vždy zobrazí následující stranu.</string>
|
||||
<string name="reader_control_ltr_summary">Neměň směr přepínání stránek v režimu čtení, například stisknutí pravé klávesy vždy přepne na další stránku. Tato volba ovlivňuje pouze hardwarová vstupní zařízení.</string>
|
||||
<string name="reader_control_ltr">Ovládání ergonomické čtečky</string>
|
||||
<string name="color_correction">Korekce barev</string>
|
||||
<string name="brightness">Jas</string>
|
||||
@@ -654,4 +654,21 @@
|
||||
<string name="unpin">Odepnout</string>
|
||||
<string name="too_many_requests_message_retry">Server přetížen. Zkuste to za %s</string>
|
||||
<string name="minutes_seconds_short">%1$d m %2$d s</string>
|
||||
<string name="invalid_proxy_configuration">Nesprávná proxy konfigurace</string>
|
||||
<string name="plugin_incompatible_with_cause">Plugin error:%s\nUjisti se, že používáš poslední verzi Kotatsu a pluginu</string>
|
||||
<string name="percent_left">Procent zbývá</string>
|
||||
<string name="plugin_incompatible">Nekompatibilní plugin nebo vnitřní chyba. Ujistěte se, že používáte nejnovější verzi pluginu a aplikace Kotatsu.</string>
|
||||
<string name="external_source">Externí zdroj/plugin</string>
|
||||
<string name="retry">Opakovat</string>
|
||||
<string name="connection_ok">Připojení je OK</string>
|
||||
<string name="recent_sources">Nedávné zdroje</string>
|
||||
<string name="image_server">Preferovaný server pro média</string>
|
||||
<string name="crop_pages">Oříznout stránky</string>
|
||||
<string name="show_quick_filters">Zobrazit rychlé filtry</string>
|
||||
<string name="source_unpinned">Zdroj odepnut</string>
|
||||
<string name="sources_unpinned">Zdroje odepnuty</string>
|
||||
<string name="sources_pinned">Zdroje připnuty</string>
|
||||
<string name="percent_read">Procenta přečtených</string>
|
||||
<string name="chapters_read">Kapitol přečtených</string>
|
||||
<string name="chapters_left">Kapitol zbývajících</string>
|
||||
</resources>
|
||||
@@ -752,4 +752,5 @@
|
||||
<string name="email">Correo electrónico</string>
|
||||
<string name="handle_links">Gestionar enlaces</string>
|
||||
<string name="handle_links_summary">Gestionar enlaces de manga desde aplicaciones externas (por ejemplo, navegador web). También puede ser necesario habilitarlo manualmente en la configuración de la aplicación</string>
|
||||
<string name="captcha_required_message">Esta fuente requiere resolver un captcha para continuar</string>
|
||||
</resources>
|
||||
@@ -752,4 +752,5 @@
|
||||
<string name="handle_links">Pangasiwaan ang mga link</string>
|
||||
<string name="handle_links_summary">Pangasiwaan ang manga link mula sa mga panlabas na application (hal. web browser). Maaaring kailanganin mo rin itong manual na paganahin sa mga setting ng system ng aplikasyon</string>
|
||||
<string name="email">Ang email</string>
|
||||
<string name="captcha_required_message">Ang source na ito ay kinakailang lutasin ang captcha para magpatuloy</string>
|
||||
</resources>
|
||||
@@ -749,4 +749,5 @@
|
||||
<string name="handle_links">Gérer les liens</string>
|
||||
<string name="email">Courriel</string>
|
||||
<string name="handle_links_summary">Gérer les liens vers des mangas à partir d\'applications externes (par exemple, un navigateur web). Il se peut que vous deviez également l\'activer manuellement dans les paramètres système de l\'application</string>
|
||||
<string name="captcha_required_message">Cette source nécessite la résolution d\'un captcha pour continuer</string>
|
||||
</resources>
|
||||
@@ -50,7 +50,7 @@
|
||||
<string name="processing_">Przetwarzanie…</string>
|
||||
<string name="updated">Zaktualizowane</string>
|
||||
<string name="save_page">Zapisz stronę</string>
|
||||
<string name="page_saved">Zapisano</string>
|
||||
<string name="page_saved">Zapisano stronę</string>
|
||||
<string name="vibration">Wibracje</string>
|
||||
<string name="manga_shelf">Biblioteka</string>
|
||||
<string name="recent_manga">Ostatnie</string>
|
||||
@@ -739,4 +739,18 @@
|
||||
<string name="allow_always">Zezwalaj zawsze</string>
|
||||
<string name="allow_once">Zezwól raz</string>
|
||||
<string name="ask_every_time">Pytaj za każdym razem</string>
|
||||
<string name="pages_saved">Zapisane strony</string>
|
||||
<string name="portrait">Portret</string>
|
||||
<string name="access_denied_403">Odmowa dostępu (403)</string>
|
||||
<string name="captcha_required_message">To źródło wymaga rozwiązania captcha, aby kontynuować</string>
|
||||
<string name="handle_links">Obsługa linków</string>
|
||||
<string name="email">E-mail</string>
|
||||
<string name="max_backups_count">Maksymalna liczba kopii zapasowych</string>
|
||||
<string name="delete_old_backups">Usuń stare kopie zapasowe</string>
|
||||
<string name="delete_old_backups_summary">Automatycznie usuwaj stare pliki kopii zapasowych, aby zaoszczędzić miejsce na dysku</string>
|
||||
<string name="plugin_incompatible_with_cause">Błąd wtyczki: %s\nUpewnij się, że używasz najnowszej wersji wtyczki i Kotatsu</string>
|
||||
<string name="error_not_image">Błędny format: oczekiwany obraz, ale pobrano %s</string>
|
||||
<string name="screen_orientation">Orientacja ekranu</string>
|
||||
<string name="landscape">Poziomo</string>
|
||||
<string name="handle_links_summary">Obsługuj linki do mangi z zewnętrznych aplikacji (np. przeglądarki internetowej). Może być również konieczne ręczne włączenie go w ustawieniach systemowych aplikacji</string>
|
||||
</resources>
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="read">Ler</string>
|
||||
<string name="you_have_not_favourites_yet">Você ainda não marcou alguma obra como favorita</string>
|
||||
<string name="local_storage">Armazenamento local</string>
|
||||
<string name="favourites">Favoritos</string>
|
||||
<string name="favourites">Favoritas</string>
|
||||
<string name="history">Histórico</string>
|
||||
<string name="error_occurred">Ocorreu um erro</string>
|
||||
<string name="network_error">Erro de rede</string>
|
||||
@@ -61,8 +61,8 @@
|
||||
<string name="standard">Padrão</string>
|
||||
<string name="read_mode">Modo de leitura</string>
|
||||
<string name="grid_size">Tamanho da grade</string>
|
||||
<string name="search_on_s">Pesquisar em %s</string>
|
||||
<string name="delete_manga">Excluir mangá</string>
|
||||
<string name="search_on_s">Procurar em %s</string>
|
||||
<string name="delete_manga">Excluir obra</string>
|
||||
<string name="text_delete_local_manga">Excluir “%s” do dispositivo permanentemente?</string>
|
||||
<string name="reader_settings">Configurações do leitor</string>
|
||||
<string name="_continue">Continuar</string>
|
||||
@@ -288,8 +288,7 @@
|
||||
<string name="history_shortcuts_summary">Torne as obras recentes disponíveis ao pressionar e segurar o ícone do aplicativo</string>
|
||||
<string name="no_manga_sources_text">Habilite fontes para ler obras online</string>
|
||||
<string name="random">Aleatória</string>
|
||||
<string name="categories_delete_confirm">Você tem certeza de que deseja excluir as categorias favoritas selecionadas?
|
||||
\nTodas as obras nelas serão perdidas e isso não pode ser desfeito</string>
|
||||
<string name="categories_delete_confirm">Você tem certeza de que deseja excluir as categorias favoritas selecionadas?\nTodas as obras nelas serão perdidas e isso não pode ser desfeito.</string>
|
||||
<string name="reorder">Reordenar</string>
|
||||
<string name="empty">Vazio</string>
|
||||
<string name="explore">Explorar</string>
|
||||
@@ -393,7 +392,7 @@
|
||||
<string name="network">Rede</string>
|
||||
<string name="data_and_privacy">Dados e privacidade</string>
|
||||
<string name="restore_summary">Restaurar backup criado anteriormente</string>
|
||||
<string name="webtoon_zoom_summary">Permitir zoom no gesto no modo webtoon</string>
|
||||
<string name="webtoon_zoom_summary">Permitir zoom em gesto no modo webtoon</string>
|
||||
<string name="reader_info_bar_summary">Mostrar a hora atual e o progresso da leitura na parte superior da tela</string>
|
||||
<string name="clear_source_cookies_summary">Limpar cookies apenas para o domínio especificado. Na maioria dos casos invalidará a autorização</string>
|
||||
<string name="download_option_all_chapters">Todos os capítulos com tradução %s</string>
|
||||
@@ -501,8 +500,7 @@
|
||||
<string name="source_enabled">Fonte habilitada</string>
|
||||
<string name="disable_nsfw_summary">Desative as fontes NSFW e oculte as obras adultas da lista, se possível</string>
|
||||
<string name="speed_value">x%.1f</string>
|
||||
<string name="no_manga_sources_catalog_text">Não há fontes disponíveis nesta seção, ou todas elas podem já ter sido habilitadas.
|
||||
\nFique atento</string>
|
||||
<string name="no_manga_sources_catalog_text">Não há fontes disponíveis nesta seção, ou todas elas podem já ter sido habilitadas.\nFique atento</string>
|
||||
<string name="available_d">Disponível: %1$d</string>
|
||||
<string name="state">Situação</string>
|
||||
<string name="state_paused">Pausada</string>
|
||||
@@ -530,9 +528,7 @@
|
||||
<string name="rating_adult">Adulta</string>
|
||||
<string name="default_tab">Aba padrão</string>
|
||||
<string name="content_rating">Classificação do Conteúdo</string>
|
||||
<string name="mark_as_completed_prompt">Deseja marcar a obra selecionado como completa?
|
||||
\n
|
||||
\nAviso: o progresso de leitura atual será perdido.</string>
|
||||
<string name="mark_as_completed_prompt">Deseja marcar a obra selecionada como completa?\n\nAviso: o progresso de leitura atual será perdido.</string>
|
||||
<string name="mark_as_completed">Marcar como completa</string>
|
||||
<string name="category_hidden_done">Esta categoria foi ocultada da tela inicial e pode ser acessada novamente através de Menu → Gerenciar categorias</string>
|
||||
<string name="volume_">Volume %d</string>
|
||||
@@ -557,7 +553,7 @@
|
||||
<string name="use_two_pages_landscape">Usar layout de duas páginas na orientação paisagem (beta)</string>
|
||||
<string name="last_read">Última leitura</string>
|
||||
<string name="none">Nenhum</string>
|
||||
<string name="default_webtoon_zoom_out">Diminuir zoom padrão do webtoon</string>
|
||||
<string name="default_webtoon_zoom_out">Diminuir zoom padrão da webtoon</string>
|
||||
<string name="fullscreen_mode">Modo tela cheia</string>
|
||||
<string name="reader_fullscreen_summary">Ocultar a barra de status e navegação</string>
|
||||
<string name="remove_from_history">Remover do histórico</string>
|
||||
@@ -612,7 +608,7 @@
|
||||
<string name="order_oldest">Mais velho</string>
|
||||
<string name="long_ago_read">Lido há muito tempo atrás</string>
|
||||
<string name="split_by_translations">Separar por traduções</string>
|
||||
<string name="fix">Fixar</string>
|
||||
<string name="fix">Corrigir</string>
|
||||
<string name="error_no_data_received">Nenhum dado foi recebido do servidor</string>
|
||||
<string name="enable_source">Habilitar fonte</string>
|
||||
<string name="unsupported_source">Esta fonte não é suportada</string>
|
||||
@@ -622,10 +618,10 @@
|
||||
<string name="disable_connectivity_check">Desativar a verificação de conectividade</string>
|
||||
<string name="disable_connectivity_check_summary">Ignore a verificação de conectividade caso tenha problemas com ela (por exemplo, entrar no modo off-line mesmo que a rede esteja conectada)</string>
|
||||
<string name="webtoon_gaps">Lacunas no modo webtoon</string>
|
||||
<string name="webtoon_gaps_summary">Mostrar espaços verticais entre as páginas no modo webtoon</string>
|
||||
<string name="webtoon_gaps_summary">Mostrar lacunas verticais entre as páginas no modo webtoon</string>
|
||||
<string name="authors">Autores</string>
|
||||
<string name="ignore_ssl_errors_summary">Você pode desativar a verificação de certificados SSL caso tenha problemas relacionados a SSL ao acessar recursos de rede. Isso pode afetar sua segurança. É necessário reiniciar o aplicativo após alterar essa configuração.</string>
|
||||
<string name="search_suggestions">Sugestões de Pesquisa</string>
|
||||
<string name="search_suggestions">Sugestões de pesquisa</string>
|
||||
<string name="recent_queries">Consultas recentes</string>
|
||||
<string name="suggested_queries">Consultas sugeridas</string>
|
||||
<string name="disable">Desativar</string>
|
||||
@@ -649,21 +645,21 @@
|
||||
<string name="chapters_read">Capítulos lidos</string>
|
||||
<string name="chapters_left">Capítulos restantes</string>
|
||||
<string name="pin">Fixar</string>
|
||||
<string name="unpin">Desfixar</string>
|
||||
<string name="unpin">Desafixar</string>
|
||||
<string name="source_pinned">Fonte fixada</string>
|
||||
<string name="source_unpinned">Fonte desfixada</string>
|
||||
<string name="source_unpinned">Fonte desafixada</string>
|
||||
<string name="sources_pinned">Fontes fixadas</string>
|
||||
<string name="recent_sources">Fontes recentes</string>
|
||||
<string name="external_source">Plugin/Externo</string>
|
||||
<string name="sources_unpinned">Fontes desfixadas</string>
|
||||
<string name="sources_unpinned">Fontes desafixadas</string>
|
||||
<string name="tracker_debug_info">Checando por novos logs de capítulos</string>
|
||||
<string name="tracker_debug_info_summary">Informações de Debug sobre a checagem de fundo para novos capítulos</string>
|
||||
<string name="plugin_incompatible">Plugin incompatível ou erro interno. Certifique-se de que está usando a versão mais recente do plugin e do Kotatsu</string>
|
||||
<string name="show_quick_filters_summary">Habilitar filtros em todas as fontes compatíveis</string>
|
||||
<string name="show_quick_filters">Mostrar filtros</string>
|
||||
<string name="scrobbler_auth_required">Faça login em %s para continuar</string>
|
||||
<string name="scrobbler_auth_intro">Faça login para configurar a integração com %s. Isso permitirá que você acompanhe o progresso e o status de sua leitura de mangás</string>
|
||||
<string name="unstable_feature_summary">Essa função é experimental. Certifique-se de que você tenha um backup para evitar a perda de dados</string>
|
||||
<string name="scrobbler_auth_intro">Faça login para configurar a integração com %s. Isso permitirá que você acompanhe o progresso e o status de sua leitura</string>
|
||||
<string name="unstable_feature_summary">Essa função é experimental. Certifique-se de que você tenha um backup para evitar a perca de dados</string>
|
||||
<string name="recently_added">Adicionado recentemente</string>
|
||||
<string name="added_long_ago">Adicionado há muito tempo</string>
|
||||
<string name="popular_today">Popular hoje</string>
|
||||
@@ -674,31 +670,31 @@
|
||||
<string name="low_rating">Classificação baixa</string>
|
||||
<string name="original_language">Idioma original</string>
|
||||
<string name="year">Ano</string>
|
||||
<string name="source_code">Códico fonte</string>
|
||||
<string name="user_manual">Manual do Usuário</string>
|
||||
<string name="source_code">Código fonte</string>
|
||||
<string name="user_manual">Manual do usuário</string>
|
||||
<string name="telegram_group">Grupo do Telegram</string>
|
||||
<string name="skip_all">Pular Todos</string>
|
||||
<string name="skip_all">Pular todas</string>
|
||||
<string name="by_date">Data</string>
|
||||
<string name="popularity">Popularidade</string>
|
||||
<string name="popular_in_month">Popular neste mês</string>
|
||||
<string name="sort_order_asc">Ascendente</string>
|
||||
<string name="years">Anos</string>
|
||||
<string name="any">Todos</string>
|
||||
<string name="any">Qualquer</string>
|
||||
<string name="filter_search_warning">Esta fonte não oferece suporte à pesquisa com filtros. Seus filtros foram limpos</string>
|
||||
<string name="start_download">Iniciar download</string>
|
||||
<string name="save_manga_confirm">Salvar o mangá selecionado? Isso pode consumir dados e armazenamento</string>
|
||||
<string name="save_manga">Salvar o mangá</string>
|
||||
<string name="start_download">Comecar a baixar</string>
|
||||
<string name="save_manga_confirm">Salvar a obra selecionada? Isso pode consumir dados e armazenamento</string>
|
||||
<string name="save_manga">Salvar a obra</string>
|
||||
<string name="genre">Gênero</string>
|
||||
<string name="error_not_image">Formato inválido: esperava imagem, mas obteve %s</string>
|
||||
<string name="error_not_image">Formato inválido: esperava-se imagem, mas obtivemos %s</string>
|
||||
<string name="invalid_proxy_configuration">Configuração de proxy inválida</string>
|
||||
<string name="error_image_format">Formato de imagem não suportado: %s</string>
|
||||
<string name="manga_with_downloaded_chapters">Mangá com capítulos baixados</string>
|
||||
<string name="manga_fix_prompt">Essa função encontrará fontes alternativas para o mangá selecionado. A tarefa levará algum tempo e será executada em segundo plano</string>
|
||||
<string name="manga_replaced">Mangá “%1$s” (%2$s) substituído por “%3$s” (%4$s)</string>
|
||||
<string name="manga_with_downloaded_chapters">Obras com capítulos baixados</string>
|
||||
<string name="manga_fix_prompt">Essa função encontrará fontes alternativas para a obra selecionada. A tarefa levará algum tempo e será executada em segundo plano</string>
|
||||
<string name="manga_replaced">Obra “%1$s” (%2$s) substituída por “%3$s” (%4$s)</string>
|
||||
<string name="no_alternatives_found">Não foi encontrado alternativas para “%s”</string>
|
||||
<string name="pages_saved">Páginas salvas</string>
|
||||
<string name="text_empty_holder_secondary_filtered">Não existem obras que correspondam aos filtros selecionados</string>
|
||||
<string name="plugin_incompatible_with_cause">Plugin error: %s\n Verifique se você está usando a versão mais recente do plug-in e do Kotatsu</string>
|
||||
<string name="plugin_incompatible_with_cause">Erro de plugin: %s\n· Certifique-se de que você está usando a versão mais recente do plugin e Kotatsu</string>
|
||||
<string name="sort_order_desc">Descendente</string>
|
||||
<string name="download_new_chapters">Baixar novos capítulos</string>
|
||||
<string name="retry">Retentar</string>
|
||||
@@ -707,17 +703,17 @@
|
||||
<string name="unstable_feature">Recurso instável</string>
|
||||
<string name="popular_in_hour">Popular no momento</string>
|
||||
<string name="updated_long_ago">Atualizado há muito tempo</string>
|
||||
<string name="downloads_background">Downloads em segundo plano</string>
|
||||
<string name="content_type_novel">Romance</string>
|
||||
<string name="downloads_background">Baixando em segundo plano</string>
|
||||
<string name="content_type_novel">Novel</string>
|
||||
<string name="content_type_manhua">Manhua</string>
|
||||
<string name="content_type_manhwa">Manhwa</string>
|
||||
<string name="seconds_short">%d s</string>
|
||||
<string name="minutes_seconds_short">%1$d m %2$d s</string>
|
||||
<string name="minutes_seconds_short">%1$d min %2$d s</string>
|
||||
<string name="unpopular">Impopular</string>
|
||||
<string name="stuck">Preso</string>
|
||||
<string name="not_in_favorites">Não está nos favoritos</string>
|
||||
<string name="fixing_manga">Corrigindo o mangá</string>
|
||||
<string name="fixed">Corrigido</string>
|
||||
<string name="not_in_favorites">Não está nas favoritas</string>
|
||||
<string name="fixing_manga">Corrigindo a obra</string>
|
||||
<string name="fixed">Corrigida</string>
|
||||
<string name="no_fix_required">Nenhuma correção necessária para \"%s\"</string>
|
||||
<string name="handle_links_summary">Lidar com links de mangá de aplicações externas (ex: navegador). Você talvez precise habilitar isso manualmente nas configurações da aplicação</string>
|
||||
<string name="demographic_kodomo">Kodomo</string>
|
||||
@@ -733,4 +729,24 @@
|
||||
<string name="max_backups_count">Número máximo de backups</string>
|
||||
<string name="delete_old_backups">Apagar backups antigos</string>
|
||||
<string name="delete_old_backups_summary">Automaticamente apagar backups antigos para liberar espaço</string>
|
||||
<string name="screen_orientation">Orientação de tela</string>
|
||||
<string name="portrait">Portrato</string>
|
||||
<string name="landscape">Paisagem</string>
|
||||
<string name="content_type_game_cg">Jogo CG</string>
|
||||
<string name="captcha_required_message">Essa fonte requisita que você resolva um captcha para continuar</string>
|
||||
<string name="content_type_image_set">Imagens</string>
|
||||
<string name="content_type_doujinshi">Doujinshi</string>
|
||||
<string name="debug">Depurar (para desenvolvedores)</string>
|
||||
<string name="content_type_artist_cg">Artista CG</string>
|
||||
<string name="more_options">Mais opções</string>
|
||||
<string name="download_added">Baixando</string>
|
||||
<string name="chapter_selection_hint">Você pode selecionar capítulos para baixar pressionando-os na lista de capítulos.</string>
|
||||
<string name="destination_directory">Diretório de destinação</string>
|
||||
<string name="chapters_all">Todos</string>
|
||||
<string name="dont_allow">Não permitir</string>
|
||||
<string name="allow_always">Permitir sempre</string>
|
||||
<string name="allow_once">Permitir uma vez</string>
|
||||
<string name="ask_every_time">Sempre pergunte</string>
|
||||
<string name="download_over_cellular">Baixando pelos dados móveis</string>
|
||||
<string name="download_cellular_confirm">Permitir baixar pelos dados móveis?</string>
|
||||
</resources>
|
||||
@@ -752,4 +752,5 @@
|
||||
<string name="handle_links_summary">Руковање манга везама из спољних апликација (нпр. из прегледача). Можда ћеш морати да је омогућиш и ручно у системским поставкама апликације</string>
|
||||
<string name="handle_links">Руковање везама</string>
|
||||
<string name="email">Е-пошта</string>
|
||||
<string name="captcha_required_message">Овај извор захтева решавање CAPTCHA за наставак</string>
|
||||
</resources>
|
||||
@@ -752,4 +752,5 @@
|
||||
<string name="email">E-posta</string>
|
||||
<string name="handle_links">Bağlantıları aç</string>
|
||||
<string name="handle_links_summary">Harici uygulamalardaki (örn. web tarayıcısı) manga bağlantılarını uygulamada açın. Uygulamanın sistem ayarlarından bunu aktifleştirmeniz gerekebilir</string>
|
||||
<string name="captcha_required_message">Bu kaynağın kullanılabilmesi için bir captcha çözülmesi gerekiyor</string>
|
||||
</resources>
|
||||
@@ -621,7 +621,7 @@
|
||||
<string name="show_updated">显示更新</string>
|
||||
<string name="webtoon_gaps_summary">在条漫模式下添加页与页之间的横向缝隙</string>
|
||||
<string name="webtoon_gaps">缝隙条漫模式</string>
|
||||
<string name="pin_navigation_ui">置顶导航 UI</string>
|
||||
<string name="pin_navigation_ui">固定导航 UI</string>
|
||||
<string name="frequency_of_check">自动检查更新频率</string>
|
||||
<string name="new_chapters_pattern">%1$s: %2$d</string>
|
||||
<string name="pin_navigation_ui_summary">当上下滑动时不隐藏导航栏和搜索框</string>
|
||||
@@ -752,4 +752,5 @@
|
||||
<string name="handle_links">处理链接</string>
|
||||
<string name="handle_links_summary">处理来自外部程序的漫画链接(如 Web 浏览器), 可能需要在程序的系统设置中手动开启</string>
|
||||
<string name="email">电子邮箱</string>
|
||||
<string name="captcha_required_message">此图源需要通过验证以继续操作</string>
|
||||
</resources>
|
||||
@@ -6,4 +6,5 @@
|
||||
<bool name="com_samsung_android_icon_container_has_icon_container">true</bool>
|
||||
<bool name="is_color_themes_available">false</bool>
|
||||
<bool name="is_sync_enabled">true</bool>
|
||||
<bool name="is_predictive_back_enabled">true</bool>
|
||||
</resources>
|
||||
|
||||
@@ -769,4 +769,5 @@
|
||||
<string name="handle_links_summary">Handle manga links from external applications (e.g. web browser). You may also need to enable it manually in the application\'s system settings</string>
|
||||
<string name="email">Email</string>
|
||||
<string name="captcha_required_message">This source requires solving a captcha to continue</string>
|
||||
<string name="error_connection_reset">Connection reset by remote host</string>
|
||||
</resources>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
android:summary="@string/restore_summary"
|
||||
android:title="@string/restore_backup" />
|
||||
|
||||
<Preference
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.backup.PeriodicalBackupSettingsFragment"
|
||||
android:key="backup_periodic"
|
||||
android:persistent="false"
|
||||
|
||||
5
app/src/release/res/values/bools.xml
Normal file
5
app/src/release/res/values/bools.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Disable predictive back due to crashes -->
|
||||
<bool name="is_predictive_back_enabled">false</bool>
|
||||
</resources>
|
||||
@@ -7,7 +7,8 @@ avifDecoder = "1.1.1.14d8e3c4"
|
||||
biometric = "1.2.0-alpha05"
|
||||
coil = "3.0.4"
|
||||
collections = "1.4.5"
|
||||
conscrypt = "2.5.3"
|
||||
#noinspection GradleDependency - 2.5.3 cause crashes
|
||||
conscrypt = "2.5.2"
|
||||
constraintlayout = "2.2.0"
|
||||
coreKtx = "1.15.0"
|
||||
coroutines = "1.9.0"
|
||||
@@ -27,10 +28,10 @@ leakcanary = "3.0-alpha-8"
|
||||
lifecycle = "2.8.7"
|
||||
markwon = "4.6.2"
|
||||
material = "1.12.0"
|
||||
moshi = "1.15.1"
|
||||
moshi = "1.15.2"
|
||||
okhttp = "4.12.0"
|
||||
okio = "3.9.1"
|
||||
parsers = "1.5"
|
||||
parsers = "8481fadbd0"
|
||||
preference = "1.2.1"
|
||||
recyclerview = "1.3.2"
|
||||
room = "2.6.1"
|
||||
|
||||
Reference in New Issue
Block a user