Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec048c70f1 | ||
|
|
282c1b51f7 | ||
|
|
d6b6ce1bcd | ||
|
|
f48444dcf6 | ||
|
|
15ba766643 | ||
|
|
a0dbbcb350 | ||
|
|
f72bba9557 | ||
|
|
207791aa3e | ||
|
|
6319997716 | ||
|
|
b70c1da54b | ||
|
|
621cb19c5b | ||
|
|
b528b7b3c1 | ||
|
|
9a1bb6f6fc | ||
|
|
37f9c4b9f6 | ||
|
|
d0084e50e7 | ||
|
|
088576cc9d | ||
|
|
f0ba42b518 |
@@ -19,8 +19,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 1022
|
||||
versionName = '9.0'
|
||||
versionCode = 1023
|
||||
versionName = '9.0.1'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||
ksp {
|
||||
|
||||
@@ -404,6 +404,13 @@
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
|
||||
<receiver
|
||||
android:name="org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler$DiscardReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="org.koitharu.kotatsu.CAPTCHA_DISCARD" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider"
|
||||
android:exported="true"
|
||||
|
||||
@@ -30,21 +30,19 @@ constructor(
|
||||
oldManga: Manga,
|
||||
newManga: Manga,
|
||||
) {
|
||||
val oldDetails =
|
||||
if (oldManga.chapters.isNullOrEmpty()) {
|
||||
runCatchingCancellable {
|
||||
mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga)
|
||||
}.getOrDefault(oldManga)
|
||||
} else {
|
||||
oldManga
|
||||
}
|
||||
val newDetails =
|
||||
if (newManga.chapters.isNullOrEmpty()) {
|
||||
mangaRepositoryFactory.create(newManga.source).getDetails(newManga)
|
||||
} else {
|
||||
newManga
|
||||
}
|
||||
mangaDataRepository.storeManga(newDetails)
|
||||
val oldDetails = if (oldManga.chapters.isNullOrEmpty()) {
|
||||
runCatchingCancellable {
|
||||
mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga)
|
||||
}.getOrDefault(oldManga)
|
||||
} else {
|
||||
oldManga
|
||||
}
|
||||
val newDetails = if (newManga.chapters.isNullOrEmpty()) {
|
||||
mangaRepositoryFactory.create(newManga.source).getDetails(newManga)
|
||||
} else {
|
||||
newManga
|
||||
}
|
||||
mangaDataRepository.storeManga(newDetails, replaceExisting = true)
|
||||
database.withTransaction {
|
||||
// replace favorites
|
||||
val favoritesDao = database.getFavouritesDao()
|
||||
@@ -101,11 +99,11 @@ constructor(
|
||||
mangaId = newDetails.id,
|
||||
rating = prevInfo.rating,
|
||||
status =
|
||||
prevInfo.status ?: when {
|
||||
newHistory == null -> ScrobblingStatus.PLANNED
|
||||
newHistory.percent == 1f -> ScrobblingStatus.COMPLETED
|
||||
else -> ScrobblingStatus.READING
|
||||
},
|
||||
prevInfo.status ?: when {
|
||||
newHistory == null -> ScrobblingStatus.PLANNED
|
||||
newHistory.percent == 1f -> ScrobblingStatus.COMPLETED
|
||||
else -> ScrobblingStatus.READING
|
||||
},
|
||||
comment = prevInfo.comment,
|
||||
)
|
||||
if (newHistory != null) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.backups.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
@@ -27,24 +28,13 @@ abstract class BaseBackupRestoreService : CoroutineIntentService() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||
createNotificationChannel()
|
||||
createNotificationChannel(this)
|
||||
}
|
||||
|
||||
override fun IntentJobContext.onError(error: Throwable) {
|
||||
showResultNotification(null, CompositeResult.failure(error))
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_HIGH)
|
||||
.setName(getString(R.string.backup_restore))
|
||||
.setShowBadge(true)
|
||||
.setVibrationEnabled(false)
|
||||
.setSound(null, null)
|
||||
.setLightsEnabled(false)
|
||||
.build()
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
protected fun IntentJobContext.showResultNotification(
|
||||
fileUri: Uri?,
|
||||
result: CompositeResult,
|
||||
@@ -128,8 +118,19 @@ abstract class BaseBackupRestoreService : CoroutineIntentService() {
|
||||
.setBigContentTitle(title),
|
||||
)
|
||||
|
||||
protected companion object {
|
||||
companion object {
|
||||
|
||||
const val CHANNEL_ID = "backup_restore"
|
||||
|
||||
fun createNotificationChannel(context: Context) {
|
||||
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_HIGH)
|
||||
.setName(context.getString(R.string.backup_restore))
|
||||
.setShowBadge(true)
|
||||
.setVibrationEnabled(false)
|
||||
.setSound(null, null)
|
||||
.setLightsEnabled(false)
|
||||
.build()
|
||||
NotificationManagerCompat.from(context).createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
package org.koitharu.kotatsu.backups.ui.periodical
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.backups.data.BackupRepository
|
||||
import org.koitharu.kotatsu.backups.domain.BackupUtils
|
||||
import org.koitharu.kotatsu.backups.domain.ExternalBackupStorage
|
||||
import org.koitharu.kotatsu.backups.ui.BaseBackupRestoreService
|
||||
import org.koitharu.kotatsu.core.ErrorReporterReceiver
|
||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import java.util.zip.ZipOutputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -48,5 +57,49 @@ class PeriodicalBackupService : CoroutineIntentService() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun IntentJobContext.onError(error: Throwable) = Unit
|
||||
override fun IntentJobContext.onError(error: Throwable) {
|
||||
if (!applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||
return
|
||||
}
|
||||
BaseBackupRestoreService.createNotificationChannel(applicationContext)
|
||||
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setDefaults(0)
|
||||
.setSilent(true)
|
||||
.setAutoCancel(true)
|
||||
val title = getString(R.string.periodic_backups)
|
||||
val message = getString(
|
||||
R.string.inline_preference_pattern,
|
||||
getString(R.string.packup_creation_failed),
|
||||
error.getDisplayMessage(resources),
|
||||
)
|
||||
notification
|
||||
.setContentText(message)
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(message)
|
||||
.setSummaryText(getString(R.string.packup_creation_failed))
|
||||
.setBigContentTitle(title),
|
||||
)
|
||||
ErrorReporterReceiver.getNotificationAction(applicationContext, error, startId, TAG)?.let { action ->
|
||||
notification.addAction(action)
|
||||
}
|
||||
notification.setContentIntent(
|
||||
PendingIntentCompat.getActivity(
|
||||
applicationContext,
|
||||
0,
|
||||
AppRouter.periodicBackupSettingsIntent(applicationContext),
|
||||
0,
|
||||
false,
|
||||
),
|
||||
)
|
||||
NotificationManagerCompat.from(applicationContext).notify(TAG, startId, notification.build())
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
const val CHANNEL_ID = BaseBackupRestoreService.CHANNEL_ID
|
||||
const val TAG = "periodical_backup"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Bundle
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.Preference
|
||||
@@ -84,6 +85,13 @@ class PeriodicalBackupSettingsFragment : BasePreferenceFragment(R.string.periodi
|
||||
"" -> null
|
||||
else -> path
|
||||
}
|
||||
preference.icon = if (path == null) {
|
||||
ContextCompat.getDrawable(preference.context, R.drawable.ic_alert_outline)?.also {
|
||||
it.setTint(ContextCompat.getColor(preference.context, R.color.warning))
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindLastBackupInfo(lastBackupDate: Date?) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.app.Notification
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.annotation.RequiresPermission
|
||||
@@ -14,7 +13,6 @@ import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import coil3.EventListener
|
||||
@@ -26,6 +24,7 @@ import coil3.request.allowConversionToBitmap
|
||||
import coil3.request.allowHardware
|
||||
import coil3.request.lifecycle
|
||||
import coil3.size.Scale
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
@@ -70,22 +69,6 @@ class CaptchaHandler @Inject constructor(
|
||||
private val exceptionMap = MutableScatterMap<MangaSource, CloudFlareProtectedException>()
|
||||
private val mutex = Mutex()
|
||||
|
||||
init {
|
||||
ContextCompat.registerReceiver(
|
||||
context,
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val sourceName = intent?.getStringExtra(AppRouter.KEY_SOURCE) ?: return
|
||||
goAsync {
|
||||
handleException(MangaSource(sourceName), exception = null, notify = false)
|
||||
}
|
||||
}
|
||||
},
|
||||
IntentFilter().apply { addAction(ACTION_DISCARD) },
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun handle(exception: CloudFlareException): Boolean = handleException(exception.source, exception, true)
|
||||
|
||||
suspend fun discard(source: MangaSource) {
|
||||
@@ -251,6 +234,20 @@ class CaptchaHandler @Inject constructor(
|
||||
it.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DiscardReceiver : BroadcastReceiver() {
|
||||
|
||||
@Inject
|
||||
lateinit var captchaHandler: CaptchaHandler
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val sourceName = intent?.getStringExtra(AppRouter.KEY_SOURCE) ?: return
|
||||
goAsync {
|
||||
captchaHandler.handleException(MangaSource(sourceName), exception = null, notify = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun ImageRequest.Builder.ignoreCaptchaErrors() = apply {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.image
|
||||
import coil3.intercept.Interceptor
|
||||
import coil3.network.httpHeaders
|
||||
import coil3.request.ImageResult
|
||||
import org.koitharu.kotatsu.core.model.unwrap
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceKey
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
@@ -10,7 +11,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
class MangaSourceHeaderInterceptor : Interceptor {
|
||||
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
val mangaSource = chain.request.extras[mangaSourceKey] as? MangaParserSource ?: return chain.proceed()
|
||||
val mangaSource = chain.request.extras[mangaSourceKey]?.unwrap() as? MangaParserSource ?: return chain.proceed()
|
||||
val request = chain.request
|
||||
val newHeaders = request.httpHeaders.newBuilder()
|
||||
.set(CommonHeaders.MANGA_SOURCE, mangaSource.name)
|
||||
|
||||
@@ -741,6 +741,10 @@ class AppRouter private constructor(
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_TRACKER)
|
||||
|
||||
fun periodicBackupSettingsIntent(context: Context) =
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_PERIODIC_BACKUP)
|
||||
|
||||
fun proxySettingsIntent(context: Context) =
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_PROXY)
|
||||
@@ -825,6 +829,7 @@ class AppRouter private constructor(
|
||||
const val ACTION_SOURCES = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES"
|
||||
const val ACTION_SUGGESTIONS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS"
|
||||
const val ACTION_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER"
|
||||
const val ACTION_PERIODIC_BACKUP = "${BuildConfig.APPLICATION_ID}.action.MANAGE_PERIODIC_BACKUP"
|
||||
|
||||
private const val ACCOUNT_KEY = "account"
|
||||
private const val ACTION_ACCOUNT_SYNC_SETTINGS = "android.settings.ACCOUNT_SYNC_SETTINGS"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.core.network
|
||||
|
||||
import android.util.Log
|
||||
import dagger.Lazy
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
@@ -36,7 +35,8 @@ class CommonHeadersInterceptor @Inject constructor(
|
||||
mangaRepositoryFactoryLazy.get().create(source) as? ParserMangaRepository
|
||||
} else {
|
||||
if (BuildConfig.DEBUG && source == null) {
|
||||
Log.w("Http", "Request without source tag: ${request.url}")
|
||||
IllegalArgumentException("Request without source tag: ${request.url}")
|
||||
.printStackTrace()
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ class AppShortcutManager @Inject constructor(
|
||||
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
|
||||
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
|
||||
)
|
||||
mangaRepository.storeManga(manga)
|
||||
mangaRepository.storeManga(manga, replaceExisting = true)
|
||||
val title = manga.title.ifEmpty {
|
||||
manga.altTitles.firstOrNull()
|
||||
}.ifNullOrEmpty {
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
|
||||
// https://stackoverflow.com/questions/77555641/saf-no-activity-found-to-handle-intent-android-intent-action-open-document-tr
|
||||
class OpenDocumentTreeHelper(
|
||||
@@ -28,38 +27,42 @@ class OpenDocumentTreeHelper(
|
||||
callback,
|
||||
)
|
||||
|
||||
private val pickFileTreeLauncherQ = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
activityResultCaller.registerForActivityResult(OpenDocumentTreeContractQ(flags), callback)
|
||||
private val pickFileTreeLauncherPrimaryStorage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
activityResultCaller.registerForActivityResult(OpenDocumentTreeContractPrimaryStorage(flags), callback)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
private val pickFileTreeLauncherLegacy = activityResultCaller.registerForActivityResult(
|
||||
contract = OpenDocumentTreeContractLegacy(flags),
|
||||
private val pickFileTreeLauncherDefault = activityResultCaller.registerForActivityResult(
|
||||
contract = OpenDocumentTreeContractDefault(flags),
|
||||
callback = callback,
|
||||
)
|
||||
|
||||
override fun launch(input: Uri?, options: ActivityOptionsCompat?) {
|
||||
if (pickFileTreeLauncherQ == null) {
|
||||
pickFileTreeLauncherLegacy.launch(input, options)
|
||||
return
|
||||
}
|
||||
try {
|
||||
pickFileTreeLauncherQ.launch(input, options)
|
||||
pickFileTreeLauncherDefault.launch(input, options)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTraceDebug()
|
||||
pickFileTreeLauncherLegacy.launch(input, options)
|
||||
if (pickFileTreeLauncherPrimaryStorage != null) {
|
||||
try {
|
||||
pickFileTreeLauncherPrimaryStorage.launch(input, options)
|
||||
} catch (e2: Exception) {
|
||||
e.addSuppressed(e2)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
pickFileTreeLauncherQ?.unregister()
|
||||
pickFileTreeLauncherLegacy.unregister()
|
||||
pickFileTreeLauncherPrimaryStorage?.unregister()
|
||||
pickFileTreeLauncherDefault.unregister()
|
||||
}
|
||||
|
||||
override val contract: ActivityResultContract<Uri?, *>
|
||||
get() = pickFileTreeLauncherQ?.contract ?: pickFileTreeLauncherLegacy.contract
|
||||
get() = pickFileTreeLauncherPrimaryStorage?.contract ?: pickFileTreeLauncherDefault.contract
|
||||
|
||||
private open class OpenDocumentTreeContractLegacy(
|
||||
private open class OpenDocumentTreeContractDefault(
|
||||
private val flags: Int,
|
||||
) : ActivityResultContracts.OpenDocumentTree() {
|
||||
|
||||
@@ -71,9 +74,9 @@ class OpenDocumentTreeHelper(
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private class OpenDocumentTreeContractQ(
|
||||
private class OpenDocumentTreeContractPrimaryStorage(
|
||||
private val flags: Int,
|
||||
) : OpenDocumentTreeContractLegacy(flags) {
|
||||
) : OpenDocumentTreeContractDefault(flags) {
|
||||
|
||||
override fun createIntent(context: Context, input: Uri?): Intent {
|
||||
val intent = (context.getSystemService(Context.STORAGE_SERVICE) as? StorageManager)
|
||||
|
||||
@@ -41,7 +41,7 @@ class MangaDataRepository @Inject constructor(
|
||||
|
||||
suspend fun saveReaderMode(manga: Manga, mode: ReaderMode) {
|
||||
db.withTransaction {
|
||||
storeManga(manga)
|
||||
storeManga(manga, replaceExisting = false)
|
||||
val entity = db.getPreferencesDao().find(manga.id) ?: newEntity(manga.id)
|
||||
db.getPreferencesDao().upsert(entity.copy(mode = mode.id))
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class MangaDataRepository @Inject constructor(
|
||||
|
||||
suspend fun saveColorFilter(manga: Manga, colorFilter: ReaderColorFilter?) {
|
||||
db.withTransaction {
|
||||
storeManga(manga)
|
||||
storeManga(manga, replaceExisting = false)
|
||||
val entity = db.getPreferencesDao().find(manga.id) ?: newEntity(manga.id)
|
||||
db.getPreferencesDao().upsert(
|
||||
entity.copy(
|
||||
@@ -87,10 +87,11 @@ class MangaDataRepository @Inject constructor(
|
||||
return map
|
||||
}
|
||||
|
||||
suspend fun setOverride(mangaId: Long, override: MangaOverride?) {
|
||||
suspend fun setOverride(manga: Manga, override: MangaOverride?) {
|
||||
db.withTransaction {
|
||||
storeManga(manga, replaceExisting = false)
|
||||
val dao = db.getPreferencesDao()
|
||||
val entity = dao.find(mangaId) ?: newEntity(mangaId)
|
||||
val entity = dao.find(manga.id) ?: newEntity(manga.id)
|
||||
dao.upsert(
|
||||
entity.copy(
|
||||
titleOverride = override?.title?.nullIfEmpty(),
|
||||
@@ -127,7 +128,10 @@ class MangaDataRepository @Inject constructor(
|
||||
else -> null
|
||||
}
|
||||
|
||||
suspend fun storeManga(manga: Manga) {
|
||||
suspend fun storeManga(manga: Manga, replaceExisting: Boolean) {
|
||||
if (!replaceExisting && db.getMangaDao().find(manga.id) != null) {
|
||||
return
|
||||
}
|
||||
db.withTransaction {
|
||||
// avoid storing local manga if remote one is already stored
|
||||
val existing = if (manga.isLocal) {
|
||||
@@ -185,7 +189,7 @@ class MangaDataRepository @Inject constructor(
|
||||
emitInitialState = emitInitialState,
|
||||
)
|
||||
|
||||
private suspend fun Manga.withCachedChaptersIfNeeded(flag: Boolean): Manga = if (flag && chapters.isNullOrEmpty()) {
|
||||
private suspend fun Manga.withCachedChaptersIfNeeded(flag: Boolean): Manga = if (flag && !isLocal && chapters.isNullOrEmpty()) {
|
||||
val cachedChapters = db.getChaptersDao().findAll(id)
|
||||
if (cachedChapters.isEmpty()) {
|
||||
this
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.util.ext.EventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import kotlin.coroutines.AbstractCoroutineContextElement
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
@@ -80,10 +81,15 @@ abstract class BaseViewModel : ViewModel() {
|
||||
|
||||
protected fun MutableStateFlow<Int>.decrement() = update { it - 1 }
|
||||
|
||||
private fun createErrorHandler() = CoroutineExceptionHandler { _, throwable ->
|
||||
private fun createErrorHandler() = CoroutineExceptionHandler { coroutineContext, throwable ->
|
||||
throwable.printStackTraceDebug()
|
||||
if (throwable !is CancellationException) {
|
||||
if (coroutineContext[SkipErrors.key] == null && throwable !is CancellationException) {
|
||||
errorEvent.call(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
protected object SkipErrors : AbstractCoroutineContextElement(Key) {
|
||||
|
||||
private object Key : CoroutineContext.Key<SkipErrors>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,14 @@ fun View.setOnContextClickListenerCompat(listener: OnContextClickListenerCompat)
|
||||
}
|
||||
}
|
||||
|
||||
fun View.setTooltipCompat(tooltip: CharSequence?) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
tooltipText = tooltip
|
||||
} else if (!isLongClickable) { // don't use TooltipCompat if has a LongClickListener
|
||||
TooltipCompat.setTooltipText(this, tooltip)
|
||||
}
|
||||
}
|
||||
|
||||
val Toolbar.menuView: ActionMenuView?
|
||||
get() {
|
||||
menu // to call ensureMenu()
|
||||
@@ -201,7 +209,7 @@ fun Chip.setProgressIcon() {
|
||||
fun View.setContentDescriptionAndTooltip(@StringRes resId: Int) {
|
||||
val text = resources.getString(resId)
|
||||
contentDescription = text
|
||||
TooltipCompat.setTooltipText(this, text)
|
||||
setTooltipCompat(text)
|
||||
}
|
||||
|
||||
fun View.getWindowBounds(): Rect {
|
||||
|
||||
@@ -58,17 +58,26 @@ class DetailsLoadUseCase @Inject constructor(
|
||||
isLoaded = false,
|
||||
),
|
||||
)
|
||||
val local = if (!manga.isLocal) {
|
||||
async {
|
||||
localMangaRepository.findSavedManga(manga)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
if (manga.isLocal) {
|
||||
val details = getDetails(manga, force)
|
||||
send(
|
||||
MangaDetails(
|
||||
manga = details,
|
||||
localManga = null,
|
||||
override = override,
|
||||
description = details.description?.parseAsHtml(withImages = false)?.trim(),
|
||||
isLoaded = true,
|
||||
),
|
||||
)
|
||||
return@channelFlow
|
||||
}
|
||||
val local = async {
|
||||
localMangaRepository.findSavedManga(manga)
|
||||
}
|
||||
if (!force && networkState.isOfflineOrRestricted()) {
|
||||
// try to avoid loading if has saved manga
|
||||
val localManga = local?.await()
|
||||
if (manga.isLocal || localManga != null) {
|
||||
val localManga = local.await()
|
||||
if (localManga != null) {
|
||||
send(
|
||||
MangaDetails(
|
||||
manga = manga,
|
||||
@@ -88,7 +97,7 @@ class DetailsLoadUseCase @Inject constructor(
|
||||
send(
|
||||
MangaDetails(
|
||||
manga = details,
|
||||
localManga = local?.peek(),
|
||||
localManga = local.peek(),
|
||||
override = override,
|
||||
description = details.description?.parseAsHtml(withImages = false)?.trim(),
|
||||
isLoaded = false,
|
||||
@@ -97,14 +106,14 @@ class DetailsLoadUseCase @Inject constructor(
|
||||
send(
|
||||
MangaDetails(
|
||||
manga = details,
|
||||
localManga = local?.await(),
|
||||
localManga = local.await(),
|
||||
override = override,
|
||||
description = details.description?.parseAsHtml(withImages = true)?.trim(),
|
||||
isLoaded = true,
|
||||
),
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
local?.await()?.manga?.also { localManga ->
|
||||
local.await()?.manga?.also { localManga ->
|
||||
send(
|
||||
MangaDetails(
|
||||
manga = localManga,
|
||||
|
||||
@@ -80,6 +80,7 @@ import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.parentView
|
||||
import org.koitharu.kotatsu.core.util.ext.setTooltipCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.start
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
||||
@@ -455,7 +456,7 @@ class DetailsActivity :
|
||||
textViewSourceLabel.isVisible = false
|
||||
} else {
|
||||
textViewSource.textAndVisible = manga.source.getTitle(this@DetailsActivity)
|
||||
TooltipCompat.setTooltipText(textViewSource, manga.source.getSummary(this@DetailsActivity))
|
||||
textViewSource.setTooltipCompat(manga.source.getSummary(this@DetailsActivity))
|
||||
textViewSourceLabel.isVisible = textViewSource.isVisible == true
|
||||
}
|
||||
val faviconPlaceholderFactory = FaviconDrawable.Factory(R.style.FaviconDrawable_Chip)
|
||||
|
||||
@@ -182,7 +182,7 @@ class DetailsViewModel @Inject constructor(
|
||||
|
||||
init {
|
||||
loadingJob = doLoad(force = false)
|
||||
launchJob(Dispatchers.Default) {
|
||||
launchJob(Dispatchers.Default + SkipErrors) {
|
||||
val manga = mangaDetails.firstOrNull { !it?.chapters.isNullOrEmpty() } ?: return@launchJob
|
||||
val h = history.firstOrNull()
|
||||
if (h != null) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package org.koitharu.kotatsu.details.ui.adapter
|
||||
|
||||
import android.graphics.Typeface
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList
|
||||
import org.koitharu.kotatsu.core.util.ext.setTooltipCompat
|
||||
import org.koitharu.kotatsu.databinding.ItemChapterGridBinding
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -23,7 +23,7 @@ fun chapterGridItemAD(
|
||||
bind { payloads ->
|
||||
if (payloads.isEmpty()) {
|
||||
binding.textViewTitle.text = item.chapter.numberString() ?: "?"
|
||||
TooltipCompat.setTooltipText(itemView, item.chapter.title)
|
||||
itemView.setTooltipCompat(item.chapter.title)
|
||||
}
|
||||
binding.imageViewNew.isVisible = item.isNew
|
||||
binding.imageViewCurrent.isVisible = item.isCurrent
|
||||
|
||||
@@ -537,7 +537,7 @@ class DownloadWorker @AssistedInject constructor(
|
||||
return
|
||||
}
|
||||
val requests = tasks.map { (manga, task) ->
|
||||
mangaDataRepository.storeManga(manga)
|
||||
mangaDataRepository.storeManga(manga, replaceExisting = true)
|
||||
OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
.setConstraints(createConstraints(task.allowMeteredNetwork))
|
||||
.addTag(TAG)
|
||||
|
||||
@@ -24,7 +24,7 @@ class RecoverMangaUseCase @Inject constructor(
|
||||
repository.getDetails(it)
|
||||
} ?: return@runCatchingCancellable null
|
||||
val merged = merge(manga, newManga)
|
||||
mangaDataRepository.storeManga(merged)
|
||||
mangaDataRepository.storeManga(merged, replaceExisting = true)
|
||||
merged
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.drawableStart
|
||||
import org.koitharu.kotatsu.core.util.ext.recyclerView
|
||||
import org.koitharu.kotatsu.core.util.ext.setProgressIcon
|
||||
import org.koitharu.kotatsu.core.util.ext.setTooltipCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding
|
||||
@@ -126,8 +127,7 @@ fun exploreSourceGridItemAD(
|
||||
|
||||
bind {
|
||||
val title = item.source.getTitle(context)
|
||||
TooltipCompat.setTooltipText(
|
||||
itemView,
|
||||
itemView.setTooltipCompat(
|
||||
buildSpannedString {
|
||||
bold {
|
||||
append(title)
|
||||
|
||||
@@ -118,7 +118,7 @@ class HistoryRepository @Inject constructor(
|
||||
}
|
||||
assert(manga.chapters != null)
|
||||
db.withTransaction {
|
||||
mangaRepository.storeManga(manga)
|
||||
mangaRepository.storeManga(manga, replaceExisting = true)
|
||||
val branch = manga.chapters?.findById(chapterId)?.branch
|
||||
db.getHistoryDao().upsert(
|
||||
HistoryEntity(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.setTooltipCompat
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -24,7 +24,7 @@ fun mangaGridItemAD(
|
||||
sizeResolver.attachToView(itemView, binding.textViewTitle, binding.progressView)
|
||||
|
||||
bind { payloads ->
|
||||
TooltipCompat.setTooltipText(itemView, item.getSummary(context))
|
||||
itemView.setTooltipCompat(item.getSummary(context))
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.progressView.setProgress(item.progress, PAYLOAD_PROGRESS_CHANGED in payloads)
|
||||
with(binding.iconsView) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.core.view.isVisible
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.setTooltipCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaListBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -20,7 +21,7 @@ fun mangaListItemAD(
|
||||
AdapterDelegateClickListenerAdapter(this, clickListener, MangaCompactListModel::manga).attach(itemView)
|
||||
|
||||
bind {
|
||||
TooltipCompat.setTooltipText(itemView, item.getSummary(context))
|
||||
itemView.setTooltipCompat(item.getSummary(context))
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.textViewSubtitle.textAndVisible = item.subtitle
|
||||
binding.imageViewCover.setImageAsync(item.coverUrl, item.manga)
|
||||
|
||||
@@ -97,7 +97,7 @@ class LocalMangaIndex @Inject constructor(
|
||||
}
|
||||
|
||||
private suspend fun upsert(manga: LocalManga) {
|
||||
mangaDataRepository.storeManga(manga.manga)
|
||||
mangaDataRepository.storeManga(manga.manga, replaceExisting = true)
|
||||
db.getLocalMangaIndexDao().upsert(manga.toEntity())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
package org.koitharu.kotatsu.local.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkManager
|
||||
@@ -12,6 +21,8 @@ import androidx.work.WorkerParameters
|
||||
import androidx.work.await
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
@@ -40,9 +51,63 @@ class LocalStorageCleanupWorker @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
val title = applicationContext.getString(R.string.local_storage_cleanup)
|
||||
val channel = NotificationChannelCompat.Builder(WORKER_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
|
||||
.setName(title)
|
||||
.setShowBadge(true)
|
||||
.setVibrationEnabled(false)
|
||||
.setSound(null, null)
|
||||
.setLightsEnabled(true)
|
||||
.build()
|
||||
NotificationManagerCompat.from(applicationContext).createNotificationChannel(channel)
|
||||
|
||||
val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentIntent(
|
||||
PendingIntentCompat.getActivity(
|
||||
applicationContext,
|
||||
0,
|
||||
AppRouter.suggestionsSettingsIntent(applicationContext),
|
||||
0,
|
||||
false,
|
||||
),
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setDefaults(0)
|
||||
.setOngoing(false)
|
||||
.setSilent(true)
|
||||
.setProgress(0, 0, true)
|
||||
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val actionIntent = PendingIntentCompat.getActivity(
|
||||
applicationContext, SETTINGS_ACTION_CODE,
|
||||
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
.putExtra(Settings.EXTRA_APP_PACKAGE, applicationContext.packageName)
|
||||
.putExtra(Settings.EXTRA_CHANNEL_ID, WORKER_CHANNEL_ID),
|
||||
0, false,
|
||||
)
|
||||
notification.addAction(
|
||||
R.drawable.ic_settings,
|
||||
applicationContext.getString(R.string.notifications_settings),
|
||||
actionIntent,
|
||||
)
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
ForegroundInfo(WORKER_NOTIFICATION_ID, notification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||
} else {
|
||||
ForegroundInfo(WORKER_NOTIFICATION_ID, notification.build())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "cleanup"
|
||||
private const val WORKER_CHANNEL_ID = "storage_cleanup"
|
||||
private const val WORKER_NOTIFICATION_ID = 32
|
||||
private const val SETTINGS_ACTION_CODE = 6
|
||||
|
||||
suspend fun enqueue(context: Context) {
|
||||
val request = OneTimeWorkRequestBuilder<LocalStorageCleanupWorker>()
|
||||
|
||||
@@ -72,7 +72,7 @@ class CoverRestoreInterceptor @Inject constructor(
|
||||
val repo = repositoryFactory.create(manga.source)
|
||||
val fixed = repo.find(manga) ?: return false
|
||||
return if (fixed != manga) {
|
||||
dataRepository.storeManga(fixed)
|
||||
dataRepository.storeManga(fixed, replaceExisting = true)
|
||||
fixed.coverUrl != manga.coverUrl
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -131,6 +131,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
onBackPressedDispatcher.addCallback(exitCallback)
|
||||
onBackPressedDispatcher.addCallback(navigationDelegate)
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
val legacySearchCallback = SearchViewLegacyBackCallback(viewBinding.searchView)
|
||||
viewBinding.searchView.addTransitionListener(legacySearchCallback)
|
||||
onBackPressedDispatcher.addCallback(legacySearchCallback)
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
onFirstStart()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import android.os.Build
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.DeprecatedSinceApi
|
||||
import com.google.android.material.search.SearchView
|
||||
|
||||
@DeprecatedSinceApi(Build.VERSION_CODES.TIRAMISU)
|
||||
class SearchViewLegacyBackCallback(
|
||||
private val searchView: SearchView
|
||||
) : OnBackPressedCallback(searchView.isShowing), SearchView.TransitionListener {
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
searchView.hide()
|
||||
}
|
||||
|
||||
override fun onStateChanged(
|
||||
searchView: SearchView,
|
||||
previousState: SearchView.TransitionState,
|
||||
newState: SearchView.TransitionState
|
||||
) {
|
||||
isEnabled = newState >= SearchView.TransitionState.SHOWING
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.prefs.ReaderControl
|
||||
import org.koitharu.kotatsu.core.util.ext.hasVisibleChildren
|
||||
import org.koitharu.kotatsu.core.util.ext.isRtl
|
||||
import org.koitharu.kotatsu.core.util.ext.setContentDescriptionAndTooltip
|
||||
import org.koitharu.kotatsu.core.util.ext.setTooltipCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.setValueRounded
|
||||
import org.koitharu.kotatsu.databinding.LayoutReaderActionsBinding
|
||||
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
|
||||
@@ -254,7 +255,7 @@ class ReaderActionsView @JvmOverloads constructor(
|
||||
private fun Button.initAction() {
|
||||
setOnClickListener(this@ReaderActionsView)
|
||||
setOnLongClickListener(this@ReaderActionsView)
|
||||
TooltipCompat.setTooltipText(this, contentDescription)
|
||||
setTooltipCompat(contentDescription)
|
||||
}
|
||||
|
||||
private fun isAutoRotationEnabled(): Boolean = Settings.System.getInt(
|
||||
|
||||
@@ -351,11 +351,11 @@ class ReaderActivity :
|
||||
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
|
||||
gestureInsets = insets.getInsets(WindowInsetsCompat.Type.systemGestures())
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
viewBinding.appbarTop.updatePadding(
|
||||
top = systemBars.top,
|
||||
right = systemBars.right,
|
||||
left = systemBars.left,
|
||||
)
|
||||
viewBinding.toolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = systemBars.top
|
||||
rightMargin = systemBars.right
|
||||
leftMargin = systemBars.left
|
||||
}
|
||||
if (viewBinding.toolbarDocked != null) {
|
||||
viewBinding.actionsView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = systemBars.bottom
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
||||
import org.koitharu.kotatsu.core.util.ext.setTooltipCompat
|
||||
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaGridBinding
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||
@@ -47,7 +48,7 @@ private fun searchSuggestionMangaGridAD(
|
||||
}
|
||||
|
||||
bind {
|
||||
TooltipCompat.setTooltipText(itemView, item.title)
|
||||
itemView.setTooltipCompat(item.title)
|
||||
binding.imageViewCover.setImageAsync(item.coverUrl, item.source)
|
||||
binding.textViewTitle.text = item.title
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
@@ -146,6 +147,7 @@ class SettingsActivity :
|
||||
AppRouter.ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
|
||||
AppRouter.ACTION_HISTORY -> UserDataSettingsFragment()
|
||||
AppRouter.ACTION_TRACKER -> TrackerSettingsFragment()
|
||||
AppRouter.ACTION_PERIODIC_BACKUP -> PeriodicalBackupSettingsFragment()
|
||||
AppRouter.ACTION_SOURCES -> SourcesSettingsFragment()
|
||||
AppRouter.ACTION_PROXY -> ProxySettingsFragment()
|
||||
AppRouter.ACTION_MANAGE_DOWNLOADS -> DownloadsSettingsFragment()
|
||||
|
||||
@@ -37,7 +37,7 @@ class OverrideConfigViewModel @Inject constructor(
|
||||
val override = checkNotNull(data.value).second.copy(
|
||||
title = title,
|
||||
)
|
||||
dataRepository.setOverride(manga.id, override)
|
||||
dataRepository.setOverride(manga, override)
|
||||
onSaved.call(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package org.koitharu.kotatsu.settings.storage.directories
|
||||
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.drawableStart
|
||||
import org.koitharu.kotatsu.core.util.ext.setTooltipCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemStorageConfigBinding
|
||||
import org.koitharu.kotatsu.settings.storage.DirectoryModel
|
||||
@@ -18,7 +18,7 @@ fun directoryConfigAD(
|
||||
) {
|
||||
|
||||
binding.buttonRemove.setOnClickListener { v -> clickListener.onItemClick(item, v) }
|
||||
TooltipCompat.setTooltipText(binding.buttonRemove, binding.buttonRemove.contentDescription)
|
||||
binding.buttonRemove.setTooltipCompat(binding.buttonRemove.contentDescription)
|
||||
|
||||
bind {
|
||||
binding.textViewTitle.text = item.title ?: getString(item.titleRes)
|
||||
|
||||
@@ -826,4 +826,13 @@
|
||||
<string name="adblock_summary">Блакаванне рэкламы ва ўбудаваным браўзэры (бэта)</string>
|
||||
<string name="changelog">Часопіс змен</string>
|
||||
<string name="changelog_summary">Гісторыя змен для нядаўна выпушчаных версій</string>
|
||||
<string name="reader_navigation_inverted">Інвертаваць элементы кіравання навігацыяй</string>
|
||||
<string name="reader_navigation_inverted_summary">Памяняць месцамі кірунак кнопкі рэгулявання гучнасці і апаратнай клавішы навігацыі (налева/уверх/уніз/направа)</string>
|
||||
<string name="creating_backup">Стварэнне рэзервовай копіі</string>
|
||||
<string name="share_backup">Падзяліцца рэзервовай копіяй</string>
|
||||
<string name="reader_multitask">Адчыняць чыталку як асобную задачу</string>
|
||||
<string name="reader_multitask_summary">Дазваляе трымаць адкрытымі некалькі чыталак з рознай мангой адначасова</string>
|
||||
<string name="theme_name_itsuka">Іцука</string>
|
||||
<string name="theme_name_totoro">Тоторо</string>
|
||||
<string name="book_effect">Жаўтлявы фон (фільтр сіняга)</string>
|
||||
</resources>
|
||||
|
||||
@@ -633,4 +633,22 @@
|
||||
<string name="recent_queries">Kürzliche Suchen</string>
|
||||
<string name="suggested_queries">Vorgeschlagene Suchen</string>
|
||||
<string name="authors">Autoren</string>
|
||||
<string name="nsfw_16">16+</string>
|
||||
<string name="blocked_by_server_message">Du wurdest vom Server blockiert. Versuche, eine andere Netzwerkverbindung zu benutzen (VPN, Proxy, etc.)</string>
|
||||
<string name="disable">Deaktivieren</string>
|
||||
<string name="sources_disabled">Quellen deaktiviert</string>
|
||||
<string name="disable_nsfw_notifications">Deaktiviere NSFW Benachrichtigungen</string>
|
||||
<string name="disable_nsfw_notifications_summary">Zeige keine Benachrichtigungen bezüglich NSFW Manga updates</string>
|
||||
<string name="_new">Neues</string>
|
||||
<string name="all_languages">Alle Sprachen</string>
|
||||
<string name="screenshots_block_incognito">Blockieren, wenn im Inkognito-Modus</string>
|
||||
<string name="image_server">Bevorzugter Server</string>
|
||||
<string name="pin">Pinnen</string>
|
||||
<string name="unpin">Entpinnen</string>
|
||||
<string name="source_pinned">Quelle gepinnt</string>
|
||||
<string name="percent_read">Prozent gelesen</string>
|
||||
<string name="percent_left">Prozent übrig</string>
|
||||
<string name="chapters_read">Kapitel gelesen</string>
|
||||
<string name="chapters_left">Kapitel übrig</string>
|
||||
<string name="plugin_incompatible">Inkompatibles Plugin oder Interner Fehler. Stelle sicher, dass du die letzte Version von Kotatsu und die des Plugins verwendest</string>
|
||||
</resources>
|
||||
|
||||
@@ -776,4 +776,12 @@
|
||||
<string name="dont_ask_again">No volver a preguntar</string>
|
||||
<string name="incognito_mode_hint_nsfw">Este manga puede contener contenido adulto. ¿Quieres usar el modo incógnito?</string>
|
||||
<string name="expand">Expandir</string>
|
||||
<string name="enable_all_sources_summary">Todas las fuentes de manga disponibles se habilitarán permanentemente</string>
|
||||
<string name="all_sources_enabled">Todas las fuentes están habilitadas</string>
|
||||
<string name="backup_restored_background">La copia de seguridad se restaurará en segundo plano</string>
|
||||
<string name="restoring_backup">Restaurando copia de seguridad</string>
|
||||
<string name="screen_rotation_locked">La rotación de pantalla ha sido bloqueada</string>
|
||||
<string name="screen_rotation_unlocked">La rotación de pantalla se ha desbloqueado</string>
|
||||
<string name="simple">Simple</string>
|
||||
<string name="global_search">Búsqueda global</string>
|
||||
</resources>
|
||||
|
||||
@@ -827,4 +827,8 @@
|
||||
<string name="creating_backup">Ginagawa ang backup</string>
|
||||
<string name="share_backup">I-share ang backup</string>
|
||||
<string name="reader_navigation_inverted">Baliktarin ang mga na kontrol sa nabigasyon</string>
|
||||
<string name="reader_navigation_inverted_summary">Baliktarin ang direksyon ng volume button at ang nabigasyon ng directional hardware key (kaliwa/tass/baba/kanan)</string>
|
||||
<string name="reader_multitask">Buksan ang reader sa isang hiwalay na gawain</string>
|
||||
<string name="reader_multitask_summary">Binibigyang-daan kang panatilihing bukas ang maraming reader na may magkakaibang manga nang sabay-sabay</string>
|
||||
<string name="book_effect">Mala-dilaw na background (asul na filter)</string>
|
||||
</resources>
|
||||
|
||||
@@ -830,4 +830,7 @@
|
||||
<string name="theme_name_itsuka">Itsuka</string>
|
||||
<string name="theme_name_totoro">Totoro</string>
|
||||
<string name="reader_multitask">Ouvrir le lecteur dans une tâche séparée</string>
|
||||
<string name="reader_navigation_inverted">Inverser les commandes de navigation</string>
|
||||
<string name="reader_navigation_inverted_summary">Inverser le sens des boutons de volume et des touches directionnelles matérielles (gauche/haut/bas/droite)</string>
|
||||
<string name="book_effect">Fond jaunâtre (filtre bleu)</string>
|
||||
</resources>
|
||||
|
||||
@@ -331,7 +331,7 @@
|
||||
<string name="clear_new_chapters_counters">Также очистить информацию о новых главах</string>
|
||||
<string name="server_error">Внутренняя ошибка сервера (%1$d). Повторите попытку позже</string>
|
||||
<string name="compact">Компактный</string>
|
||||
<string name="source_disabled">Источник отключен</string>
|
||||
<string name="source_disabled">Источник отключён</string>
|
||||
<string name="prefetch_content">Предварительная загрузка содержимого</string>
|
||||
<string name="mark_as_current">Пометить как текущую</string>
|
||||
<string name="language">Язык</string>
|
||||
@@ -492,7 +492,7 @@
|
||||
<string name="manage_sources">Управление источниками</string>
|
||||
<string name="no_manga_sources_found">Не найдено доступных источников по вашему запросу</string>
|
||||
<string name="manual">Вручную</string>
|
||||
<string name="source_enabled">Источник включен</string>
|
||||
<string name="source_enabled">Источник включён</string>
|
||||
<string name="disable_nsfw_summary">Отключить NSFW источники и скрывать мангу для взрослых в списках, если это возможно</string>
|
||||
<string name="no_manga_sources_catalog_text">В этом разделе нет доступных источников, или все они могли быть уже добавлены.
|
||||
\nСледите за обновлениями</string>
|
||||
@@ -788,7 +788,7 @@
|
||||
<string name="error_details">Подробности ошибки</string>
|
||||
<string name="error_disclaimer_app_outdated">Похоже, что Ваша версия Kotatsu устарела. Пожалуйста, установите последнюю версию, чтобы получить все доступные исправления.</string>
|
||||
<string name="error_disclaimer_report">Вы можете отправить отчёт об ошибке разработчикам. Это поможет нам исправить проблему.</string>
|
||||
<string name="search_disabled_sources">Поиск по отключенным источникам</string>
|
||||
<string name="search_disabled_sources">Поиск по отключённым источникам</string>
|
||||
<string name="chapter_volume_number">Том %1$s Глава %2$s</string>
|
||||
<string name="chapter_number">Глава %s</string>
|
||||
<string name="unnamed_chapter">Безымянная глава</string>
|
||||
@@ -833,4 +833,6 @@
|
||||
<string name="theme_name_itsuka">Ицука</string>
|
||||
<string name="theme_name_totoro">Тоторо</string>
|
||||
<string name="book_effect">Желтоватый фон (фильтр синего)</string>
|
||||
<string name="reader_navigation_inverted_summary">Поменять местами направление кнопки регулировки громкости и аппаратной клавиши навигации (влево/вверх/вниз/вправо)</string>
|
||||
<string name="reader_multitask_summary">Позволяет держать открытыми несколько читалок с разной мангой одновременно</string>
|
||||
</resources>
|
||||
|
||||
@@ -834,4 +834,5 @@
|
||||
<string name="theme_name_totoro">Totoro</string>
|
||||
<string name="reader_navigation_inverted">Navigasyon kontrollerini ters çevir</string>
|
||||
<string name="reader_navigation_inverted_summary">Ses tuşlarının yönlerini değiştir ve donanımsal yön tuşlarını yer değiştir (sol/yukarı/aşağı/sağ)</string>
|
||||
<string name="book_effect">Sarımsı arkaplan (mavi filtre)</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="delete">Видалити</string>
|
||||
<string name="nothing_found">Нічого не знайдено</string>
|
||||
<string name="add_to_favourites">Додати до улюблених</string>
|
||||
<string name="add_to_favourites">До улюблених</string>
|
||||
<string name="clear_history">Очистити історію</string>
|
||||
<string name="history_is_empty">Історії ще немає</string>
|
||||
<string name="add">Додати</string>
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="read">Читати</string>
|
||||
<string name="grid">Таблиця</string>
|
||||
<string name="share">Поділитися</string>
|
||||
<string name="create_shortcut">Створити ярлик…</string>
|
||||
<string name="create_shortcut">Створити ярлик</string>
|
||||
<string name="share_s">Поділитися %s</string>
|
||||
<string name="search">Пошук</string>
|
||||
<string name="search_manga">Пошук манґи</string>
|
||||
@@ -49,7 +49,7 @@
|
||||
<string name="remove">Видалити</string>
|
||||
<string name="_s_deleted_from_local_storage">«%s» видалено з локального сховища</string>
|
||||
<string name="save_page">Зберегти сторінку</string>
|
||||
<string name="page_saved">Збережено</string>
|
||||
<string name="page_saved">Сторінку збережено</string>
|
||||
<string name="share_image">Поділитись зображенням</string>
|
||||
<string name="operation_not_supported">Ця операція не підтримується</string>
|
||||
<string name="text_file_not_supported">Виберіть файл ZIP або CBZ.</string>
|
||||
@@ -315,7 +315,7 @@
|
||||
<string name="manga_error_description_pattern">Деталі помилки:<br><tt>%1$s</tt><br><br>1. Спробуйте <a href=%2$s>відкрити манґу у веб-браузері</a>, щоб переконатися, що вона доступна в джерелі<br>2. Переконайтеся, що ви використовуєте <a href=kotatsu://about>останню версію Kotatsu</a><br>3. Якщо він доступний, надішліть звіт про помилку розробникам.</string>
|
||||
<string name="history_shortcuts">Показувати ярлики останньої прочитаної манґи</string>
|
||||
<string name="history_shortcuts_summary">Зробити нещодавно прочитану манґу доступною за довгим натисканням на іконку застосунку</string>
|
||||
<string name="reader_control_ltr_summary">Натискання на правий край або натискання правої клавіші завжди перемикається на наступну сторінку.</string>
|
||||
<string name="reader_control_ltr_summary">Не налаштовуйте напрям перемикання сторінок у режим читання, наприклад, натискання правої клавіші завжди перемикається на наступну сторінку. Ця опція впливає лише на апаратні пристрої введення</string>
|
||||
<string name="reader_control_ltr">Ергономічне керування режимом читання</string>
|
||||
<string name="brightness">Яскравість</string>
|
||||
<string name="color_correction">Корекція кольору</string>
|
||||
@@ -384,7 +384,7 @@
|
||||
<string name="suggestions_notifications_summary">Іноді показувати сповіщення з запропонованою манґою</string>
|
||||
<string name="more">Більше</string>
|
||||
<string name="cancel_all_downloads_confirm">Усі активні завантаження буде скасовано, частково завантажені дані буде втрачено</string>
|
||||
<string name="remove_completed_downloads_confirm">Ваша історія завантажень буде остаточно видалена</string>
|
||||
<string name="remove_completed_downloads_confirm">Вашу історію завантажень буде видалено назавжди. Завантажені файли не будуть порушені</string>
|
||||
<string name="text_downloads_list_holder">У вас немає завантажень</string>
|
||||
<string name="downloads_resumed">Завантаження відновлено</string>
|
||||
<string name="downloads_paused">Завантаження призупинено</string>
|
||||
@@ -661,4 +661,175 @@
|
||||
<string name="text_empty_holder_secondary_filtered">Немає манґи, що відповідає вибраним вами фільтрам</string>
|
||||
<string name="connection_ok">З\'єднання в порядку</string>
|
||||
<string name="invalid_proxy_configuration">Неправильна конфігурація проксі</string>
|
||||
<string name="retry">Повторити</string>
|
||||
<string name="pages_saved">Сторінки збережені</string>
|
||||
<string name="nsfw_16">16+</string>
|
||||
<string name="invalid_server_address_message">Невірна адреса сервера</string>
|
||||
<string name="theme_name_expressive">Виразний (Тест)</string>
|
||||
<string name="too_many_requests_message_retry">Забагато запитів. Спробуйте ще раз через %s</string>
|
||||
<string name="reader_navigation_inverted">Інвертувати елементи керування навігацією</string>
|
||||
<string name="reader_navigation_inverted_summary">Поміняти місцями напрямок кнопки регулювання гучності та апаратної клавіші навігації (вліво/вгору/вниз/вправо)</string>
|
||||
<string name="seconds_short">%d с</string>
|
||||
<string name="minutes_seconds_short">%1$d хв %2$d с</string>
|
||||
<string name="plugin_incompatible_with_cause">Помилка плагіна: %s\nПереконайтеся, що ви використовуєте останню версію плагіна та Kotatsu</string>
|
||||
<string name="show_quick_filters">Показати швидкі фільтри</string>
|
||||
<string name="show_quick_filters_summary">Надає можливість фільтрувати списки манги за певними параметрами</string>
|
||||
<string name="sfw">SFW</string>
|
||||
<string name="skip_all">Пропустити все</string>
|
||||
<string name="stuck">Зависло</string>
|
||||
<string name="not_in_favorites">Немає в обраних</string>
|
||||
<string name="updated_long_ago">Оновлено давно</string>
|
||||
<string name="unpopular">Непопулярний</string>
|
||||
<string name="low_rating">Низький рейтинг</string>
|
||||
<string name="sort_order_asc">За зростанням</string>
|
||||
<string name="sort_order_desc">За спаданням</string>
|
||||
<string name="by_date">Дата</string>
|
||||
<string name="popularity">Популярність</string>
|
||||
<string name="scrobbler_auth_required">Увійдіть у %s, щоб продовжити</string>
|
||||
<string name="scrobbler_auth_intro">Увійдіть, щоб настроїти інтеграцію з %s. Це дозволить вам відстежувати прогрес та статус читання манги</string>
|
||||
<string name="unstable_feature">Нестабільна функція</string>
|
||||
<string name="unstable_feature_summary">Ця функція є експериментальною. Переконайтеся, що у вас є резервна копія, щоб уникнути втрати даних</string>
|
||||
<string name="downloads_background">Фонові завантаження</string>
|
||||
<string name="download_new_chapters">Завантажити нові розділи</string>
|
||||
<string name="manga_with_downloaded_chapters">Манга із завантаженими розділами</string>
|
||||
<string name="manga_replaced">Манґу «%1$s» (%2$s) замінено на «%3$s» (%4$s)</string>
|
||||
<string name="fixing_manga">Виправлення манґи</string>
|
||||
<string name="fixed">Виправлено успішно</string>
|
||||
<string name="no_fix_required">Виправлення для «%s» не потрібно</string>
|
||||
<string name="no_alternatives_found">Альтернативи для «%s» не знайдені</string>
|
||||
<string name="manga_fix_prompt">Ця функція знайде альтернативні джерела обраної манги. Завдання займе деякий час і виконуватиметься у фоновому режимі</string>
|
||||
<string name="content_type_novel">Новела</string>
|
||||
<string name="content_type_manhua">Маньхуа</string>
|
||||
<string name="content_type_manhwa">Манхва</string>
|
||||
<string name="recently_added">Нещодавно додано</string>
|
||||
<string name="added_long_ago">Додано давно</string>
|
||||
<string name="popular_in_hour">Популярне в цю годину</string>
|
||||
<string name="popular_today">Популярно сьогодні</string>
|
||||
<string name="popular_in_week">Популярно цього тижня</string>
|
||||
<string name="popular_in_month">Популярно цього місяця</string>
|
||||
<string name="popular_in_year">Популярно цього року</string>
|
||||
<string name="original_language">Вихідна мова</string>
|
||||
<string name="year">Рік</string>
|
||||
<string name="demographics">Демографія</string>
|
||||
<string name="demographic_shounen">Шьонен</string>
|
||||
<string name="demographic_shoujo">Шьоджьо</string>
|
||||
<string name="demographic_seinen">Сейнен</string>
|
||||
<string name="demographic_josei">Джьосей</string>
|
||||
<string name="years">Роки</string>
|
||||
<string name="any">Будь-який</string>
|
||||
<string name="filter_search_warning">Це джерело не підтримує пошук з фільтрами. Ваші фільтри були очищені</string>
|
||||
<string name="demographic_kodomo">Кодомо</string>
|
||||
<string name="content_type_one_shot">Ваншот</string>
|
||||
<string name="content_type_doujinshi">Додзінсі</string>
|
||||
<string name="content_type_image_set">Набір зображень</string>
|
||||
<string name="content_type_artist_cg">Художник CG</string>
|
||||
<string name="content_type_game_cg">Ігрова CG</string>
|
||||
<string name="debug">Налагодження</string>
|
||||
<string name="source_code">Вихідний код</string>
|
||||
<string name="user_manual">Посібник користувача</string>
|
||||
<string name="telegram_group">Група у Telegram</string>
|
||||
<string name="error_image_format">Формат зображення, що не підтримується: %s</string>
|
||||
<string name="error_not_image">Невірний формат: очікувалося зображення, але отримано %s</string>
|
||||
<string name="start_download">Почати завантаження</string>
|
||||
<string name="save_manga_confirm">Зберегти вибрану мангу? Це може зайняти трафік та місце на диску</string>
|
||||
<string name="save_manga">Зберегти манґу</string>
|
||||
<string name="genre">Жанр</string>
|
||||
<string name="download_added">Завантаження додано</string>
|
||||
<string name="more_options">Більше опцій</string>
|
||||
<string name="destination_directory">Каталог призначення</string>
|
||||
<string name="chapter_selection_hint">Ви можете вибрати розділи для завантаження, утримуючи елемент у списку розділів.</string>
|
||||
<string name="chapters_all">Усі</string>
|
||||
<string name="download_over_cellular">Завантаження через мобільну мережу</string>
|
||||
<string name="download_cellular_confirm">Дозволити завантаження через мобільну мережу?</string>
|
||||
<string name="dont_allow">Не дозволяти</string>
|
||||
<string name="allow_always">Дозволити завжди</string>
|
||||
<string name="allow_once">Дозволити один раз</string>
|
||||
<string name="ask_every_time">Запитувати щоразу</string>
|
||||
<string name="screen_orientation">Орієнтація екрану</string>
|
||||
<string name="portrait">Портретна</string>
|
||||
<string name="landscape">Ландшафтна</string>
|
||||
<string name="access_denied_403">Доступ був відхилений (403)</string>
|
||||
<string name="max_backups_count">Максимальна кількість резервних копій</string>
|
||||
<string name="delete_old_backups">Видалити старі резервні копії</string>
|
||||
<string name="delete_old_backups_summary">Автоматично видаляти старі резервні копії, щоб звільнити місце для даних</string>
|
||||
<string name="handle_links">Обробка посилань</string>
|
||||
<string name="email">Електронна пошта</string>
|
||||
<string name="captcha_required_message">Це джерело вимагає рішення капчі, щоб продовжити</string>
|
||||
<string name="author">Автор</string>
|
||||
<string name="rating">Рейтинг</string>
|
||||
<string name="source">Джерело</string>
|
||||
<string name="translation">Переклад</string>
|
||||
<string name="show_slider">Показати слайдер</string>
|
||||
<string name="incognito">Інкогніто</string>
|
||||
<string name="error_connection_reset">Скидання з\'єднання віддаленим хостом</string>
|
||||
<string name="backup_tg_check">Перевірте, чи працює API</string>
|
||||
<string name="backup_tg_echo">Тестове повідомлення</string>
|
||||
<string name="backup_tg_id_not_set">ID чату не встановлено</string>
|
||||
<string name="telegram_chat_id">ID чату Telegram</string>
|
||||
<string name="open_telegram_bot">Відкрийте Telegram-бота</string>
|
||||
<string name="send_backups_telegram">Надсилати резервні копії до Telegram</string>
|
||||
<string name="test_connection">Тестове з\'єднання</string>
|
||||
<string name="telegram_chat_id_summary">Введіть ID чату, куди слід надсилати резервні копії</string>
|
||||
<string name="open_telegram_bot_summary">Натисніть, щоб відкрити чат з Kotatsu Backup Bot</string>
|
||||
<string name="clear_database">Очистити базу даних</string>
|
||||
<string name="clear_database_summary">Видалити інформацію про мангу, яка не використовується</string>
|
||||
<string name="enable_all_sources">Включити всі джерела манги</string>
|
||||
<string name="enable_all_sources_summary">Усі доступні джерела манги будуть включені назавжди</string>
|
||||
<string name="all_sources_enabled">Усі джерела включені</string>
|
||||
<string name="reader_info_bar_transparent">Прозора інформаційна панель читання</string>
|
||||
<string name="backup_restored_background">Резервну копію буде відновлено у фоновому режимі</string>
|
||||
<string name="restoring_backup">Відновлення резервної копії</string>
|
||||
<string name="reader_controls_in_bottom_bar">Елементи керування читанням на нижній панелі</string>
|
||||
<string name="chapters_and_pages">Розділи та сторінки</string>
|
||||
<string name="pages_slider">Повзунок перемикання сторінок</string>
|
||||
<string name="screen_rotation_locked">Поворот екрана було заблоковано</string>
|
||||
<string name="screen_rotation_unlocked">Поворот екрана було розблоковано</string>
|
||||
<string name="badges_in_lists">Піктограми у списках</string>
|
||||
<string name="search_everywhere">Пошук усюди</string>
|
||||
<string name="simple">Простий</string>
|
||||
<string name="global_search">Глобальний пошук</string>
|
||||
<string name="disable_captcha_notifications">Вимкнути сповіщення про CAPTCHA</string>
|
||||
<string name="chapter_volume_number">Том %1$s Розділ %2$s</string>
|
||||
<string name="chapter_number">Розділ %s</string>
|
||||
<string name="unnamed_chapter">Безіменний розділ</string>
|
||||
<string name="search_disabled_sources">Пошук за відключеними джерелами</string>
|
||||
<string name="error_details">Подробиці помилки</string>
|
||||
<string name="book_effect">Жовтий фон (фільтр синього)</string>
|
||||
<string name="theme_name_totoro">Тоторо</string>
|
||||
<string name="theme_name_itsuka">Іцука</string>
|
||||
<string name="reader_multitask_summary">Дозволяє тримати відкритими кілька читалок із різною мангою одночасно</string>
|
||||
<string name="reader_multitask">Відкривати читалку як окреме завдання</string>
|
||||
<string name="share_backup">Поділитись резервною копією</string>
|
||||
<string name="creating_backup">Створення резервної копії</string>
|
||||
<string name="collapse_long_description">Згорнути довгий опис</string>
|
||||
<string name="adblock_summary">Блокування реклами у вбудованому браузері (бета)</string>
|
||||
<string name="adblock">Блокувати рекламу у браузері</string>
|
||||
<string name="expand">Розширювати</string>
|
||||
<string name="collapse">Згорнутвання</string>
|
||||
<string name="changelog_summary">Історія змін для нещодавно випущених версій</string>
|
||||
<string name="changelog">Журнал змін</string>
|
||||
<string name="hide_from_main_screen">Сховати з головного екрану</string>
|
||||
<string name="additional_action_required">Потрібні додаткові дії</string>
|
||||
<string name="incognito_for_nsfw">Режим інкогніто для NSFW-манґи</string>
|
||||
<string name="incognito_mode_hint_nsfw">Ця манґа може містити контент для дорослих. Бажаєте використовувати режим інкогніто?</string>
|
||||
<string name="dont_ask_again">Більше не питати</string>
|
||||
<string name="page_switch_timer">Сторінка змінюватиметься кожні ~%d секунд</string>
|
||||
<string name="change_cover">Змінити обкладинку</string>
|
||||
<string name="pick_custom_file">Вибрати файл користувача</string>
|
||||
<string name="pick_manga_page">Вибрати сторінку манґи</string>
|
||||
<string name="use_default_cover">Використовувати стандартну обкладинку</string>
|
||||
<string name="manga_override_hint">Ці зміни впливатимуть на те, як манга відображається у програмі</string>
|
||||
<string name="error_non_file_uri">Вибраний шлях не може бути використаний, оскільки він не позначає файл чи каталог</string>
|
||||
<string name="tags_warnings_summary">Виділяти жанри, які можуть бути неприйнятними для більшості користувачів</string>
|
||||
<string name="tags_warnings">Виділяти небезпечні жанри</string>
|
||||
<string name="suggestions_disabled_sources_summary">Відображати рекомендації з усіх джерел манги, включаючи вимкнені</string>
|
||||
<string name="include_disabled_sources">Увімкнути відключені джерела</string>
|
||||
<string name="exclude_nsfw_from_suggestions_summary">Доросла манґа не відображатиметься у рекомендаціях. Ця опція може не працювати з деякими джерелами</string>
|
||||
<string name="no_write_permission_to_file">Немає прав на запис до файлу</string>
|
||||
<string name="clear_browser_data">Очистити дані браузера</string>
|
||||
<string name="link_to_manga_in_app">Посилання на мангу у Kotatsu</string>
|
||||
<string name="link_to_manga_on_s">Посилання на мангу на %s</string>
|
||||
<string name="error_disclaimer_report">Ви можете надіслати звіт про помилку розробникам. Це допоможе нам виправити проблему.</string>
|
||||
<string name="error_disclaimer_app_outdated">Схоже, що ваша версія Kotatsu застаріла. Будь ласка, установіть останню версію, щоб отримати всі доступні виправлення.</string>
|
||||
<string name="error_disclaimer_manga">Спробуйте відкрити манґу в браузері, щоб переконатися, що вона доступна джерелом.</string>
|
||||
</resources>
|
||||
|
||||
@@ -824,7 +824,7 @@
|
||||
<string name="adblock">Chặn quảng cáo trong trình duyệt (Webview)</string>
|
||||
<string name="adblock_summary">Chặn những quảng cáo xuất hiện trong trình duyệt Webview của hệ thống khi duyệt (Beta)</string>
|
||||
<string name="collapse_long_description">Thu gọn đoạn mô tả dài</string>
|
||||
<string name="creating_backup">Đang tạo sao lưu</string>
|
||||
<string name="creating_backup">Sao lưu dữ liệu</string>
|
||||
<string name="share_backup">Chia sẻ dữ liệu đã sao lưu</string>
|
||||
<string name="reader_multitask">Mở trình đọc sang 1 tác vụ riêng biệt</string>
|
||||
<string name="reader_multitask_summary">Tách trình đọc thành 1 tác vụ riêng biệt sẽ cho bạn khả năng mở và đọc nhiều truyện cùng một lúc (tốt hơn với máy tính bảng, Chrome OS và các giả lập Android cho phép mở đa cửa sổ trên máy tính như WSA,...)</string>
|
||||
@@ -832,4 +832,7 @@
|
||||
<string name="theme_name_totoro">Totoro</string>
|
||||
<string name="reader_navigation_inverted">Đảo ngược phím điều khiển hướng đọc</string>
|
||||
<string name="reader_navigation_inverted_summary">Đảo ngược hướng của các phím điều khiển để đọc truyện như nút tăng / giảm âm lượng, D-pad (lên / xuống / trái / phải)</string>
|
||||
<string name="book_effect">Làm dịu ánh màu xanh của ảnh (chuyển sang màu vàng)</string>
|
||||
<string name="local_storage_cleanup">Dọn dẹp bộ nhớ lưu trữ cục bộ</string>
|
||||
<string name="packup_creation_failed">Tạo bản sao lưu dữ liệu thất bại</string>
|
||||
</resources>
|
||||
|
||||
@@ -854,4 +854,6 @@
|
||||
<string name="theme_name_itsuka">Itsuka</string>
|
||||
<string name="theme_name_totoro">Totoro</string>
|
||||
<string name="book_effect">Yellowish background (blue filter)</string>
|
||||
<string name="local_storage_cleanup">Local storage cleanup</string>
|
||||
<string name="packup_creation_failed">Failed to create backup</string>
|
||||
</resources>
|
||||
|
||||
@@ -13,6 +13,7 @@ constraintlayout = "2.2.1"
|
||||
coreKtx = "1.16.0"
|
||||
coroutines = "1.10.2"
|
||||
dagger = "2.56.2"
|
||||
decoroutinator = "2.5.4"
|
||||
desugar = "2.1.5"
|
||||
diskLruCache = "1.5"
|
||||
documentfile = "1.1.0"
|
||||
@@ -32,7 +33,7 @@ material = "1.14.0-alpha02"
|
||||
moshi = "1.15.2"
|
||||
okhttp = "4.12.0"
|
||||
okio = "3.12.0"
|
||||
parsers = "248d51321a"
|
||||
parsers = "613d1cd1a1"
|
||||
preference = "1.2.1"
|
||||
recyclerview = "1.4.0"
|
||||
room = "2.7.2"
|
||||
@@ -119,3 +120,4 @@ kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlinx-serizliation = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
room = { id = "androidx.room", version.ref = "room" }
|
||||
decoroutinator = { id = "dev.reformator.stacktracedecoroutinator", version.ref = "decoroutinator" }
|
||||
|
||||
Reference in New Issue
Block a user