Compare commits

..

29 Commits
v4.2 ... v4.3.2

Author SHA1 Message Date
Koitharu
7ffa15d2d7 Update parsers 2023-01-30 20:11:14 +02:00
Koitharu
e11e890818 Update parsers 2023-01-25 20:02:01 +02:00
Koitharu
3e7a48d27a Fix NPE during PagesCache initialization 2023-01-22 09:21:24 +02:00
Koitharu
eeba959ba5 Replace fadingEdges with scrollIndicators 2023-01-22 09:09:45 +02:00
Zakhar Timoshenko
e7fa1036be Fading chips on detailed list 2023-01-21 20:55:54 +03:00
Zakhar Timoshenko
542a7e1141 Merge remote-tracking branch 'origin/devel' into devel 2023-01-21 20:48:25 +03:00
Zakhar Timoshenko
5951f4438a Fix dialog background on Android 5 2023-01-21 20:47:56 +03:00
Koitharu
1fbae6bd7b Translated using Weblate (Russian)
Currently translated at 100.0% (405 of 405 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2023-01-21 19:14:27 +02:00
Dan
b73924aea8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (402 of 402 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Co-authored-by: Dan <denqwerta@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/uk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-01-21 19:14:27 +02:00
Shippo
005443f4ae Translated using Weblate (Arabic)
Currently translated at 14.9% (60 of 401 strings)

Co-authored-by: Shippo <Shipox@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2023-01-21 19:14:27 +02:00
J. Lavoie
abb55d4424 Translated using Weblate (Italian)
Currently translated at 99.2% (398 of 401 strings)

Translated using Weblate (French)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Italian)

Currently translated at 80.7% (324 of 401 strings)

Translated using Weblate (German)

Currently translated at 97.5% (391 of 401 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2023-01-21 19:14:27 +02:00
ssantos
e0538da079 Translated using Weblate (Portuguese)
Currently translated at 99.2% (398 of 401 strings)

Translated using Weblate (Portuguese)

Currently translated at 66.5% (267 of 401 strings)

Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2023-01-21 19:14:27 +02:00
Eric
665bf5a034 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (405 of 405 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (402 of 402 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (401 of 401 strings)

Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2023-01-21 19:14:27 +02:00
Evgeniy Khramov
dc7e1282c6 Translated using Weblate (Russian)
Currently translated at 100.0% (401 of 401 strings)

Co-authored-by: Evgeniy Khramov <thejenjagamertjg@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2023-01-21 19:14:27 +02:00
Oğuz Ersen
3a877d4f4a Translated using Weblate (Turkish)
Currently translated at 100.0% (405 of 405 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (402 of 402 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (401 of 401 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-01-21 19:14:27 +02:00
gallegonovato
8a23c9a327 Translated using Weblate (Spanish)
Currently translated at 100.0% (405 of 405 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (402 of 402 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (401 of 401 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-01-21 19:14:27 +02:00
Koitharu
452c0edfc7 Fix Continue button behavior 2023-01-20 17:06:29 +02:00
Koitharu
2b9307aa17 Update dynamic colors 2023-01-20 15:48:54 +02:00
Koitharu
f91d5e1c29 Update gradle 2023-01-20 15:13:16 +02:00
Koitharu
2fbfd14252 Fix tracker cancellation errors 2023-01-20 14:28:59 +02:00
Koitharu
c09dd92cff Logger for debug logs 2023-01-20 11:32:29 +02:00
Koitharu
6b08074a70 Fix changelog formatting 2023-01-19 19:52:00 +02:00
Koitharu
9cb5971182 Option to change app language #282 2023-01-19 18:58:15 +02:00
Zakhar Timoshenko
6f37d95c24 Adjust alert dialogs to M3 guidelines 2023-01-19 07:37:00 +03:00
Zakhar Timoshenko
d290ba24b7 Use Markwon for pretty changelogs 2023-01-19 07:35:56 +03:00
Koitharu
f57d23026b Update room 2023-01-17 08:17:43 +02:00
Koitharu
1a70ccff55 Merge branch 'master' into devel 2023-01-17 07:41:09 +02:00
Koitharu
bd6a51e58d Fix crash on cold launch 2023-01-09 19:12:37 +02:00
Koitharu
a9c122b144 Option to mark chapter as current #56 2023-01-09 16:34:18 +02:00
69 changed files with 1019 additions and 341 deletions

2
.idea/gradle.xml generated
View File

@@ -7,7 +7,7 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@@ -7,16 +7,16 @@ plugins {
}
android {
compileSdkVersion 33
buildToolsVersion '33.0.0'
namespace 'org.koitharu.kotatsu'
compileSdk = 33
buildToolsVersion = '33.0.1'
namespace = 'org.koitharu.kotatsu'
defaultConfig {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 33
versionCode 509
versionName '4.2'
versionCode 513
versionName '4.3.2'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -56,6 +56,7 @@ android {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += [
'-opt-in=kotlin.ExperimentalStdlibApi',
'-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi',
'-opt-in=kotlinx.coroutines.FlowPreview',
'-opt-in=kotlin.contracts.ExperimentalContracts',
@@ -83,13 +84,14 @@ afterEvaluate {
}
}
dependencies {
implementation('com.github.KotatsuApp:kotatsu-parsers:cf00732023') {
implementation('com.github.KotatsuApp:kotatsu-parsers:7f630184c0') {
exclude group: 'org.json', module: 'json'
}
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation "androidx.appcompat:appcompat:1.6.0"
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.activity:activity-ktx:1.6.1'
implementation 'androidx.fragment:fragment-ktx:1.5.5'
@@ -104,13 +106,13 @@ dependencies {
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
implementation 'com.google.android.material:material:1.7.0'
implementation 'com.google.android.material:material:1.8.0'
//noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
implementation 'androidx.room:room-runtime:2.4.3'
implementation 'androidx.room:room-ktx:2.4.3'
kapt 'androidx.room:room-compiler:2.4.3'
implementation 'androidx.room:room-runtime:2.5.0'
implementation 'androidx.room:room-ktx:2.5.0'
kapt 'androidx.room:room-compiler:2.5.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
@@ -128,6 +130,7 @@ dependencies {
implementation 'io.coil-kt:coil-svg:2.2.2'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:1b19231b2f'
implementation 'com.github.solkin:disk-lru-cache:1.4'
implementation 'io.noties.markwon:core:4.6.2'
implementation 'ch.acra:acra-http:5.9.7'
implementation 'ch.acra:acra-dialog:5.9.7'
@@ -145,7 +148,7 @@ dependencies {
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
androidTestImplementation 'androidx.room:room-testing:2.4.3'
androidTestImplementation 'androidx.room:room-testing:2.5.0'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.44.2'

View File

@@ -50,6 +50,7 @@ class KotatsuApp : Application(), Configuration.Provider {
enableStrictMode()
}
AppCompatDelegate.setDefaultNightMode(settings.theme)
AppCompatDelegate.setApplicationLocales(settings.appLocales)
setupActivityLifecycleCallbacks()
processLifecycleScope.launch(Dispatchers.Default) {
setupDatabaseObservers()

View File

@@ -8,10 +8,12 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior
import androidx.core.view.ViewCompat
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
class ShrinkOnScrollBehavior : Behavior<ExtendedFloatingActionButton> {
open class ShrinkOnScrollBehavior : Behavior<ExtendedFloatingActionButton> {
@Suppress("unused") constructor() : super()
@Suppress("unused") constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
@Suppress("unused")
constructor() : super()
@Suppress("unused")
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
@@ -45,4 +47,4 @@ class ShrinkOnScrollBehavior : Behavior<ExtendedFloatingActionButton> {
}
}
}
}
}

View File

@@ -2,15 +2,4 @@ package org.koitharu.kotatsu.core.cache
import androidx.collection.LruCache
class DeferredLruCache<T>(maxSize: Int) : LruCache<ContentCache.Key, SafeDeferred<T>>(maxSize) {
override fun entryRemoved(
evicted: Boolean,
key: ContentCache.Key,
oldValue: SafeDeferred<T>,
newValue: SafeDeferred<T>?,
) {
super.entryRemoved(evicted, key, oldValue, newValue)
oldValue.cancel()
}
}
class DeferredLruCache<T>(maxSize: Int) : LruCache<ContentCache.Key, SafeDeferred<T>>(maxSize)

View File

@@ -13,16 +13,6 @@ abstract class PreferencesDao {
@Query("SELECT * FROM preferences WHERE manga_id = :mangaId")
abstract fun observe(mangaId: Long): Flow<MangaPrefsEntity?>
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun insert(pref: MangaPrefsEntity): Long
@Update
abstract suspend fun update(pref: MangaPrefsEntity): Int
@Transaction
open suspend fun upsert(pref: MangaPrefsEntity) {
if (update(pref) == 0) {
insert(pref)
}
}
@Upsert
abstract suspend fun upsert(pref: MangaPrefsEntity)
}

View File

@@ -14,7 +14,7 @@ abstract class TagsDao {
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
GROUP BY tags.title
ORDER BY COUNT(manga_id) DESC
LIMIT :limit"""
LIMIT :limit""",
)
abstract suspend fun findPopularTags(limit: Int): List<TagEntity>
@@ -24,7 +24,7 @@ abstract class TagsDao {
WHERE tags.source = :source
GROUP BY tags.title
ORDER BY COUNT(manga_id) DESC
LIMIT :limit"""
LIMIT :limit""",
)
abstract suspend fun findPopularTags(source: String, limit: Int): List<TagEntity>
@@ -34,7 +34,7 @@ abstract class TagsDao {
WHERE tags.source = :source AND title LIKE :query
GROUP BY tags.title
ORDER BY COUNT(manga_id) DESC
LIMIT :limit"""
LIMIT :limit""",
)
abstract suspend fun findTags(source: String, query: String, limit: Int): List<TagEntity>
@@ -44,22 +44,10 @@ abstract class TagsDao {
WHERE title LIKE :query
GROUP BY tags.title
ORDER BY COUNT(manga_id) DESC
LIMIT :limit"""
LIMIT :limit""",
)
abstract suspend fun findTags(query: String, limit: Int): List<TagEntity>
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun insert(tag: TagEntity): Long
@Update(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun update(tag: TagEntity): Int
@Transaction
open suspend fun upsert(tags: Iterable<TagEntity>) {
tags.forEach { tag ->
if (update(tag) <= 0) {
insert(tag)
}
}
}
}
@Upsert
abstract suspend fun upsert(tags: Iterable<TagEntity>)
}

View File

@@ -80,6 +80,12 @@ class AppUpdateRepository @Inject constructor(
return BuildConfig.DEBUG || getCertificateSHA1Fingerprint() == CERT_SHA1
}
suspend fun getCurrentVersionChangelog(): String? {
val currentVersion = VersionId(BuildConfig.VERSION_NAME)
val available = getAvailableVersions()
return available.find { x -> x.versionId == currentVersion }?.description
}
@Suppress("DEPRECATION")
@SuppressLint("PackageManagerGetSignatures")
private fun getCertificateSHA1Fingerprint(): String? = runCatching {

View File

@@ -0,0 +1,128 @@
package org.koitharu.kotatsu.core.logs
import android.content.Context
import androidx.annotation.WorkerThread
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.ext.subdir
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.ConcurrentLinkedQueue
private const val DIR = "logs"
private const val FLUSH_DELAY = 2_000L
private const val MAX_SIZE_BYTES = 1024 * 1024 // 1 MB
class FileLogger(
context: Context,
private val settings: AppSettings,
name: String,
) {
val file by lazy {
val dir = context.getExternalFilesDir(DIR) ?: context.filesDir.subdir(DIR)
File(dir, "$name.log")
}
val isEnabled: Boolean
get() = settings.isLoggingEnabled
private val dateFormat = SimpleDateFormat.getDateTimeInstance(
SimpleDateFormat.SHORT,
SimpleDateFormat.SHORT,
Locale.ROOT,
)
private val buffer = ConcurrentLinkedQueue<String>()
private val mutex = Mutex()
private var flushJob: Job? = null
fun log(message: String, e: Throwable? = null) {
if (!isEnabled) {
return
}
val text = buildString {
append(dateFormat.format(Date()))
append(": ")
if (e != null) {
append("E!")
}
append(message)
if (e != null) {
append(' ')
append(e.stackTraceToString())
appendLine()
}
}
buffer.add(text)
postFlush()
}
suspend fun flush() {
if (!isEnabled) {
return
}
flushJob?.cancelAndJoin()
flushImpl()
}
private fun postFlush() {
if (flushJob?.isActive == true) {
return
}
flushJob = processLifecycleScope.launch(Dispatchers.Default) {
delay(FLUSH_DELAY)
runCatchingCancellable {
flushImpl()
}.onFailure {
it.printStackTraceDebug()
}
}
}
private suspend fun flushImpl() {
mutex.withLock {
if (buffer.isEmpty()) {
return
}
runInterruptible(Dispatchers.IO) {
if (file.length() > MAX_SIZE_BYTES) {
rotate()
}
FileOutputStream(file, true).use {
while (true) {
val message = buffer.poll() ?: break
it.write(message.toByteArray())
it.write('\n'.code)
}
it.flush()
}
}
}
}
@WorkerThread
private fun rotate() {
val length = file.length()
val bakFile = File(file.parentFile, file.name + ".bak")
file.renameTo(bakFile)
bakFile.inputStream().use { input ->
input.skip(length - MAX_SIZE_BYTES / 2)
file.outputStream().use { output ->
input.copyTo(output)
output.flush()
}
}
bakFile.delete()
}
}

View File

@@ -0,0 +1,7 @@
package org.koitharu.kotatsu.core.logs
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class TrackerLogger

View File

@@ -0,0 +1,31 @@
package org.koitharu.kotatsu.core.logs
import android.content.Context
import androidx.collection.arraySetOf
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ElementsIntoSet
import org.koitharu.kotatsu.core.prefs.AppSettings
@Module
@InstallIn(SingletonComponent::class)
object LoggersModule {
@Provides
@TrackerLogger
fun provideTrackerLogger(
@ApplicationContext context: Context,
settings: AppSettings,
) = FileLogger(context, settings, "tracker")
@Provides
@ElementsIntoSet
fun provideAllLoggers(
@TrackerLogger trackerLogger: FileLogger,
): Set<@JvmSuppressWildcards FileLogger> = arraySetOf(
trackerLogger,
)
}

View File

@@ -1,8 +1,11 @@
package org.koitharu.kotatsu.core.parser
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainCoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.currentCoroutineContext
import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.cache.SafeDeferred
import org.koitharu.kotatsu.core.prefs.SourceSettings
@@ -76,9 +79,15 @@ class RemoteMangaRepository(
private fun getConfig() = parser.config as SourceSettings
private fun <T> asyncSafe(block: suspend CoroutineScope.() -> T) = SafeDeferred(
processLifecycleScope.async(Dispatchers.Default) {
runCatchingCancellable { block() }
},
)
private suspend fun <T> asyncSafe(block: suspend CoroutineScope.() -> T): SafeDeferred<T> {
var dispatcher = currentCoroutineContext()[CoroutineDispatcher.Key]
if (dispatcher == null || dispatcher is MainCoroutineDispatcher) {
dispatcher = Dispatchers.Default
}
return SafeDeferred(
processLifecycleScope.async(dispatcher) {
runCatchingCancellable { block() }
},
)
}
}

View File

@@ -7,6 +7,7 @@ import android.provider.Settings
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.arraySetOf
import androidx.core.content.edit
import androidx.core.os.LocaleListCompat
import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -79,6 +80,17 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getInt(KEY_GRID_SIZE, 100)
set(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }
var appLocales: LocaleListCompat
get() {
val raw = prefs.getString(KEY_APP_LOCALE, null)
return LocaleListCompat.forLanguageTags(raw)
}
set(value) {
prefs.edit {
putString(KEY_APP_LOCALE, value.toLanguageTags())
}
}
val readerPageSwitch: Set<String>
get() = prefs.getStringSet(KEY_READER_SWITCHERS, null) ?: setOf(PAGE_SWITCH_TAPS)
@@ -147,6 +159,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getString(KEY_APP_PASSWORD, null)
set(value) = prefs.edit { if (value != null) putString(KEY_APP_PASSWORD, value) else remove(KEY_APP_PASSWORD) }
val isLoggingEnabled: Boolean
get() = prefs.getBoolean(KEY_LOGGING_ENABLED, false)
var isBiometricProtectionEnabled: Boolean
get() = prefs.getBoolean(KEY_PROTECT_APP_BIOMETRIC, true)
set(value) = prefs.edit { putBoolean(KEY_PROTECT_APP_BIOMETRIC, value) }
@@ -358,6 +373,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
const val KEY_SHELF_SECTIONS = "shelf_sections_2"
const val KEY_PREFETCH_CONTENT = "prefetch_content"
const val KEY_APP_LOCALE = "app_locale"
const val KEY_LOGGING_ENABLED = "logging"
const val KEY_LOGS_SHARE = "logs_share"
// About
const val KEY_APP_UPDATE = "app_update"

View File

@@ -24,6 +24,7 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig
override fun <T> get(key: ConfigKey<T>): T {
return when (key) {
is ConfigKey.Domain -> prefs.getString(key.key, key.defaultValue).ifNullOrEmpty { key.defaultValue }
is ConfigKey.ShowSuspiciousContent -> prefs.getBoolean(key.key, key.defaultValue)
} as T
}
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.content.Intent
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.base.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
@@ -47,7 +46,7 @@ class MangaPrefetchService : CoroutineIntentService() {
override fun onError(startId: Int, error: Throwable) = Unit
private suspend fun prefetchDetails(manga: Manga) = coroutineScope {
private suspend fun prefetchDetails(manga: Manga) {
val source = mangaRepositoryFactory.create(manga.source)
runCatchingCancellable { source.getDetails(manga) }
}

View File

@@ -1,15 +1,16 @@
package org.koitharu.kotatsu.details.ui
import android.os.Bundle
import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.Snackbar
import kotlin.math.roundToInt
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
@@ -24,8 +25,8 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
import org.koitharu.kotatsu.utils.ext.parents
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
import kotlin.math.roundToInt
class ChaptersFragment :
BaseFragment<FragmentChaptersBinding>(),
@@ -102,6 +103,7 @@ class ChaptersFragment :
mode.finish()
true
}
R.id.action_delete -> {
val ids = selectionController?.peekCheckedIds()
val manga = viewModel.manga.value
@@ -120,6 +122,7 @@ class ChaptersFragment :
mode.finish()
true
}
R.id.action_select_range -> {
val items = chaptersAdapter?.items ?: return false
val ids = HashSet(controller.peekCheckedIds())
@@ -139,11 +142,20 @@ class ChaptersFragment :
controller.addAll(ids)
true
}
R.id.action_select_all -> {
val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false
selectionController?.addAll(ids)
controller.addAll(ids)
true
}
R.id.action_mark_current -> {
val id = controller.peekCheckedIds().singleOrNull() ?: return false
viewModel.markChapterAsCurrent(id)
mode.finish()
true
}
else -> false
}
}
@@ -164,6 +176,7 @@ class ChaptersFragment :
x.chapter.source == MangaSource.LOCAL
}
menu.findItem(R.id.action_select_all).isVisible = items.size < allItems.size
menu.findItem(R.id.action_mark_current).isVisible = items.size == 1
mode.title = items.size.toString()
var hasGap = false
for (i in 0 until items.size - 1) {

View File

@@ -281,6 +281,17 @@ class DetailsViewModel @AssistedInject constructor(
}
}
fun markChapterAsCurrent(chapterId: Long) {
launchJob(Dispatchers.Default) {
val manga = checkNotNull(delegate.manga.value)
val chapters = checkNotNull(manga.chapters)
val chapterIndex = chapters.indexOfFirst { it.id == chapterId }
check(chapterIndex in chapters.indices) { "Chapter not found" }
val percent = chapterIndex / chapters.size.toFloat()
historyRepository.addOrUpdate(manga = manga, chapterId = chapterId, page = 0, scroll = 0, percent = percent)
}
}
private fun doLoad() = launchLoadingJob(Dispatchers.Default) {
delegate.doLoad()
}

View File

@@ -21,9 +21,6 @@ abstract class FavouriteCategoriesDao {
@Insert(onConflict = OnConflictStrategy.ABORT)
abstract suspend fun insert(category: FavouriteCategoryEntity): Long
@Update
abstract suspend fun update(category: FavouriteCategoryEntity): Int
suspend fun delete(id: Long) = setDeletedAt(id, System.currentTimeMillis())
@Query("UPDATE favourite_categories SET title = :title, `order` = :order, `track` = :tracker WHERE category_id = :id")
@@ -51,12 +48,8 @@ abstract class FavouriteCategoriesDao {
return (getMaxSortKey() ?: 0) + 1
}
@Transaction
open suspend fun upsert(entity: FavouriteCategoryEntity) {
if (update(entity) == 0) {
insert(entity)
}
}
@Upsert
abstract suspend fun upsert(entity: FavouriteCategoryEntity)
@Query("UPDATE favourite_categories SET deleted_at = :deletedAt WHERE category_id = :id")
protected abstract suspend fun setDeletedAt(id: Long, deletedAt: Long)

View File

@@ -99,11 +99,6 @@ abstract class FavouritesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(favourite: FavouriteEntity)
/** UPDATE **/
@Update
abstract suspend fun update(favourite: FavouriteEntity): Int
/** DELETE **/
suspend fun delete(mangaId: Long) = setDeletedAt(
@@ -138,12 +133,8 @@ abstract class FavouritesDao {
/** TOOLS **/
@Transaction
open suspend fun upsert(entity: FavouriteEntity) {
if (update(entity) == 0) {
insert(entity)
}
}
@Upsert
abstract suspend fun upsert(entity: FavouriteEntity)
@Transaction
@RawQuery(observedEntities = [FavouriteEntity::class])
@@ -166,6 +157,7 @@ abstract class FavouritesDao {
SortOrder.NEWEST,
SortOrder.UPDATED,
-> "created_at DESC"
SortOrder.ALPHABETICAL -> "title ASC"
else -> throw IllegalArgumentException("Sort order $sortOrder is not supported")
}

View File

@@ -21,7 +21,7 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
private val cacheDir = checkNotNull(findSuitableDir(context)) {
val dirs = (context.externalCacheDirs + context.cacheDir).joinToString(";") {
it.absolutePath
it?.absolutePath.toString()
}
"Cannot find any suitable directory for PagesCache: [$dirs]"
}
@@ -60,6 +60,6 @@ private fun createDiskLruCacheSafe(dir: File, size: Long): DiskLruCache {
private fun findSuitableDir(context: Context): File? {
val dirs = context.externalCacheDirs + context.cacheDir
return dirs.firstNotNullOfOrNull {
it.subdir(CacheDir.PAGES.dir).takeIfWriteable()
it?.subdir(CacheDir.PAGES.dir)?.takeIfWriteable()
}
}

View File

@@ -0,0 +1,39 @@
package org.koitharu.kotatsu.main.ui
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import org.koitharu.kotatsu.base.ui.util.ShrinkOnScrollBehavior
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
class MainActionButtonBehavior : ShrinkOnScrollBehavior {
constructor() : super()
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
override fun layoutDependsOn(
parent: CoordinatorLayout,
child: ExtendedFloatingActionButton,
dependency: View
): Boolean {
return dependency is SlidingBottomNavigationView || super.layoutDependsOn(parent, child, dependency)
}
override fun onDependentViewChanged(
parent: CoordinatorLayout,
child: ExtendedFloatingActionButton,
dependency: View
): Boolean {
val bottom = child.bottom
val bottomLine = parent.height
return if (bottom > bottomLine) {
ViewCompat.offsetTopAndBottom(child, bottomLine - bottom)
true
} else {
false
}
}
}

View File

@@ -38,7 +38,10 @@ class MainNavigationDelegate(
}
override fun onNavigationItemReselected(item: MenuItem) {
val fragment = fragmentManager.findFragmentByTag(TAG_PRIMARY) as? RecyclerViewOwner ?: return
val fragment = fragmentManager.findFragmentByTag(TAG_PRIMARY)
if (fragment == null || fragment !is RecyclerViewOwner || fragment.view == null) {
return
}
val recyclerView = fragment.recyclerView
recyclerView.smoothScrollToPosition(0)
}
@@ -46,7 +49,10 @@ class MainNavigationDelegate(
fun onCreate(savedInstanceState: Bundle?) {
primaryFragment?.let {
onFragmentChanged(it, fromUser = false)
navBar.selectedItemId = getItemId(it)
val itemId = getItemId(it)
if (navBar.selectedItemId != itemId) {
navBar.selectedItemId = itemId
}
} ?: onNavigationItemSelected(navBar.selectedItemId)
}

View File

@@ -1,27 +1,38 @@
package org.koitharu.kotatsu.settings
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.LocaleManagerCompat
import androidx.core.view.postDelayed
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.TwoStatePreference
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.parsers.util.names
import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
import org.koitharu.kotatsu.settings.utils.ActivityListPreference
import org.koitharu.kotatsu.settings.utils.SliderPreference
import org.koitharu.kotatsu.utils.ext.getLocalesConfig
import org.koitharu.kotatsu.utils.ext.map
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
import org.koitharu.kotatsu.utils.ext.toList
import java.util.Date
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
class AppearanceSettingsFragment :
@@ -52,7 +63,7 @@ class AppearanceSettingsFragment :
entries = entryValues.map { value ->
val formattedDate = settings.getDateFormat(value.toString()).format(now)
if (value == "") {
"${context.getString(R.string.system_default)} ($formattedDate)"
getString(R.string.default_s, formattedDate)
} else {
formattedDate
}
@@ -62,6 +73,20 @@ class AppearanceSettingsFragment :
}
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
?.isChecked = !settings.appPassword.isNullOrEmpty()
findPreference<ActivityListPreference>(AppSettings.KEY_APP_LOCALE)?.run {
initLocalePicker(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
activityIntent = Intent(
Settings.ACTION_APP_LOCALE_SETTINGS,
Uri.fromParts("package", context.packageName, null),
)
}
summaryProvider = Preference.SummaryProvider<ActivityListPreference> {
val locale = AppCompatDelegate.getApplicationLocales().get(0)
locale?.getDisplayName(locale)?.toTitleCase(locale) ?: getString(R.string.automatic)
}
setDefaultValueCompat("")
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -79,16 +104,23 @@ class AppearanceSettingsFragment :
AppSettings.KEY_THEME -> {
AppCompatDelegate.setDefaultNightMode(settings.theme)
}
AppSettings.KEY_DYNAMIC_THEME -> {
postRestart()
}
AppSettings.KEY_THEME_AMOLED -> {
postRestart()
}
AppSettings.KEY_APP_PASSWORD -> {
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
?.isChecked = !settings.appPassword.isNullOrEmpty()
}
AppSettings.KEY_APP_LOCALE -> {
AppCompatDelegate.setApplicationLocales(settings.appLocales)
}
}
}
@@ -104,6 +136,7 @@ class AppearanceSettingsFragment :
}
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
@@ -113,4 +146,45 @@ class AppearanceSettingsFragment :
activityRecreationHandle.recreateAll()
}
}
private fun initLocalePicker(preference: ListPreference) {
val locales = resources.getLocalesConfig()
.toList()
.sortedWith(LocaleComparator(preference.context))
preference.entries = Array(locales.size + 1) { i ->
if (i == 0) {
getString(R.string.automatic)
} else {
val lc = locales[i - 1]
lc.getDisplayName(lc).toTitleCase(lc)
}
}
preference.entryValues = Array(locales.size + 1) { i ->
if (i == 0) {
""
} else {
locales[i - 1].toLanguageTag()
}
}
}
private class LocaleComparator(context: Context) : Comparator<Locale> {
private val deviceLocales = LocaleManagerCompat.getSystemLocales(context)
.map { it.language }
override fun compare(a: Locale, b: Locale): Int {
return if (a === b) {
0
} else {
val indexA = deviceLocales.indexOf(a.language)
val indexB = deviceLocales.indexOf(b.language)
if (indexA == -1 && indexB == -1) {
compareValues(a.language, b.language)
} else {
-2 - (indexA - indexB)
}
}
}
}
}

View File

@@ -78,6 +78,7 @@ class SettingsActivity :
startActivity(intent)
true
}
else -> super.onOptionsItemSelected(item)
}
@@ -132,6 +133,7 @@ class SettingsActivity :
ACTION_SOURCE -> SourceSettingsFragment.newInstance(
intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL,
)
ACTION_MANAGE_SOURCES -> SourcesSettingsFragment()
else -> SettingsHeadersFragment()
}

View File

@@ -4,6 +4,7 @@ import android.view.inputmethod.EditorInfo
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -29,15 +30,22 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang
inputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI,
hint = key.defaultValue,
validator = DomainValidator(),
)
),
)
setTitle(R.string.domain)
setDialogTitle(R.string.domain)
}
}
is ConfigKey.ShowSuspiciousContent -> {
SwitchPreferenceCompat(requireContext()).apply {
setDefaultValue(key.defaultValue)
setTitle(R.string.show_suspicious_content)
}
}
}
preference.isIconSpaceReserved = false
preference.key = key.key
screen.addPreference(preference)
}
}
}

View File

@@ -7,16 +7,24 @@ import androidx.core.net.toUri
import androidx.fragment.app.viewModels
import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.github.AppVersion
import org.koitharu.kotatsu.core.logs.FileLogger
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.utils.ShareHelper
import javax.inject.Inject
@AndroidEntryPoint
class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
private val viewModel by viewModels<AboutSettingsViewModel>()
@Inject
lateinit var loggers: Set<@JvmSuppressWildcards FileLogger>
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_about)
findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run {
@@ -39,10 +47,17 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
viewModel.checkForUpdates()
true
}
AppSettings.KEY_APP_TRANSLATION -> {
openLink(getString(R.string.url_weblate), preference.title)
true
}
AppSettings.KEY_LOGS_SHARE -> {
ShareHelper(preference.context).shareLogs(loggers)
true
}
else -> super.onPreferenceTreeClick(preference)
}
}

