Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e11e890818 | ||
|
|
3e7a48d27a | ||
|
|
eeba959ba5 | ||
|
|
e7fa1036be | ||
|
|
542a7e1141 | ||
|
|
5951f4438a | ||
|
|
1fbae6bd7b | ||
|
|
b73924aea8 | ||
|
|
005443f4ae | ||
|
|
abb55d4424 | ||
|
|
e0538da079 | ||
|
|
665bf5a034 | ||
|
|
dc7e1282c6 | ||
|
|
3a877d4f4a | ||
|
|
8a23c9a327 | ||
|
|
452c0edfc7 | ||
|
|
2b9307aa17 | ||
|
|
f91d5e1c29 | ||
|
|
2fbfd14252 | ||
|
|
c09dd92cff | ||
|
|
6b08074a70 | ||
|
|
9cb5971182 | ||
|
|
6f37d95c24 | ||
|
|
d290ba24b7 | ||
|
|
f57d23026b | ||
|
|
1a70ccff55 | ||
|
|
a9c122b144 |
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@@ -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$" />
|
||||
|
||||
@@ -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 510
|
||||
versionName '4.2.1'
|
||||
versionCode 512
|
||||
versionName '4.3.1'
|
||||
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:e5a6b82853') {
|
||||
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'
|
||||
|
||||
@@ -50,6 +50,7 @@ class KotatsuApp : Application(), Configuration.Provider {
|
||||
enableStrictMode()
|
||||
}
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
AppCompatDelegate.setApplicationLocales(settings.appLocales)
|
||||
setupActivityLifecycleCallbacks()
|
||||
processLifecycleScope.launch(Dispatchers.Default) {
|
||||
setupDatabaseObservers()
|
||||
|
||||
@@ -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> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
128
app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt
Normal file
128
app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.koitharu.kotatsu.core.logs
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class TrackerLogger
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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? {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
15
app/src/main/res/drawable-v23/m3_popup_background.xml
Normal file
15
app/src/main/res/drawable-v23/m3_popup_background.xml
Normal 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>
|
||||
12
app/src/main/res/drawable/ic_eye_check.xml
Normal file
12
app/src/main/res/drawable/ic_eye_check.xml
Normal 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>
|
||||
15
app/src/main/res/drawable/m3_popup_background.xml
Normal file
15
app/src/main/res/drawable/m3_popup_background.xml
Normal 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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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:<br><tt>%1$s</tt><br>1. Prova ad <a href=%2$s>aprire il manga in un browser web</a> per assicurarsi che sia disponibile sulla sua fonte<br>2. 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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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 lé 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 <«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:<br><tt>%1$s</tt><br><br>1. Tente <a href=%2$s>abrir o mangá num navegador de internet</a>para garantir que ele está disponível na fonte<br>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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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">Деталі помилки:<br><tt>%1$s</tt><br><br>1. Спробуйте <a href=%2$s>відкрити манґу у веб-браузері</a>, щоб переконатися, що вона доступна в джерелі<br>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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
8
gradle/wrapper/gradle-wrapper.properties
vendored
8
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user