View File

@@ -3,22 +3,24 @@ package org.koitharu.kotatsu.settings.about
import android.content.Context
import android.content.Intent
import androidx.core.net.toUri
import com.google.android.material.R as materialR
import androidx.core.text.buildSpannedString
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.noties.markwon.Markwon
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.github.AppVersion
import org.koitharu.kotatsu.utils.FileSize
import com.google.android.material.R as materialR
class AppUpdateDialog(private val context: Context) {
fun show(version: AppVersion) {
val message = buildString {
val message = buildSpannedString {
append(context.getString(R.string.new_version_s, version.name))
appendLine()
append(context.getString(R.string.size_s, FileSize.BYTES.format(context, version.apkSize)))
appendLine()
appendLine()
append(version.description)
append(Markwon.create(context).toMarkdown(version.description))
}
MaterialAlertDialogBuilder(
context,

View File

@@ -4,11 +4,11 @@ import android.app.backup.BackupManager
import android.content.Context
import androidx.room.InvalidationTracker
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class BackupObserver @Inject constructor(
@@ -17,7 +17,7 @@ class BackupObserver @Inject constructor(
private val backupManager = BackupManager(context)
override fun onInvalidated(tables: MutableSet<String>) {
override fun onInvalidated(tables: Set<String>) {
backupManager.dataChanged()
}
}

View File

@@ -47,7 +47,6 @@ class ToolsFragment :
super.onViewCreated(view, savedInstanceState)
binding.buttonSettings.setOnClickListener(this)
binding.buttonDownloads.setOnClickListener(this)
binding.cardUpdate.root.setOnClickListener(this)
binding.cardUpdate.buttonChangelog.setOnClickListener(this)
binding.cardUpdate.buttonDownload.setOnClickListener(this)
binding.switchIncognito.setOnCheckedChangeListener(this)
@@ -71,12 +70,10 @@ class ToolsFragment :
startActivity(Intent.createChooser(intent, getString(R.string.open_in_browser)))
}
R.id.card_update -> {
R.id.button_changelog -> {
val version = viewModel.appUpdate.value ?: return
AppUpdateDialog(v.context).show(version)
}
R.id.button_changelog -> showChangelog()
}
}
@@ -96,7 +93,6 @@ class ToolsFragment :
return
}
binding.cardUpdate.textSecondary.text = getString(R.string.new_version_s, version.name)
binding.cardUpdate.textChangelog.text = version.description
binding.cardUpdate.root.isVisible = true
}
@@ -145,13 +141,6 @@ class ToolsFragment :
return MaterialColors.harmonize(color, backgroundColor)
}
private fun showChangelog() {
TransitionManager.beginDelayedTransition(binding.cardUpdate.root)
binding.cardUpdate.buttonChangelog.isVisible = false
binding.cardUpdate.textSecondary.isVisible = true
binding.cardUpdate.textChangelog.isVisible = true
}
companion object {
fun newInstance() = ToolsFragment()

View File

@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.settings.tracker
import androidx.lifecycle.MutableLiveData
import androidx.room.InvalidationTracker
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import okio.Closeable
import org.koitharu.kotatsu.base.ui.BaseViewModel
@@ -11,6 +10,7 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
import org.koitharu.kotatsu.core.db.removeObserverAsync
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import javax.inject.Inject
@HiltViewModel
class TrackerSettingsViewModel @Inject constructor(
@@ -39,7 +39,7 @@ class TrackerSettingsViewModel @Inject constructor(
InvalidationTracker.Observer(arrayOf(TABLE_FAVOURITE_CATEGORIES)),
Closeable {
override fun onInvalidated(tables: MutableSet<String>) {
override fun onInvalidated(tables: Set<String>) {
vm?.updateCategoriesCount()
}

View File

@@ -0,0 +1,38 @@
package org.koitharu.kotatsu.settings.utils
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.util.AttributeSet
import androidx.preference.ListPreference
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
class ActivityListPreference : ListPreference {
var activityIntent: Intent? = null
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context) : super(context)
override fun onClick() {
val intent = activityIntent
if (intent == null) {
super.onClick()
return
}
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
e.printStackTraceDebug()
super.onClick()
}
}
}

View File

@@ -44,7 +44,7 @@ class SyncController @Inject constructor(
private val defaultGcPeriod: Long // gc period if sync disabled
get() = TimeUnit.HOURS.toMillis(2)
override fun onInvalidated(tables: MutableSet<String>) {
override fun onInvalidated(tables: Set<String>) {
requestSync(
favourites = TABLE_FAVOURITES in tables || TABLE_FAVOURITE_CATEGORIES in tables,
history = TABLE_HISTORY in tables,

View File

@@ -11,11 +11,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import java.util.concurrent.Callable
import org.koitharu.kotatsu.core.db.*
import java.util.concurrent.Callable
abstract class SyncProvider : ContentProvider() {
@@ -44,15 +43,14 @@ abstract class SyncProvider : ContentProvider() {
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? = if (getTableName(uri) != null) {
val sqlQuery = SupportSQLiteQueryBuilder.builder(uri.lastPathSegment)
): Cursor? {
val tableName = getTableName(uri) ?: return null
val sqlQuery = SupportSQLiteQueryBuilder.builder(tableName)
.columns(projection)
.selection(selection, selectionArgs)
.orderBy(sortOrder)
.create()
database.openHelper.readableDatabase.query(sqlQuery)
} else {
null
return database.openHelper.readableDatabase.query(sqlQuery)
}
override fun getType(uri: Uri): String? {

View File

@@ -1,12 +1,10 @@
package org.koitharu.kotatsu.tracker.data
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.MapInfo
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import androidx.room.Upsert
import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
@@ -46,22 +44,12 @@ abstract class TracksDao {
@Query("UPDATE tracks SET chapters_new = 0")
abstract suspend fun clearCounters()
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun insert(entity: TrackEntity): Long
@Update
abstract suspend fun update(entity: TrackEntity): Int
@Query("DELETE FROM tracks WHERE manga_id = :mangaId")
abstract suspend fun delete(mangaId: Long)
@Query("DELETE FROM tracks WHERE manga_id NOT IN (SELECT manga_id FROM history UNION SELECT manga_id FROM favourites)")
abstract suspend fun gc()
@Transaction
open suspend fun upsert(entity: TrackEntity) {
if (update(entity) == 0) {
insert(entity)
}
}
@Upsert
abstract suspend fun upsert(entity: TrackEntity)
}

View File

@@ -12,14 +12,33 @@ import androidx.core.content.ContextCompat
import androidx.hilt.work.HiltWorker
import androidx.lifecycle.LiveData
import androidx.lifecycle.map
import androidx.work.*
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.WorkerParameters
import coil.ImageLoader
import coil.request.ImageRequest
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.logs.FileLogger
import org.koitharu.kotatsu.core.logs.TrackerLogger
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.parsers.model.Manga
@@ -31,6 +50,7 @@ import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
import org.koitharu.kotatsu.utils.ext.trySetForeground
import java.util.concurrent.TimeUnit
@HiltWorker
class TrackWorker @AssistedInject constructor(
@@ -39,6 +59,7 @@ class TrackWorker @AssistedInject constructor(
private val coil: ImageLoader,
private val settings: AppSettings,
private val tracker: Tracker,
@TrackerLogger private val logger: FileLogger,
) : CoroutineWorker(context, workerParams) {
private val notificationManager by lazy {
@@ -46,6 +67,20 @@ class TrackWorker @AssistedInject constructor(
}
override suspend fun doWork(): Result {
logger.log("doWork()")
try {
return doWorkImpl()
} catch (e: Throwable) {
logger.log("fatal", e)
throw e
} finally {
withContext(NonCancellable) {
logger.flush()
}
}
}
private suspend fun doWorkImpl(): Result {
if (!settings.isTrackerEnabled) {
return Result.success(workDataOf(0, 0))
}
@@ -53,12 +88,12 @@ class TrackWorker @AssistedInject constructor(
trySetForeground()
}
val tracks = tracker.getAllTracks()
logger.log("Total ${tracks.size} tracks")
if (tracks.isEmpty()) {
return Result.success(workDataOf(0, 0))
}
val updates = checkUpdatesAsync(tracks)
val results = updates.awaitAll()
val results = checkUpdatesAsync(tracks)
tracker.gc()
var success = 0
@@ -70,6 +105,7 @@ class TrackWorker @AssistedInject constructor(
success++
}
}
logger.log("Result: success: $success, failed: $failed")
val resultData = workDataOf(success, failed)
return if (success == 0 && failed != 0) {
Result.failure(resultData)
@@ -78,13 +114,15 @@ class TrackWorker @AssistedInject constructor(
}
}
private suspend fun checkUpdatesAsync(tracks: List<TrackingItem>): List<Deferred<MangaUpdates?>> {
private suspend fun checkUpdatesAsync(tracks: List<TrackingItem>): List<MangaUpdates?> {
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
val deferredList = coroutineScope {
return supervisorScope {
tracks.map { (track, channelId) ->
async(dispatcher) {
runCatchingCancellable {
tracker.fetchUpdates(track, commit = true)
}.onFailure {
logger.log("checkUpdatesAsync", it)
}.onSuccess { updates ->
if (updates.isValid && updates.isNotEmpty()) {
showNotification(
@@ -95,9 +133,8 @@ class TrackWorker @AssistedInject constructor(
}
}.getOrNull()
}
}
}.awaitAll()
}
return deferredList
}
private suspend fun showNotification(manga: Manga, channelId: String?, newChapters: List<MangaChapter>) {

View File

@@ -4,10 +4,11 @@ import android.content.Context
import android.net.Uri
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import java.io.File
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.logs.FileLogger
import org.koitharu.kotatsu.parsers.model.Manga
import java.io.File
private const val TYPE_TEXT = "text/plain"
private const val TYPE_IMAGE = "image/*"
@@ -79,4 +80,15 @@ class ShareHelper(private val context: Context) {
.setChooserTitle(R.string.share)
.startChooser()
}
}
fun shareLogs(loggers: Collection<FileLogger>) {
val intentBuilder = ShareCompat.IntentBuilder(context)
.setType(TYPE_TEXT)
for (logger in loggers) {
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.files", logger.file)
intentBuilder.addStream(uri)
}
intentBuilder.setChooserTitle(R.string.share_logs)
intentBuilder.startChooser()
}
}

View File

@@ -8,6 +8,7 @@ import android.content.OperationApplicationException
import android.content.SharedPreferences
import android.content.SyncResult
import android.content.pm.ResolveInfo
import android.content.res.Resources
import android.database.SQLException
import android.graphics.Color
import android.net.ConnectivityManager
@@ -20,6 +21,7 @@ import android.view.Window
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IntegerRes
import androidx.core.app.ActivityOptionsCompat
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.work.CoroutineWorker
@@ -34,8 +36,12 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import okio.IOException
import org.json.JSONException
import org.jsoup.internal.StringUtil.StringJoiner
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.InternalResourceHelper
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import kotlin.math.roundToLong
val Context.activityManager: ActivityManager?
@@ -146,3 +152,23 @@ fun scaleUpActivityOptionsOf(view: View): ActivityOptions = ActivityOptions.make
view.width,
view.height,
)
fun Resources.getLocalesConfig(): LocaleListCompat {
val tagsList = StringJoiner(",")
try {
val xpp: XmlPullParser = getXml(R.xml.locales)
while (xpp.eventType != XmlPullParser.END_DOCUMENT) {
if (xpp.eventType == XmlPullParser.START_TAG) {
if (xpp.name == "locale") {
tagsList.add(xpp.getAttributeValue(0))
}
}
xpp.next()
}
} catch (e: XmlPullParserException) {
e.printStackTraceDebug()
} catch (e: IOException) {
e.printStackTraceDebug()
}
return LocaleListCompat.forLanguageTags(tagsList.complete())
}

View File

@@ -6,19 +6,19 @@ import android.content.Context
import android.content.Intent
import androidx.room.InvalidationTracker
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.widget.recent.RecentWidgetProvider
import org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class WidgetUpdater @Inject constructor(
@ApplicationContext private val context: Context,
) : InvalidationTracker.Observer(TABLE_HISTORY, TABLE_FAVOURITES) {
override fun onInvalidated(tables: MutableSet<String>) {
override fun onInvalidated(tables: Set<String>) {
if (TABLE_HISTORY in tables) {
updateWidgets(RecentWidgetProvider::class.java)
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="?attr/colorSurface" />
<corners android:radius="@dimen/m3_alert_dialog_corner_size" />
</shape>
</item>
<item>
<shape>
<solid android:color="@color/m3_popupmenu_overlay_color" />
<corners android:radius="@dimen/m3_alert_dialog_corner_size" />
</shape>
</item>
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M23.5,17L18.5,22L15,18.5L16.5,17L18.5,19L22,15.5L23.5,17M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M12,4.5C17,4.5 21.27,7.61 23,12C22.75,12.65 22.44,13.26 22.08,13.85C21.5,13.5 20.86,13.25 20.18,13.12L20.82,12C19.17,8.64 15.76,6.5 12,6.5C8.24,6.5 4.83,8.64 3.18,12C4.83,15.36 8.24,17.5 12,17.5L13.21,17.43C13.07,17.93 13,18.46 13,19V19.46L12,19.5C7,19.5 2.73,16.39 1,12C2.73,7.61 7,4.5 12,4.5Z" />
</vector>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="@color/kotatsu_surface" />
<corners android:radius="@dimen/m3_alert_dialog_corner_size" />
</shape>
</item>
<item>
<shape>
<solid android:color="@color/kotatsu_surface" />
<corners android:radius="@dimen/m3_alert_dialog_corner_size" />
</shape>
</item>
</layer-list>

View File

@@ -75,15 +75,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="-4dp"
android:paddingBottom="8dp"
android:text="@string/_continue"
android:visibility="gone"
app:backgroundTint="?attr/colorContainer"
app:icon="@drawable/ic_read"
app:layout_anchor="@id/bottomNav"
app:layout_anchorGravity="top|end"
app:layout_behavior="org.koitharu.kotatsu.base.ui.util.ShrinkOnScrollBehavior"
app:layout_behavior="org.koitharu.kotatsu.main.ui.MainActionButtonBehavior"
app:layout_dodgeInsetEdges="bottom"
app:layout_insetEdge="bottom"
tools:visibility="visible" />
@@ -94,7 +91,6 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:clickable="true"
app:itemActiveIndicatorStyle="@style/Widget.Kotatsu.BottomNavigationView.ActiveIndicator"
app:layout_insetEdge="bottom"
app:menu="@menu/nav_bottom"
tools:ignore="KeyboardInaccessibleWidget" />

View File

@@ -43,11 +43,12 @@
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal"
android:requiresFadingEdge="horizontal"
android:scrollIndicators="start|end"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_title"
tools:ignore="UnusedAttribute"
tools:listitem="@layout/item_bookmark" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="?listPreferredItemPaddingStart"
tools:ignore="Overdraw">
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:text="@string/all_favourites"
android:textAppearance="?attr/textAppearanceBodyLarge" />
<org.koitharu.kotatsu.base.ui.widgets.CheckableImageView
android:id="@+id/imageView_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:padding="?listPreferredItemPaddingEnd"
android:scaleType="center"
app:srcCompat="@drawable/ic_shown_hidden" />
</LinearLayout>

View File

@@ -14,8 +14,9 @@
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/margin_small"
android:layout_toStartOf="@id/textView_filter"
android:requiresFadingEdge="horizontal"
android:scrollbars="none">
android:scrollIndicators="start|end"
android:scrollbars="none"
tools:ignore="UnusedAttribute">
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
android:id="@+id/chips_tags"
@@ -44,4 +45,4 @@
tools:ignore="RtlSymmetry"
tools:text="@string/popular" />
</RelativeLayout>
</RelativeLayout>

View File

@@ -53,10 +53,9 @@
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
android:ellipsize="none"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="2"
android:requiresFadingEdge="horizontal"
android:textAppearance="?attr/textAppearanceSubtitle1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_cover"
@@ -71,11 +70,13 @@
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:scrollIndicators="start|end"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle"
app:layout_goneMarginTop="12dp">
app:layout_goneMarginTop="12dp"
tools:ignore="UnusedAttribute">
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
android:id="@+id/chips_tags"

View File

@@ -2,7 +2,6 @@
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_update"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
@@ -17,7 +16,6 @@
android:id="@+id/textPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:text="@string/app_update_available"
android:textAppearance="?attr/textAppearanceTitleLarge"
app:layout_constraintEnd_toEndOf="parent"
@@ -28,30 +26,12 @@
android:id="@+id/textSecondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:text="@string/new_version_s"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textPrimary"
tools:visibility="visible" />
<TextView
android:id="@+id/textChangelog"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:fontFamily="monospace"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textSecondary"
tools:text="- Fixes\n- Improvements"
tools:visibility="visible" />
app:layout_constraintTop_toBottomOf="@id/textPrimary" />
<Button
android:id="@+id/button_changelog"
@@ -62,7 +42,7 @@
android:layout_marginEnd="12dp"
android:text="@string/details"
app:layout_constraintEnd_toStartOf="@id/button_download"
app:layout_constraintTop_toBottomOf="@id/textChangelog" />
app:layout_constraintTop_toBottomOf="@id/textSecondary" />
<Button
android:id="@+id/button_download"
@@ -72,7 +52,7 @@
android:layout_marginTop="12dp"
android:text="@string/download"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/textChangelog" />
app:layout_constraintTop_toBottomOf="@id/textSecondary" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -15,6 +15,12 @@
android:title="@string/delete"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_mark_current"
android:icon="@drawable/ic_eye_check"
android:title="@string/mark_as_current"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_select_range"
android:icon="@drawable/ic_select_range"
@@ -27,4 +33,4 @@
android:title="@android:string/selectAll"
app:showAsAction="ifRoom|withText" />
</menu>
</menu>

View File

@@ -11,7 +11,7 @@
<string name="open_menu">فتح قائمة</string>
<string name="chapters">فصول</string>
<string name="favourites">المفضلة</string>
<string name="network_error">تعذر الاتصال بالإنترنت</string>
<string name="network_error">‌خطاء في الشبكة</string>
<string name="loading_">جار التحميل…</string>
<string name="chapter_d_of_d">فصل %1$d في %2$d</string>
<string name="close">غلق</string>
@@ -54,4 +54,5 @@
<string name="add_new_category">قائمة جديدة</string>
<string name="download_complete">تم التنزيل</string>
<string name="text_clear_history_prompt">هل تريد محو سجل القراءة بالكامل بشكل دائم؟</string>
<string name="_s_deleted_from_local_storage">حذفت من تخزين محلي</string>
</resources>

View File

@@ -391,4 +391,7 @@
<string name="reader_control_ltr_summary">Tippe auf den rechten Rand oder drücke die rechte Taste, um immer zur nächsten Seite zu wechseln</string>
<string name="reader_slider">Seitenwechsel-Schieberegler anzeigen</string>
<string name="source_disabled">Quelle deaktiviert</string>
<string name="prefetch_content">Vorladen von Inhalten</string>
<string name="mark_as_current">Als aktuell markieren</string>
<string name="webtoon_zoom">Webtoon-Zoom</string>
</resources>

View File

@@ -398,4 +398,9 @@
<string name="network_unavailable_hint">Enciende la Wi-Fi o la red móvil para leer los mangas en línea</string>
<string name="source_disabled">Fuente desactivada</string>
<string name="prefetch_content">Precargar el contenido</string>
<string name="mark_as_current">Marcar como actual</string>
<string name="language">Idioma</string>
<string name="share_logs">Compartir los registros</string>
<string name="enable_logging_summary">Grabar algunas acciones para depurar</string>
<string name="enable_logging">Activar el registro</string>
</resources>

View File

@@ -396,4 +396,6 @@
<string name="server_error">Erreur côté serveur (%1$d). Veuillez réessayer plus tard</string>
<string name="clear_new_chapters_counters">Effacer aussi les informations sur les nouveaux chapitres</string>
<string name="source_disabled">Source désactivée</string>
<string name="prefetch_content">Préchargement du contenu</string>
<string name="mark_as_current">Marquer comme actuel</string>
</resources>

View File

@@ -59,7 +59,7 @@
<string name="list">Lista</string>
<string name="chapters">Capitoli</string>
<string name="details">Dettagli</string>
<string name="network_error">Errore di connessione alla rete</string>
<string name="network_error">Errore di connessione</string>
<string name="error_occurred">Si è verificato un errore</string>
<string name="history">Cronologia</string>
<string name="favourites">Preferiti</string>
@@ -267,7 +267,7 @@
<string name="suggestions_excluded_genres">Escludi generi</string>
<string name="removal_completed">Rimozione completata</string>
<string name="text_delete_local_manga_batch">Eliminare gli elementi selezionati dal dispositivo in modo permanente\?</string>
<string name="batch_manga_save_confirm">Vuoi davvero scaricare tutti i manga selezionati con tutti i loro capitoli\? Questa azione può consumare molto traffico e memoria</string>
<string name="batch_manga_save_confirm">Scaricare tutti i manga selezionati e i loro capitoli\? Questo può consumare molto traffico e spazio di archiviazione.</string>
<string name="parallel_downloads">Scaricamenti paralleli</string>
<string name="download_slowdown">Rallentamento dello scaricamento</string>
<string name="local_manga_processing">Elaborazione dei manga salvati</string>
@@ -322,4 +322,80 @@
<string name="not_found_404">Contenuto non trovato o rimosso</string>
<string name="compact">Compatto</string>
<string name="source_disabled">Fonte disabilitata</string>
<string name="text_shelf_holder_primary">I tuoi manga verrano visualizzati qui</string>
<string name="text_shelf_holder_secondary">Scopri cosa leggere nella sezione «Esplora»</string>
<string name="canceled">Annullato</string>
<string name="server_error">Errore lato server (%1$d). Riprovare più tardi</string>
<string name="clear_new_chapters_counters">Informazioni chiare anche sui nuovi capitoli</string>
<string name="prefetch_content">Precaricamento dei contenuti</string>
<string name="mark_as_current">Contrassegna come corrente</string>
<string name="error_no_space_left">Non c\'è più spazio sul dispositivo</string>
<string name="different_languages">Lingue diverse</string>
<string name="network_unavailable">La rete non è disponibile</string>
<string name="network_unavailable_hint">Attiva il Wi-Fi o la rete mobile per leggere i manga in linea</string>
<string name="webtoon_zoom">Zoom Webtoon</string>
<string name="account_already_exists">Questo account già esiste</string>
<string name="back">Indietro</string>
<string name="sync">Sincronizzazione</string>
<string name="clear_all_history">Cancella tutta la cronologia</string>
<string name="last_2_hours">Ultime 2 ore</string>
<string name="history_cleared">Cronologia cancellata</string>
<string name="manage">Gestisci</string>
<string name="no_bookmarks_yet">Non ci sono ancora segnalibri</string>
<string name="no_bookmarks_summary">È possibile creare segnalibri durante la lettura dei manga</string>
<string name="bookmarks_removed">Segnalibri rimossi</string>
<string name="no_manga_sources">Nessuna fonte manga</string>
<string name="no_manga_sources_text">Abilita le fonti manga per leggere manga in linea</string>
<string name="random">Casuale</string>
<string name="reorder">Riordina</string>
<string name="empty">Vuoto</string>
<string name="explore">Esplora</string>
<string name="incognito_mode">Modalità Incognito</string>
<string name="import_completed">Importazione completata</string>
<string name="import_completed_hint">È possibile eliminare il file originale dalla memoria per risparmiare spazio</string>
<string name="confirm_exit">Premi nuovamente Indietro per uscire</string>
<string name="exit_confirmation_summary">Premi due volte Indietro per uscire dall\'applicazione</string>
<string name="exit_confirmation">Conferma di uscita</string>
<string name="saved_manga">Manga salvati</string>
<string name="pages_cache">Cache delle pagine</string>
<string name="other_cache">Altra cache</string>
<string name="storage_usage">Utilizzo dello spazio di archiviazione</string>
<string name="available">Disponibile</string>
<string name="memory_usage_pattern">%s - %s</string>
<string name="enter_email_text">Inserisci il tuo indirizzo e-mail per continuare</string>
<string name="removed_from_favourites">Rimosso dai preferiti</string>
<string name="removed_from_s">Rimosso da «%s»</string>
<string name="options">Opzioni</string>
<string name="downloading_manga">Scarica manga</string>
<string name="app_update_available_s">Aggiornamento dell\'applicazione disponibile: %s</string>
<string name="no_chapters">Nessun capitolo</string>
<string name="automatic_scroll">Scorrimento automatico</string>
<string name="off_short">Diattivato</string>
<string name="seconds_pattern">%s s</string>
<string name="reader_info_pattern">Ca. %1$d/%2$d Pg. %3$d/%4$d</string>
<string name="comics_archive">Archivio fumetti</string>
<string name="folder_with_images">Cartella con immagini</string>
<string name="importing_manga">Importazione di manga</string>
<string name="import_will_start_soon">L\'importazione inizierà presto</string>
<string name="feed">Flusso</string>
<string name="reader_control_ltr_summary">Toccando il bordo destro o premendo il tasto destro si passa sempre alla pagina successiva</string>
<string name="contrast">Contrasto</string>
<string name="reset">Ripristina</string>
<string name="reader_slider">Mostra il cursore di cambio pagina</string>
<string name="color_correction">Correzione del colore</string>
<string name="email_enter_hint">Inserisci il tuo indirizzo e-mail per continuare</string>
<string name="sync_title">Sincronizza i tuoi dati</string>
<string name="changelog">Registro delle modifiche</string>
<string name="history_shortcuts">Mostra i collegamenti ai manga recenti</string>
<string name="color_correction_hint">Le impostazioni di colore scelte saranno ricordate per questo manga</string>
<string name="reader_control_ltr">Controllo ergonomico del lettore</string>
<string name="brightness">Luminosità</string>
<string name="categories_delete_confirm">Sei sicuro/a di voler eliminare le categorie preferite selezionate\?
\n Tutti i manga in esso contenuti andranno persi e questo non può essere annullato.</string>
<string name="history_shortcuts_summary">Rendere disponibili i manga recenti premendo a lungo sull\'icona dell\'applicazione</string>
<string name="webtoon_zoom_summary">Consenti gesto di zoom avanti/zoom indietro in modalità webtoon (beta)</string>
<string name="reader_info_bar">Mostra la barra delle informazioni nel lettore</string>
<string name="manga_error_description_pattern">Dettagli dell\'errore:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;1. Prova ad &lt;a href=%2$s&gt;aprire il manga in un browser web&lt;/a&gt; per assicurarsi che sia disponibile sulla sua fonte&lt;br&gt;2. Se è disponibile, inviare una segnalazione di errore agli sviluppatori.</string>
<string name="text_unsaved_changes_prompt">Salvare o eliminare le modifiche non salvate\?</string>
<string name="discard">Abbandona</string>
</resources>

View File

@@ -2,30 +2,34 @@
<resources>
<!-- From ThemeOverlay.Material3.DynamicColors.Dark -->
<style name="Theme.Kotatsu.Monet">
<item name="isMaterial3DynamicColorApplied">true</item>
<!-- Color palettes -->
<item name="colorPrimary">@android:color/system_accent1_200</item>
<item name="colorOnPrimary">@android:color/system_accent1_900</item>
<item name="colorPrimaryInverse">@android:color/system_accent1_600</item>
<item name="colorPrimaryContainer">@android:color/system_accent1_700</item>
<item name="colorOnPrimaryContainer">@android:color/system_accent1_100</item>
<item name="colorSecondary">@android:color/system_accent1_200</item>
<item name="colorOnSecondary">@android:color/system_neutral1_1000</item>
<item name="colorSecondaryContainer">@android:color/system_accent2_800</item>
<item name="colorOnSecondaryContainer">@android:color/system_accent2_100</item>
<item name="colorTertiary">@android:color/system_accent3_200</item>
<item name="colorOnTertiary">@android:color/system_accent3_800</item>
<item name="colorTertiaryContainer">@android:color/system_accent3_700</item>
<item name="colorOnTertiaryContainer">@android:color/system_accent3_100</item>
<item name="android:colorBackground">@android:color/system_neutral1_900</item>
<item name="colorOnBackground">@android:color/system_neutral1_200</item>
<item name="colorSurface">@android:color/system_neutral1_900</item>
<item name="colorOnSurface">@android:color/system_neutral1_100</item>
<item name="colorSurfaceVariant">@android:color/system_neutral2_700</item>
<item name="colorOnSurfaceVariant">@android:color/system_neutral2_200</item>
<item name="colorSurfaceInverse">@android:color/system_neutral1_100</item>
<item name="colorOnSurfaceInverse">@android:color/system_neutral1_800</item>
<item name="colorOutline">@android:color/system_neutral2_500</item>
<item name="colorPrimary">@color/m3_sys_color_dynamic_dark_primary</item>
<item name="colorOnPrimary">@color/m3_sys_color_dynamic_dark_on_primary</item>
<item name="colorPrimaryInverse">@color/m3_sys_color_dynamic_dark_inverse_primary</item>
<item name="colorPrimaryContainer">@color/m3_sys_color_dynamic_dark_primary_container</item>
<item name="colorOnPrimaryContainer">@color/m3_sys_color_dynamic_dark_on_primary_container</item>
<item name="colorSecondary">@color/m3_sys_color_dynamic_dark_secondary</item>
<item name="colorOnSecondary">@color/m3_sys_color_dynamic_dark_on_secondary</item>
<item name="colorSecondaryContainer">@color/m3_sys_color_dynamic_dark_secondary_container</item>
<item name="colorOnSecondaryContainer">@color/m3_sys_color_dynamic_dark_on_secondary_container</item>
<item name="colorTertiary">@color/m3_sys_color_dynamic_dark_tertiary</item>
<item name="colorOnTertiary">@color/m3_sys_color_dynamic_dark_on_tertiary</item>
<item name="colorTertiaryContainer">@color/m3_sys_color_dynamic_dark_tertiary_container</item>
<item name="colorOnTertiaryContainer">@color/m3_sys_color_dynamic_dark_on_tertiary_container</item>
<item name="android:colorBackground">@color/m3_sys_color_dynamic_dark_background</item>
<item name="colorOnBackground">@color/m3_sys_color_dynamic_dark_on_background</item>
<item name="colorSurface">@color/m3_sys_color_dynamic_dark_surface</item>
<item name="colorOnSurface">@color/m3_sys_color_dynamic_dark_on_surface</item>
<item name="colorSurfaceVariant">@color/m3_sys_color_dynamic_dark_surface_variant</item>
<item name="colorOnSurfaceVariant">@color/m3_sys_color_dynamic_dark_on_surface_variant</item>
<item name="colorSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_surface</item>
<item name="colorOnSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_on_surface</item>
<item name="colorOutline">@color/m3_sys_color_dynamic_dark_outline</item>
<item name="colorError">@color/m3_sys_color_dark_error</item>
<item name="colorOnError">@color/m3_sys_color_dark_on_error</item>
<item name="colorErrorContainer">@color/m3_sys_color_dark_error_container</item>
<item name="colorOnErrorContainer">@color/m3_sys_color_dark_on_error_container</item>
<!-- Default Framework Text Colors. -->
<item name="android:textColorPrimary">@color/m3_dynamic_dark_default_color_primary_text</item>
<item name="android:textColorPrimaryInverse">@color/m3_dynamic_default_color_primary_text</item>
@@ -40,6 +44,8 @@
<item name="android:textColorHighlight">@color/m3_dynamic_dark_highlighted_text</item>
<item name="android:textColorHighlightInverse">@color/m3_dynamic_highlighted_text</item>
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_dark_default_color_primary_text</item>
<!-- Fixes -->
<item name="bottomNavigationStyle">@style/Widget.Kotatsu.BottomNavigationView</item>
</style>
<style name="Theme.Kotatsu.AppWidgetContainer" parent="@android:style/Theme.DeviceDefault.DayNight">

View File

@@ -4,14 +4,14 @@
<string name="local_storage">Armazenamento local</string>
<string name="favourites">Favoritos</string>
<string name="error_occurred">Um erro ocorreu</string>
<string name="network_error">Não foi possível conectar à Internet</string>
<string name="network_error">Erro de rede</string>
<string name="details">Detalhes</string>
<string name="list">Lista</string>
<string name="detailed_list">Lista detalhada</string>
<string name="grid">Grade</string>
<string name="list_mode">Modo lista</string>
<string name="settings">Configurações</string>
<string name="loading_">Carregando</string>
<string name="loading_">A carregar</string>
<string name="chapter_d_of_d">Capítulo %1$d de %2$d</string>
<string name="try_again">Tente novamente</string>
<string name="clear_history">Limpar histórico</string>
@@ -27,9 +27,9 @@
<string name="share_s">Compartilhar %s</string>
<string name="search">Pesquisar</string>
<string name="search_manga">Pesquisar mangá</string>
<string name="manga_downloading_">Baixando</string>
<string name="download_complete">Baixado</string>
<string name="downloads">Downloads</string>
<string name="manga_downloading_">A descarregar</string>
<string name="download_complete">Descarregado</string>
<string name="downloads">Descargas</string>
<string name="by_name">Nome</string>
<string name="popular">Populares</string>
<string name="by_rating">Avaliação</string>
@@ -75,8 +75,8 @@
<string name="internal_storage">Armazenamento interno</string>
<string name="external_storage">Armazenamento externo</string>
<string name="domain">Domínio</string>
<string name="application_update">Verifique se há novas versões do aplicativo</string>
<string name="app_update_available">Uma nova versão do aplicativo está disponível</string>
<string name="application_update">Verifique se há novas versões da app</string>
<string name="app_update_available">Uma nova versão da app está disponível</string>
<string name="show_notification_app_update">Mostrar notificação se uma nova versão estiver disponível</string>
<string name="open_in_browser">Abrir no navegador da web</string>
<string name="large_manga_save_confirm">Este mangá tem %s. Salvar tudo isso\?</string>
@@ -102,8 +102,8 @@
<string name="chapters">Capítulos</string>
<string name="add_new_category">Nova categoria</string>
<string name="warning">Aviso</string>
<string name="text_delete_local_manga">Excluir «%s» do dispositivo permanentemente\?</string>
<string name="text_file_not_supported">Escolha um arquivo ZIP ou CBZ.</string>
<string name="text_delete_local_manga">Apagar «%s» do dispositivo permanentemente\?</string>
<string name="text_file_not_supported">Escolha um ficheiro ZIP ou CBZ.</string>
<string name="clear_search_history">Limpar histórico de pesquisa</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Ativado %1$d de %2$d</string>
<string name="notification_sound">Som de notificação</string>
@@ -133,7 +133,7 @@
<string name="check_for_updates">Verifique se há atualizações</string>
<string name="checking_for_updates">Verificando atualizações…</string>
<string name="no_update_available">Nenhuma atualização disponível</string>
<string name="right_to_left">Da direita para a esquerda (←)</string>
<string name="right_to_left">Da direita para a esquerda</string>
<string name="create_category">Nova categoria</string>
<string name="report_github">Criar problema no GitHub</string>
<string name="scale_mode">Modo de escala</string>
@@ -144,7 +144,7 @@
<string name="restore_backup">Restaurar do backup</string>
<string name="data_restored">Restaurado</string>
<string name="preparing_">Preparando…</string>
<string name="file_not_found">Arquivo não encontrado</string>
<string name="file_not_found">Ficheiro não encontrado</string>
<string name="data_restored_success">Todos os dados foram restaurados</string>
<string name="data_restored_with_errors">Os dados foram restaurados, mas há erros</string>
<string name="just_now">Agora mesmo</string>
@@ -166,11 +166,11 @@
<string name="default_s">Padrão: %s</string>
<string name="_and_x_more">…e %1$d mais</string>
<string name="next">Próximo</string>
<string name="protect_application_subtitle">Digite a senha que será necessária quando o aplicativo for iniciado</string>
<string name="protect_application_subtitle">Digite a senha que será necessária quando a app for iniciado</string>
<string name="confirm">Confirme</string>
<string name="password_length_hint">A senha deve ter 4 caracteres ou mais</string>
<string name="backup_saved">Backup salvo</string>
<string name="tracker_warning">Alguns dispositivos têm um comportamento de sistema diferente, o que pode interromper as tarefas em segundo plano.</string>
<string name="tracker_warning">Alguns aparelhos têm um comportamento de sistema diferente, o que pode interromper as tarefas em segundo plano.</string>
<string name="read_more">Leia mais</string>
<string name="search_only_on_s">Pesquise apenas em %s</string>
<string name="other">Outros</string>
@@ -179,7 +179,7 @@
<string name="enabled_sources">Fontes usadas</string>
<string name="queued">Enfileirado</string>
<string name="text_downloads_holder">Nenhum download ativo</string>
<string name="error_empty_name">Você deve inserir um nome</string>
<string name="error_empty_name">Deve inserir um nome</string>
<string name="about_app_translation_summary">Traduzir esta aplicação</string>
<string name="about_feedback">Comentar</string>
<string name="about_feedback_4pda">Tópico no 4PDA</string>
@@ -189,20 +189,20 @@
<string name="auth_not_supported_by">O login em %s não é suportado</string>
<string name="genres">Gêneros</string>
<string name="about_app_translation">Tradução</string>
<string name="text_clear_cookies_prompt">Você será desconectado de todas as fontes</string>
<string name="text_clear_cookies_prompt">Será desconectado de todas as fontes</string>
<string name="vibration">Vibração</string>
<string name="cannot_find_available_storage">Sem armazenamento disponível</string>
<string name="favourites_categories">Categorias favoritas</string>
<string name="category_delete_confirm">Remover a categoria \"%s\" dos seus favoritos\?
\nTodos os mangás nela serão perdidos.</string>
<string name="text_history_holder_secondary">Encontre o que ler no menu lateral.</string>
<string name="text_local_holder_secondary">Salve-o de fontes online ou importe arquivos.</string>
<string name="text_local_holder_secondary">Salve-o de fontes online ou importe fiheiros.</string>
<string name="recent_manga">Recente</string>
<string name="other_storage">Outro armazenamento</string>
<string name="text_search_holder_secondary">Tente reformular a consulta.</string>
<string name="not_available">Não disponível</string>
<string name="size_s">Tamanho: %s</string>
<string name="text_history_holder_primary">O que você ler será exibido aqui</string>
<string name="text_history_holder_primary">O que ler será exibido aqui</string>
<string name="text_local_holder_primary">Salve algo primeiro</string>
<string name="pages_animation">Animação de página</string>
<string name="favourites_category_empty">Categoria vazia</string>
@@ -211,36 +211,36 @@
<string name="all_favourites">Todos os favoritos</string>
<string name="waiting_for_network">À espera de rede…</string>
<string name="search_results">Resultados da pesquisa</string>
<string name="text_feed_holder">Novos capítulos do que você está lendo são mostrados aqui</string>
<string name="text_feed_holder">Novos capítulos do que são mostrados aqui</string>
<string name="new_version_s">Nova versão: %s</string>
<string name="rotate_screen">Girar a tela</string>
<string name="rotate_screen">Girar o ecrã</string>
<string name="update_check_failed">Não foi possível procurar atualizações</string>
<string name="protect_application">Proteja o aplicativo</string>
<string name="protect_application">Proteja a app</string>
<string name="protect_application_summary">Peça a senha ao iniciar o Kotatsu</string>
<string name="zoom_mode_fit_height">Ajustar à altura</string>
<string name="black_dark_theme">Escuro</string>
<string name="black_dark_theme_summary">Usa menos energia em telas AMOLED</string>
<string name="reader_mode_hint">A configuração escolhida será lembrada para este mangá</string>
<string name="backup_information">Você pode criar backup de seu histórico e favoritos e restaurá-lo</string>
<string name="backup_information">Pode criar um backup do seu histórico e favoritos e restaurá-lo</string>
<string name="clear_cookies">Limpar cookies</string>
<string name="text_clear_search_history_prompt">Remover todas as consultas de pesquisa recentes permanentemente\?</string>
<string name="auth_required">Faça login para ver este conteúdo</string>
<string name="text_categories_holder">Você pode usar categorias para organizar seus favoritos. Pressione «+» para criar uma categoria</string>
<string name="text_categories_holder">Pode usar categorias para organizar os seus favoritos. Pressione «+» para criar uma categoria</string>
<string name="manga_save_location">Pasta para downloads</string>
<string name="exclude_nsfw_from_history">Excluir mangá NSFW do histórico</string>
<string name="date_format">Formato da data</string>
<string name="system_default">Padrão</string>
<string name="dynamic_theme">Tema dinâmico</string>
<string name="dynamic_theme_summary">Aplica um tema criado no esquema de cores do seu papel de parede</string>
<string name="computing_">Computando</string>
<string name="computing_">A computar</string>
<string name="importing_progress">Importando mangá: %1$d de %2$d</string>
<string name="screenshots_allow">Permitir</string>
<string name="screenshots_block_nsfw">Bloquear no NSFW</string>
<string name="screenshots_policy">Política de captura de tela</string>
<string name="screenshots_policy">Política de captura de ecrã</string>
<string name="screenshots_block_all">Sempre bloquear</string>
<string name="suggestions_summary">Sugira mangá com base em suas preferências</string>
<string name="suggestions_info">Todos os dados são analisados localmente neste dispositivo. Não há transferência de seus dados pessoais para nenhum serviço</string>
<string name="text_suggestion_holder">Comece a ler mangá e você receberá sugestões personalizadas</string>
<string name="suggestions_summary">Sugira mangá com base nas suas preferências</string>
<string name="suggestions_info">Todos os dados são analisados localmente neste dispositivo. Não há transferência dos seus dados pessoais para nenhum serviço</string>
<string name="text_suggestion_holder">Comece a ler mangá e receberá sugestões personalizadas</string>
<string name="suggestions">Sugestões</string>
<string name="suggestions_enable">Ativar sugestões</string>
<string name="exclude_nsfw_from_suggestions">Não sugira mangá NSFW</string>
@@ -248,7 +248,7 @@
<string name="disabled">Desabilitado</string>
<string name="filter_load_error">Não foi possível carregar a lista de gêneros</string>
<string name="only_using_wifi">Somente em Wi-Fi</string>
<string name="onboard_text">Selecione os idiomas que você deseja ler mangá. Você pode alterá-lo mais tarde nas configurações.</string>
<string name="onboard_text">Selecione os idiomas que deseja ler mangá. Pode alterá-lo mais tarde nas configurações.</string>
<string name="find_genre">Encontrar gênero</string>
<string name="always">Sempre</string>
<string name="reset_filter">Redefinir filtro</string>
@@ -263,6 +263,137 @@
<string name="content">Conteúdo</string>
<string name="chapters_empty">Não há capítulos nesta manga</string>
<string name="percent_string_pattern">%1$s%%</string>
<string name="text_shelf_holder_primary">Seu mangá será exibido aqui</string>
<string name="text_shelf_holder_primary">O seu mangá será exibido aqui</string>
<string name="color_correction">Correção de cor</string>
<string name="server_error">Erro do lado do servidor (%1$d). Por favor, tente novamente mais tarde</string>
<string name="clear_new_chapters_counters">Também limpar informações sobre capítulos novos</string>
<string name="hide">Esconder</string>
<string name="text_delete_local_manga_batch">Apagar itens selecionados do aparelho permanentemente\?</string>
<string name="compact">Compactar</string>
<string name="bookmark_added">Marcador adicionado</string>
<string name="prefetch_content">Pré-carregamento de conteúdo</string>
<string name="invalid_domain_message">Endereço inválido</string>
<string name="use_fingerprint">Usar impressão digital, se disponível</string>
<string name="appwidget_shelf_description">Mangás dos seus favoritos</string>
<string name="appwidget_recent_description">Os seus mangás recentemente lidos</string>
<string name="suggestions_excluded_genres_summary">Especifique os gêneros que não deseja ver nas sugestões</string>
<string name="removal_completed">Remoção concluída</string>
<string name="check_new_chapters_title">Verifique se há novos capítulos e notifique sobre isso</string>
<string name="default_mode">Modo padrão</string>
<string name="mark_as_current">Marcar como atual</string>
<string name="error_no_space_left">Não há espaço disponível no aparelho</string>
<string name="different_languages">Idiomas diferentes</string>
<string name="network_unavailable">A rede não está disponível</string>
<string name="network_unavailable_hint">Ative o Wi-Fi ou a rede móvel para ler mangá online</string>
<string name="parallel_downloads">Descargas paralelas</string>
<string name="name">Nome</string>
<string name="logout">Terminar sessão</string>
<string name="edit">Editar</string>
<string name="edit_category">Editar categoria</string>
<string name="tracking">Monitoramento</string>
<string name="empty_favourite_categories">Nenhuma categoria favorita</string>
<string name="removed_from_history">Removido do histórico</string>
<string name="send">Enviar</string>
<string name="batch_manga_save_confirm">Descarregar todos os mangás selecionados e os capítulos deles\? Isso pode consumir muito tráfego e armazenamento.</string>
<string name="text_shelf_holder_secondary">Encontre o que ler na secção &lt;«Explorar»</string>
<string name="suggestions_excluded_genres">Excluir gêneros</string>
<string name="download_slowdown">Lentidão de descarga</string>
<string name="download_slowdown_summary">Ajuda a evitar o bloqueio do seu endereço IP</string>
<string name="local_manga_processing">Processamento de mangá gravado</string>
<string name="chapters_will_removed_background">Os capítulos serão removidos em segundo plano. Pode levar algum tempo</string>
<string name="canceled">Cancelado</string>
<string name="email_enter_hint">Digite o seu e-mail para continuar</string>
<string name="new_sources_text">Novas fontes de mangá estão disponíveis</string>
<string name="show_notification_new_chapters_on">Receberá notificações sobre atualizações do mangá que está lendo</string>
<string name="notifications_enable">Ativar notificações</string>
<string name="crash_text">Algo deu errado. Por favor, envie um relatório de bug para ajudar os programadores a consertarem isso.</string>
<string name="status_planned">Planejado</string>
<string name="status_reading">Lendo</string>
<string name="status_re_reading">Relendo</string>
<string name="status_completed">Concluído</string>
<string name="status_on_hold">Em espera</string>
<string name="show_reading_indicators">Mostrar indicadores de progresso de leitura</string>
<string name="data_deletion">Exclusão de dados</string>
<string name="clear_cookies_summary">Pode ajudar no caso de alguns problemas. Todas as autorizações serão invalidadas</string>
<string name="show_all">Mostrar tudo</string>
<string name="clear_all_history">Limpar todo o histórico</string>
<string name="last_2_hours">Ultimas 2 horas</string>
<string name="categories_delete_confirm">Tem certeza que deseja apagar as categorias favoritas selecionadas\?
\nTodos os mangás serão perdidos e isso não pode ser desfeito.</string>
<string name="reorder">Reordenar</string>
<string name="empty">Vazio</string>
<string name="changelog">Registo de alterações</string>
<string name="explore">Explorar</string>
<string name="comics_archive">Arquivo de banda desenhada</string>
<string name="folder_with_images">Pasta com imagens</string>
<string name="importing_manga">Importando mangá(s)</string>
<string name="saved_manga">Mangás gravados</string>
<string name="history_shortcuts">Mostrar atalhos de mangás recentes</string>
<string name="history_shortcuts_summary">Torne os mangás recentes visíveis pressionando o ícone da aplicação</string>
<string name="brightness">Luminosidade</string>
<string name="contrast">Contraste</string>
<string name="reset">Redefinir</string>
<string name="text_unsaved_changes_prompt">Gravar ou descartar alterações não gravadas\?</string>
<string name="select_range">Selecionar intervalo</string>
<string name="reader_slider">Mostrar controle deslizante de troca de página</string>
<string name="source_disabled">Fonte desativada</string>
<string name="account_already_exists">Essa conta já existe</string>
<string name="back">Voltar</string>
<string name="sync">Sincronização</string>
<string name="sync_title">Sincronize os seus dados</string>
<string name="show_notification_new_chapters_off">Não receberá notificações, mas novos capítulos serão destacados nas listas</string>
<string name="bookmark_add">Adicionar marcador</string>
<string name="bookmark_remove">Remover marcador</string>
<string name="bookmarks">Marcadores</string>
<string name="bookmark_removed">Marcador removido</string>
<string name="undo">Desfazer</string>
<string name="dns_over_https">DNS sobre HTTPS</string>
<string name="detect_reader_mode">Detecção automática do modo de leitura</string>
<string name="detect_reader_mode_summary">Detetar automaticamente se o mangá é webtoon</string>
<string name="disable_battery_optimization">Desative a otimização da bateria</string>
<string name="disable_battery_optimization_summary">Ajuda com verificações de atualizações em segundo plano</string>
<string name="status_dropped">Desistido</string>
<string name="disable_all">Desativar tudo</string>
<string name="report">Reportar</string>
<string name="show_reading_indicators_summary">Mostrar percentual de leitura no histórico e nos favoritos</string>
<string name="exclude_nsfw_from_history_summary">Mangás marcados como +18 nunca serão adicionados ao histórico e o seu progresso não sera gravado</string>
<string name="history_cleared">Histórico apagado</string>
<string name="manage">Gerir</string>
<string name="no_bookmarks_yet">Sem páginas marcadas ainda</string>
<string name="no_bookmarks_summary">Pode criar um marcador de página enquanto lé o mangá</string>
<string name="bookmarks_removed">Marcadores de página removidos</string>
<string name="no_manga_sources">Sem fontes de mangás</string>
<string name="no_manga_sources_text">Ative as fontes de mangá para ler online</string>
<string name="random">Aleatório</string>
<string name="confirm_exit">Pressione Voltar novamente para sair</string>
<string name="exit_confirmation_summary">Pressione Voltar duas vezes para sair do app</string>
<string name="exit_confirmation">Confirmação de saída</string>
<string name="pages_cache">Cache de páginas</string>
<string name="other_cache">Outro cache</string>
<string name="storage_usage">Uso de armazenamento</string>
<string name="available">Disponível</string>
<string name="memory_usage_pattern">%s - %s</string>
<string name="enter_email_text">Digite o seu e-mail para continuar</string>
<string name="removed_from_favourites">Removido dos favoritos</string>
<string name="removed_from_s">Removido de \"%s\"</string>
<string name="options">Opções</string>
<string name="not_found_404">Conteúdo não encontrado ou removido</string>
<string name="downloading_manga">A descarrgar mangá</string>
<string name="incognito_mode">Modo anônimo</string>
<string name="app_update_available_s">Atualização da aplicação disponível: %s</string>
<string name="no_chapters">Sem capítulos</string>
<string name="automatic_scroll">Rolagem automática</string>
<string name="off_short">Desligado</string>
<string name="seconds_pattern">%ss</string>
<string name="reader_info_pattern">Cap. %1$d/%2$d Pág. %3$d/%4$d</string>
<string name="reader_info_bar">Mostrar barra de informações no leitor</string>
<string name="import_completed">Importação completa</string>
<string name="import_completed_hint">Pode apagar o ficheiro original do aparelho para poupar espaço</string>
<string name="import_will_start_soon">A importação começará em breve</string>
<string name="feed">Feed</string>
<string name="manga_error_description_pattern">Detalhes do erro:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Tente &lt;a href=%2$s&gt;abrir o mangá num navegador de internet&lt;/a&gt;para garantir que ele está disponível na fonte&lt;br&gt;2. Se estiver disponível, envie um relatório de erro para os programadores.</string>
<string name="reader_control_ltr_summary">Tocar na borda direita ou pressionar a tecla direita sempre passa para a próxima página</string>
<string name="reader_control_ltr">Controle de leitura ergonômico</string>
<string name="color_correction_hint">As configurações de cor escolhidas serão lembradas para esse mangá</string>
<string name="discard">Descartar</string>
</resources>

View File

@@ -398,4 +398,9 @@
<string name="compact">Компактно</string>
<string name="source_disabled">Источник отключен</string>
<string name="prefetch_content">Предварительная загрузка содержимого</string>
<string name="mark_as_current">Пометить как текущую</string>
<string name="language">Язык</string>
<string name="share_logs">Поделиться логами</string>
<string name="enable_logging">Включить логирование</string>
<string name="enable_logging_summary">Записывать некоторые действия для отладки</string>
</resources>

View File

@@ -396,4 +396,10 @@
<string name="history_shortcuts_summary">Uygulama simgesine uzun basarak son mangaları kullanılabilir hale getirin</string>
<string name="reader_control_ltr_summary">Sağ kenara dokunulduğunda veya sağ tuşa basıldığında her zaman bir sonraki sayfaya geçilir</string>
<string name="source_disabled">Kaynak devre dışı</string>
<string name="prefetch_content">İçerik ön yüklemesi</string>
<string name="mark_as_current">Geçerli olarak işaretle</string>
<string name="language">Dil</string>
<string name="share_logs">Günlükleri paylaş</string>
<string name="enable_logging">Günlük kaydını etkinleştir</string>
<string name="enable_logging_summary">Hata ayıklama amacıyla bazı eylemleri kaydedin</string>
</resources>

View File

@@ -2,7 +2,7 @@
<resources>
<plurals name="new_chapters">
<item quantity="one">%1$d новий розділ</item>
<item quantity="few">%1$d нових розділи</item>
<item quantity="few">%1$d нові розділи</item>
<item quantity="many">%1$d нових розділів</item>
<item quantity="other">%1$d нових розділів</item>
</plurals>

View File

@@ -82,8 +82,8 @@
<string name="internal_storage">Внутрішнє сховище</string>
<string name="external_storage">Зовнішнє сховище</string>
<string name="domain">Домен</string>
<string name="application_update">Перевірити наявність нових версій додатка</string>
<string name="app_update_available">Доступна нова версія додатка</string>
<string name="application_update">Перевірити наявність нових версій застосунку</string>
<string name="app_update_available">Доступна нова версія застосунку</string>
<string name="large_manga_save_confirm">Ця манґа має %s. Зберегти все це\?</string>
<string name="save_manga">Зберегти</string>
<string name="notifications">Сповіщення</string>
@@ -125,11 +125,11 @@
<string name="track_sources">Стежити за оновленнями</string>
<string name="dont_check">Не перевіряти</string>
<string name="wrong_password">Неправильний пароль</string>
<string name="protect_application">Захистити додаток</string>
<string name="protect_application">Захистити застосунок</string>
<string name="protect_application_summary">Запитувати пароль під час запуску Kotatsu</string>
<string name="repeat_password">Повторіть пароль</string>
<string name="passwords_mismatch">Паролі не співпадають</string>
<string name="about">Про програму</string>
<string name="about">Про застосунок</string>
<string name="app_version">Версія %s</string>
<string name="check_for_updates">Перевірити наявність оновлень</string>
<string name="checking_for_updates">Перевірка наявності оновлень…</string>
@@ -165,7 +165,7 @@
<string name="default_s">За замовчуванням: %s</string>
<string name="_and_x_more">…і ще %1$d</string>
<string name="next">Далі</string>
<string name="protect_application_subtitle">Введіть пароль для запуску програми</string>
<string name="protect_application_subtitle">Введіть пароль для запуску застосунку</string>
<string name="confirm">Підтвердити</string>
<string name="password_length_hint">Пароль має містити 4 символи або більше</string>
<string name="search_only_on_s">Пошук лише на %s</string>
@@ -174,7 +174,7 @@
<string name="read_more">Докладніше</string>
<string name="queued">У черзі</string>
<string name="text_downloads_holder">Немає активних завантажень</string>
<string name="about_app_translation_summary">Допомогти з перекладом програми</string>
<string name="about_app_translation_summary">Допомогти з перекладом застосунку</string>
<string name="about_app_translation">Переклад</string>
<string name="about_feedback_4pda">Тема на 4PDA</string>
<string name="auth_complete">Авторизація виконана</string>
@@ -187,7 +187,7 @@
<string name="error_empty_name">Ви повинні ввести ім’я</string>
<string name="show_pages_numbers">Показувати номери сторінок</string>
<string name="enabled_sources">Включені джерела</string>
<string name="dynamic_theme_summary">Застосовує тему програми, засновану на палітрі кольорів шпалер на пристрої</string>
<string name="dynamic_theme_summary">Застосовує тему застосунку, засновану на палітрі кольорів шпалер на пристрої</string>
<string name="importing_progress">Імпорт манґи: %1$d з %2$d</string>
<string name="screenshots_policy">Політика щодо знімків екрана</string>
<string name="screenshots_allow">Дозволити</string>
@@ -346,7 +346,7 @@
<string name="options">Параметри</string>
<string name="downloading_manga">Завантаження манґи</string>
<string name="incognito_mode">Режим інкогніто</string>
<string name="app_update_available_s">Доступне оновлення програми: %s</string>
<string name="app_update_available_s">Доступне оновлення застосунку: %s</string>
<string name="no_chapters">Немає розділів</string>
<string name="automatic_scroll">Автоматична прокрутка</string>
<string name="off_short">Викл.</string>
@@ -367,7 +367,7 @@
<string name="sync">Синхронізація</string>
<string name="clear_all_history">Очистити всю історію</string>
<string name="last_2_hours">Останні 2 години</string>
<string name="exit_confirmation_summary">Двічі натисніть Назад, щоб вийти з програми</string>
<string name="exit_confirmation_summary">Двічі натисніть Назад, щоб вийти зі застосунку</string>
<string name="exit_confirmation">Підтвердження виходу</string>
<string name="enter_email_text">Введіть електронну пошту, щоб продовжити</string>
<string name="removed_from_favourites">Видалено з уподобань</string>
@@ -375,7 +375,7 @@
<string name="memory_usage_pattern">%s - %s</string>
<string name="manga_error_description_pattern">Деталі помилки:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Спробуйте &lt;a href=%2$s&gt;відкрити манґу у веб-браузері&lt;/a&gt;, щоб переконатися, що вона доступна в джерелі&lt;br&gt;2. Якщо вона доступна, то надішліть звіт про помилку розробникам.</string>
<string name="history_shortcuts">Показувати ярлики останньої прочитаної манґи</string>
<string name="history_shortcuts_summary">Зробити нещодавно прочитану манґу доступною за довгим натисканням на іконку програми</string>
<string name="history_shortcuts_summary">Зробити нещодавно прочитану манґу доступною за довгим натисканням на іконку застосунку</string>
<string name="reader_control_ltr_summary">Натискання на правий край або натискання правої клавіші завжди переходить на наступну сторінку</string>
<string name="reader_control_ltr">Ергономічне керування режимом читання</string>
<string name="brightness">Яскравість</string>
@@ -397,4 +397,6 @@
<string name="compact">Компактно</string>
<string name="prefetch_content">Передвчасне завантаження контенту</string>
<string name="source_disabled">Джерело відключено</string>
<string name="mark_as_current">Позначити як актуальне</string>
<string name="language">Мова</string>
</resources>

View File

@@ -2,30 +2,34 @@
<resources>
<!-- From ThemeOverlay.Material3.DynamicColors.Light -->
<style name="Theme.Kotatsu.Monet">
<item name="isMaterial3DynamicColorApplied">true</item>
<!-- Color palettes -->
<item name="colorPrimary">@android:color/system_accent1_500</item>
<item name="colorOnPrimary">@android:color/system_accent1_0</item>
<item name="colorPrimaryInverse">@android:color/system_accent1_200</item>
<item name="colorPrimaryContainer">@android:color/system_accent1_100</item>
<item name="colorOnPrimaryContainer">@android:color/system_accent1_900</item>
<item name="colorSecondary">@android:color/system_neutral1_600</item>
<item name="colorOnSecondary">@android:color/system_neutral1_0</item>
<item name="colorSecondaryContainer">@android:color/system_accent2_100</item>
<item name="colorOnSecondaryContainer">@android:color/system_accent1_900</item>
<item name="colorTertiary">@android:color/system_accent3_500</item>
<item name="colorOnTertiary">@android:color/system_accent3_50</item>
<item name="colorTertiaryContainer">@android:color/system_accent3_100</item>
<item name="colorOnTertiaryContainer">@android:color/system_accent3_900</item>
<item name="android:colorBackground">@android:color/system_neutral1_50</item>
<item name="colorOnBackground">@android:color/system_neutral1_900</item>
<item name="colorSurface">@android:color/system_neutral1_50</item>
<item name="colorOnSurface">@android:color/system_neutral1_900</item>
<item name="colorSurfaceVariant">@android:color/system_neutral2_100</item>
<item name="colorOnSurfaceVariant">@android:color/system_neutral2_700</item>
<item name="colorSurfaceInverse">@android:color/system_neutral1_800</item>
<item name="colorOnSurfaceInverse">@android:color/system_neutral1_50</item>
<item name="colorOutline">@android:color/system_neutral2_500</item>
<item name="colorPrimary">@color/m3_sys_color_dynamic_light_primary</item>
<item name="colorOnPrimary">@color/m3_sys_color_dynamic_light_on_primary</item>
<item name="colorPrimaryInverse">@color/m3_sys_color_dynamic_light_inverse_primary</item>
<item name="colorPrimaryContainer">@color/m3_sys_color_dynamic_light_primary_container</item>
<item name="colorOnPrimaryContainer">@color/m3_sys_color_dynamic_light_on_primary_container</item>
<item name="colorSecondary">@color/m3_sys_color_dynamic_light_secondary</item>
<item name="colorOnSecondary">@color/m3_sys_color_dynamic_light_on_secondary</item>
<item name="colorSecondaryContainer">@color/m3_sys_color_dynamic_light_secondary_container</item>
<item name="colorOnSecondaryContainer">@color/m3_sys_color_dynamic_light_on_secondary_container</item>
<item name="colorTertiary">@color/m3_sys_color_dynamic_light_tertiary</item>
<item name="colorOnTertiary">@color/m3_sys_color_dynamic_light_on_tertiary</item>
<item name="colorTertiaryContainer">@color/m3_sys_color_dynamic_light_tertiary_container</item>
<item name="colorOnTertiaryContainer">@color/m3_sys_color_dynamic_light_on_tertiary_container</item>
<item name="android:colorBackground">@color/m3_sys_color_dynamic_light_background</item>
<item name="colorOnBackground">@color/m3_sys_color_dynamic_light_on_background</item>
<item name="colorSurface">@color/m3_sys_color_dynamic_light_surface</item>
<item name="colorOnSurface">@color/m3_sys_color_dynamic_light_on_surface</item>
<item name="colorSurfaceVariant">@color/m3_sys_color_dynamic_light_surface_variant</item>
<item name="colorOnSurfaceVariant">@color/m3_sys_color_dynamic_light_on_surface_variant</item>
<item name="colorSurfaceInverse">@color/m3_sys_color_dynamic_light_inverse_surface</item>
<item name="colorOnSurfaceInverse">@color/m3_sys_color_dynamic_light_inverse_on_surface</item>
<item name="colorOutline">@color/m3_sys_color_dynamic_light_outline</item>
<item name="colorError">@color/m3_sys_color_light_error</item>
<item name="colorOnError">@color/m3_sys_color_light_on_error</item>
<item name="colorErrorContainer">@color/m3_sys_color_light_error_container</item>
<item name="colorOnErrorContainer">@color/m3_sys_color_light_on_error_container</item>
<!-- Default Framework Text Colors. -->
<item name="android:textColorPrimary">@color/m3_dynamic_default_color_primary_text</item>
<item name="android:textColorPrimaryInverse">@color/m3_dynamic_dark_default_color_primary_text</item>
@@ -40,6 +44,8 @@
<item name="android:textColorHighlight">@color/m3_dynamic_highlighted_text</item>
<item name="android:textColorHighlightInverse">@color/m3_dynamic_dark_highlighted_text</item>
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_default_color_primary_text</item>
<!-- Fixes -->
<item name="bottomNavigationStyle">@style/Widget.Kotatsu.BottomNavigationView</item>
</style>
<style name="Theme.Kotatsu.AppWidgetContainer" parent="@android:style/Theme.DeviceDefault.DayNight">

View File

@@ -396,4 +396,10 @@
<string name="server_error">服务器端错误 (%1$d)。请稍后再试</string>
<string name="compact">紧凑</string>
<string name="source_disabled">已禁用图源</string>
<string name="prefetch_content">内容预加载</string>
<string name="mark_as_current">标为当前</string>
<string name="language">语言</string>
<string name="enable_logging">启用日志记录</string>
<string name="share_logs">分享日志</string>
<string name="enable_logging_summary">出于调试目的记录某些操作</string>
</resources>

View File

@@ -400,4 +400,10 @@
<string name="compact">Compact</string>
<string name="source_disabled">Source disabled</string>
<string name="prefetch_content">Content preloading</string>
<string name="mark_as_current">Mark as current</string>
<string name="language">Language</string>
<string name="share_logs">Share logs</string>
<string name="enable_logging">Enable logging</string>
<string name="enable_logging_summary">Record some actions for debug purposes</string>
<string name="show_suspicious_content">Show suspicious content</string>
</resources>

View File

@@ -12,6 +12,10 @@
<item name="labelVisibilityMode">labeled</item>
</style>
<style name="Widget.Kotatsu.BottomNavigationView.ColoredIndicators">
<item name="itemActiveIndicatorStyle">@style/Widget.Kotatsu.BottomNavigationView.ActiveIndicator</item>
</style>
<style name="Widget.Kotatsu.BottomNavigationView.ActiveIndicator" parent="Widget.Material3.BottomNavigationView.ActiveIndicator">
<item name="android:color">@color/bottom_menu_active_indicator</item>
</style>
@@ -24,10 +28,9 @@
<!--AlertDialog-->
<style name="ThemeOverlay.Kotatsu.MaterialAlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
<item name="android:background">?attr/colorSurface</item>
<item name="android:textColorPrimary">?attr/colorOnSurface</item>
<item name="android:textColor">?attr/colorOnSurface</item>
<item name="dialogCornerRadius">28dp</item>
<item name="android:layout">@layout/m3_alert_dialog</item>
<item name="android:background">@drawable/m3_popup_background</item>
<item name="dialogCornerRadius">@dimen/m3_alert_dialog_corner_size</item>
</style>
<!-- Bottom sheet -->

View File

@@ -33,13 +33,11 @@
<item name="colorError">@color/error</item>
<item name="colorOnError">@color/onError</item>
<item name="colorErrorContainer">@color/errorContainer</item>
<item name="colorControlHighlight">?attr/colorSurfaceVariant</item>
<item name="colorOnErrorContainer">@color/onErrorContainer</item>
<item name="android:divider">@color/divider_default</item>
<!-- Ripples -->
<item name="colorControlHighlight">?attr/colorSurfaceVariant</item>
<!-- Handles RTL text -->
<item name="android:textAlignment">gravity</item>
<item name="android:textDirection">locale</item>
@@ -67,7 +65,7 @@
<item name="textInputStyle">@style/Widget.Material3.TextInputLayout.OutlinedBox</item>
<item name="toolbarStyle">@style/Widget.Material3.Toolbar</item>
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item>
<item name="bottomNavigationStyle">@style/Widget.Kotatsu.BottomNavigationView</item>
<item name="bottomNavigationStyle">@style/Widget.Kotatsu.BottomNavigationView.ColoredIndicators</item>
<item name="tabStyle">@style/Widget.Kotatsu.Tabs</item>
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Filled</item>
<item name="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</item>

View File

@@ -14,16 +14,16 @@
<locale android:name="in" />
<locale android:name="it" />
<locale android:name="ja" />
<locale android:name="nb-rNO" />
<locale android:name="nb-NO" />
<locale android:name="pl" />
<locale android:name="pt" />
<locale android:name="pt-rBR" />
<locale android:name="pt-BR" />
<locale android:name="ru" />
<locale android:name="si" />
<locale android:name="sr" />
<locale android:name="sv" />
<locale android:name="tr" />
<locale android:name="uk" />
<locale android:name="zh-rCN" />
<locale android:name="zh-rTW" />
<locale android:name="zh-CN" />
<locale android:name="zh-TW" />
</locale-config>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/app_name">
@@ -9,10 +10,22 @@
android:persistent="false"
android:summary="@string/check_for_updates" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="logging"
android:summary="@string/enable_logging_summary"
android:title="@string/enable_logging" />
<Preference
android:dependency="logging"
android:key="logs_share"
android:title="@string/share_logs" />
<Preference
android:key="about_app_translation"
android:summary="@string/about_app_translation_summary"
android:title="@string/about_app_translation" />
android:title="@string/about_app_translation"
app:allowDividerAbove="true" />
<org.koitharu.kotatsu.settings.utils.AboutLinksPreference />

View File

@@ -24,6 +24,10 @@
android:summary="@string/black_dark_theme_summary"
android:title="@string/black_dark_theme" />
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
android:key="app_locale"
android:title="@string/language" />
<ListPreference
android:key="date_format"
android:title="@string/date_format" />
@@ -56,4 +60,4 @@
android:summary="@string/protect_application_summary"
android:title="@string/protect_application" />
</PreferenceScreen>
</PreferenceScreen>

View File

@@ -1,16 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath 'com.android.tools.build:gradle:7.4.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

View File

@@ -1,7 +1,7 @@
#Wed Aug 24 10:43:54 EEST 2022
#Fri Jan 20 14:35:39 EET 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
zipStorePath=wrapper/dists