Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f518acb8ee | ||
|
|
b39a51d497 | ||
|
|
8819d8b1ee | ||
|
|
05a502b89a | ||
|
|
c320e3c26a | ||
|
|
938849c31e | ||
|
|
95c243daa1 | ||
|
|
6ce6a02b56 | ||
|
|
e92e9fb393 | ||
|
|
f4186a2787 | ||
|
|
8b93b699d3 |
13
README.md
13
README.md
@@ -1,8 +1,8 @@
|
|||||||
# Kotatsu
|
# Kotatsu
|
||||||
|
|
||||||
Kotatsu is a free and open source manga reader for Android.
|
Kotatsu is a free and open-source manga reader for Android with built-in online content sources.
|
||||||
|
|
||||||
   [](https://hosted.weblate.org/engage/kotatsu/) [](https://t.me/kotatsuapp) [](https://discord.gg/NNJ5RgVBC5)
|
[](https://github.com/KotatsuApp/kotatsu-parsers)  [](https://hosted.weblate.org/engage/kotatsu/) [](https://t.me/kotatsuapp) [](https://discord.gg/NNJ5RgVBC5) [](https://github.com/KotatsuApp/Kotatsu/blob/devel/LICENSE)
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
@@ -12,16 +12,15 @@ Kotatsu is a free and open source manga reader for Android.
|
|||||||
### Main Features
|
### Main Features
|
||||||
|
|
||||||
* Online [manga catalogues](https://github.com/KotatsuApp/kotatsu-parsers)
|
* Online [manga catalogues](https://github.com/KotatsuApp/kotatsu-parsers)
|
||||||
* Search manga by name and genres
|
* Search manga by name, genres, and more filters
|
||||||
* Reading history and bookmarks
|
* Reading history and bookmarks
|
||||||
* Favourites organized by user-defined categories
|
* Favorites organized by user-defined categories
|
||||||
* Downloading manga and reading it offline. Third-party CBZ archives also supported
|
* Downloading manga and reading it offline. Third-party CBZ archives also supported
|
||||||
* Tablet-optimized Material You UI
|
* Tablet-optimized Material You UI
|
||||||
* Standard and Webtoon-optimized reader
|
* Standard and Webtoon-optimized customizable reader
|
||||||
* Notifications about new chapters with updates feed
|
* Notifications about new chapters with updates feed
|
||||||
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu
|
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu
|
||||||
* Password/fingerprint protect access to the app
|
* Password/fingerprint-protected access to the app
|
||||||
* History and favourites [synchronization](https://github.com/KotatsuApp/kotatsu-syncserver) across devices
|
|
||||||
|
|
||||||
### Screenshots
|
### Screenshots
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 673
|
versionCode = 674
|
||||||
versionName = '7.6'
|
versionName = '7.6.1'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||||
ksp {
|
ksp {
|
||||||
@@ -83,7 +83,7 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation('com.github.KotatsuApp:kotatsu-parsers:3cdd391410') {
|
implementation('com.github.KotatsuApp:kotatsu-parsers:1.1') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'io.coil-kt:coil-base:2.7.0'
|
implementation 'io.coil-kt:coil-base:2.7.0'
|
||||||
implementation 'io.coil-kt:coil-svg:2.7.0'
|
implementation 'io.coil-kt:coil-svg:2.7.0'
|
||||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:4ec7176962'
|
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:b2c5a6d5ca'
|
||||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||||
implementation 'io.noties.markwon:core:4.6.2'
|
implementation 'io.noties.markwon:core:4.6.2'
|
||||||
|
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
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.NonCancellable
|
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.runInterruptible
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.subdir
|
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.time.format.FormatStyle
|
|
||||||
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 dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(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(dateTimeFormatter.format(LocalDateTime.now()))
|
|
||||||
append(": ")
|
|
||||||
if (e != null) {
|
|
||||||
append("E!")
|
|
||||||
}
|
|
||||||
append(message)
|
|
||||||
if (e != null) {
|
|
||||||
append(' ')
|
|
||||||
append(e.stackTraceToString())
|
|
||||||
appendLine()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.add(text)
|
|
||||||
postFlush()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun log(messageProducer: () -> String) {
|
|
||||||
if (isEnabled) {
|
|
||||||
log(messageProducer())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun flush() {
|
|
||||||
if (!isEnabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
flushJob?.cancelAndJoin()
|
|
||||||
flushImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
fun flushBlocking() {
|
|
||||||
if (!isEnabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
runBlockingSafe { flushJob?.cancelAndJoin() }
|
|
||||||
runBlockingSafe { 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() = withContext(NonCancellable) {
|
|
||||||
mutex.withLock {
|
|
||||||
if (buffer.isEmpty()) {
|
|
||||||
return@withContext
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun runBlockingSafe(crossinline block: suspend () -> Unit) = try {
|
|
||||||
runBlocking(NonCancellable) { block() }
|
|
||||||
} catch (_: InterruptedException) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.logs
|
|
||||||
|
|
||||||
import javax.inject.Qualifier
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class TrackerLogger
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class SyncLogger
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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
|
|
||||||
@SyncLogger
|
|
||||||
fun provideSyncLogger(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
settings: AppSettings,
|
|
||||||
) = FileLogger(context, settings, "sync")
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ElementsIntoSet
|
|
||||||
fun provideAllLoggers(
|
|
||||||
@TrackerLogger trackerLogger: FileLogger,
|
|
||||||
@SyncLogger syncLogger: FileLogger,
|
|
||||||
): Set<@JvmSuppressWildcards FileLogger> = arraySetOf(
|
|
||||||
trackerLogger,
|
|
||||||
syncLogger,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -74,7 +74,7 @@ interface NetworkModule {
|
|||||||
if (settings.isSSLBypassEnabled) {
|
if (settings.isSSLBypassEnabled) {
|
||||||
disableCertificateVerification()
|
disableCertificateVerification()
|
||||||
} else {
|
} else {
|
||||||
installExtraCertsificates(contextProvider.get())
|
installExtraCertificates(contextProvider.get())
|
||||||
}
|
}
|
||||||
cache(cache)
|
cache(cache)
|
||||||
addInterceptor(GZipInterceptor())
|
addInterceptor(GZipInterceptor())
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ fun OkHttpClient.Builder.disableCertificateVerification() = also { builder ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun OkHttpClient.Builder.installExtraCertsificates(context: Context) = also { builder ->
|
fun OkHttpClient.Builder.installExtraCertificates(context: Context) = also { builder ->
|
||||||
val certificatesBuilder = HandshakeCertificates.Builder()
|
val certificatesBuilder = HandshakeCertificates.Builder()
|
||||||
.addPlatformTrustedCertificates()
|
.addPlatformTrustedCertificates()
|
||||||
val assets = context.assets.list("").orEmpty()
|
val assets = context.assets.list("").orEmpty()
|
||||||
|
|||||||
@@ -239,9 +239,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
}
|
}
|
||||||
} ?: EnumSet.allOf(SearchSuggestionType::class.java)
|
} ?: EnumSet.allOf(SearchSuggestionType::class.java)
|
||||||
|
|
||||||
val isLoggingEnabled: Boolean
|
|
||||||
get() = prefs.getBoolean(KEY_LOGGING_ENABLED, false)
|
|
||||||
|
|
||||||
var isBiometricProtectionEnabled: Boolean
|
var isBiometricProtectionEnabled: Boolean
|
||||||
get() = prefs.getBoolean(KEY_PROTECT_APP_BIOMETRIC, true)
|
get() = prefs.getBoolean(KEY_PROTECT_APP_BIOMETRIC, true)
|
||||||
set(value) = prefs.edit { putBoolean(KEY_PROTECT_APP_BIOMETRIC, value) }
|
set(value) = prefs.edit { putBoolean(KEY_PROTECT_APP_BIOMETRIC, value) }
|
||||||
@@ -665,7 +662,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_WEBTOON_ZOOM_OUT = "webtoon_zoom_out"
|
const val KEY_WEBTOON_ZOOM_OUT = "webtoon_zoom_out"
|
||||||
const val KEY_PREFETCH_CONTENT = "prefetch_content"
|
const val KEY_PREFETCH_CONTENT = "prefetch_content"
|
||||||
const val KEY_APP_LOCALE = "app_locale"
|
const val KEY_APP_LOCALE = "app_locale"
|
||||||
const val KEY_LOGGING_ENABLED = "logging"
|
|
||||||
const val KEY_SOURCES_GRID = "sources_grid"
|
const val KEY_SOURCES_GRID = "sources_grid"
|
||||||
const val KEY_UPDATES_UNSTABLE = "updates_unstable"
|
const val KEY_UPDATES_UNSTABLE = "updates_unstable"
|
||||||
const val KEY_TIPS_CLOSED = "tips_closed"
|
const val KEY_TIPS_CLOSED = "tips_closed"
|
||||||
@@ -709,9 +705,11 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_APP_VERSION = "app_version"
|
const val KEY_APP_VERSION = "app_version"
|
||||||
const val KEY_IGNORE_DOZE = "ignore_dose"
|
const val KEY_IGNORE_DOZE = "ignore_dose"
|
||||||
const val KEY_TRACKER_DEBUG = "tracker_debug"
|
const val KEY_TRACKER_DEBUG = "tracker_debug"
|
||||||
const val KEY_LOGS_SHARE = "logs_share"
|
|
||||||
const val KEY_APP_UPDATE = "app_update"
|
const val KEY_APP_UPDATE = "app_update"
|
||||||
const val KEY_APP_TRANSLATION = "about_app_translation"
|
const val KEY_LINK_WEBLATE = "about_app_translation"
|
||||||
|
const val KEY_LINK_TELEGRAM = "about_telegram"
|
||||||
|
const val KEY_LINK_GITHUB = "about_github"
|
||||||
|
const val KEY_LINK_MANUAL = "about_help"
|
||||||
const val PROXY_TEST = "proxy_test"
|
const val PROXY_TEST = "proxy_test"
|
||||||
|
|
||||||
// old keys are for migration only
|
// old keys are for migration only
|
||||||
|
|||||||
@@ -80,11 +80,11 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
|||||||
(activity as? SettingsActivity)?.setSectionTitle(title)
|
(activity as? SettingsActivity)?.setSectionTitle(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun startActivitySafe(intent: Intent) {
|
protected fun startActivitySafe(intent: Intent): Boolean = try {
|
||||||
try {
|
startActivity(intent)
|
||||||
startActivity(intent)
|
true
|
||||||
} catch (_: ActivityNotFoundException) {
|
} catch (_: ActivityNotFoundException) {
|
||||||
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
|
||||||
}
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package org.koitharu.kotatsu.core.util
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.app.ShareCompat
|
import androidx.core.app.ShareCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.logs.FileLogger
|
|
||||||
import org.koitharu.kotatsu.core.model.appUrl
|
import org.koitharu.kotatsu.core.model.appUrl
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -84,25 +82,4 @@ class ShareHelper(private val context: Context) {
|
|||||||
.setChooserTitle(R.string.share)
|
.setChooserTitle(R.string.share)
|
||||||
.startChooser()
|
.startChooser()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shareLogs(loggers: Collection<FileLogger>) {
|
|
||||||
val intentBuilder = ShareCompat.IntentBuilder(context)
|
|
||||||
.setType(TYPE_TEXT)
|
|
||||||
var hasLogs = false
|
|
||||||
for (logger in loggers) {
|
|
||||||
val logFile = logger.file
|
|
||||||
if (!logFile.exists()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.files", logFile)
|
|
||||||
intentBuilder.addStream(uri)
|
|
||||||
hasLogs = true
|
|
||||||
}
|
|
||||||
if (hasLogs) {
|
|
||||||
intentBuilder.setChooserTitle(R.string.share_logs)
|
|
||||||
intentBuilder.startChooser()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, R.string.nothing_here, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.ActivityNotFoundException
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import coil.network.HttpException
|
import coil.network.HttpException
|
||||||
|
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
|
||||||
import okio.FileNotFoundException
|
import okio.FileNotFoundException
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
import okio.ProtocolException
|
import okio.ProtocolException
|
||||||
@@ -80,6 +81,7 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
|
|||||||
is UnknownHostException,
|
is UnknownHostException,
|
||||||
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
||||||
|
|
||||||
|
is ImageDecodeException -> resources.getString(R.string.error_corrupted_file)
|
||||||
is NoDataReceivedException -> resources.getString(R.string.error_no_data_received)
|
is NoDataReceivedException -> resources.getString(R.string.error_no_data_received)
|
||||||
is IncompatiblePluginException -> resources.getString(R.string.plugin_incompatible)
|
is IncompatiblePluginException -> resources.getString(R.string.plugin_incompatible)
|
||||||
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
||||||
|
|||||||
@@ -230,9 +230,12 @@ class LocalMangaRepository @Inject constructor(
|
|||||||
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
|
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
launch(dispatcher) {
|
launch(dispatcher) {
|
||||||
val m = LocalMangaInput.ofOrNull(file)?.getManga()
|
runCatchingCancellable {
|
||||||
if (m != null) {
|
LocalMangaInput.ofOrNull(file)?.getManga()
|
||||||
send(m)
|
}.onFailure { e ->
|
||||||
|
e.printStackTraceDebug()
|
||||||
|
}.onSuccess { m ->
|
||||||
|
if (m != null) send(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.settings.about
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
@@ -14,23 +15,16 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.core.github.AppVersion
|
import org.koitharu.kotatsu.core.github.AppVersion
|
||||||
import org.koitharu.kotatsu.core.github.VersionId
|
import org.koitharu.kotatsu.core.github.VersionId
|
||||||
import org.koitharu.kotatsu.core.github.isStable
|
import org.koitharu.kotatsu.core.github.isStable
|
||||||
import org.koitharu.kotatsu.core.logs.FileLogger
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
import org.koitharu.kotatsu.core.util.ShareHelper
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||||
|
|
||||||
private val viewModel by viewModels<AboutSettingsViewModel>()
|
private val viewModel by viewModels<AboutSettingsViewModel>()
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var loggers: Set<@JvmSuppressWildcards FileLogger>
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
addPreferencesFromResource(R.xml.pref_about)
|
addPreferencesFromResource(R.xml.pref_about)
|
||||||
findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run {
|
findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run {
|
||||||
@@ -41,12 +35,6 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
|||||||
isEnabled = VersionId(BuildConfig.VERSION_NAME).isStable
|
isEnabled = VersionId(BuildConfig.VERSION_NAME).isStable
|
||||||
if (!isEnabled) isChecked = true
|
if (!isEnabled) isChecked = true
|
||||||
}
|
}
|
||||||
if (!settings.isTrackerEnabled) {
|
|
||||||
findPreference<Preference>(AppSettings.KEY_TRACKER_DEBUG)?.run {
|
|
||||||
isEnabled = false
|
|
||||||
setSummary(R.string.check_for_new_chapters_disabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -64,21 +52,27 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_APP_TRANSLATION -> {
|
AppSettings.KEY_LINK_WEBLATE -> {
|
||||||
openLink(getString(R.string.url_weblate), preference.title)
|
openLink(R.string.url_weblate, preference.title)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_LOGS_SHARE -> {
|
AppSettings.KEY_LINK_GITHUB -> {
|
||||||
ShareHelper(preference.context).shareLogs(loggers)
|
openLink(R.string.url_github, preference.title)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_TRACKER_DEBUG -> {
|
AppSettings.KEY_LINK_MANUAL -> {
|
||||||
startActivity(Intent(preference.context, TrackerDebugActivity::class.java))
|
openLink(R.string.url_user_manual, preference.title)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_LINK_TELEGRAM -> {
|
||||||
|
if (!openLink(R.string.url_telegram, null)) {
|
||||||
|
openLink(R.string.url_telegram_web, preference.title)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> super.onPreferenceTreeClick(preference)
|
else -> super.onPreferenceTreeClick(preference)
|
||||||
}
|
}
|
||||||
@@ -87,15 +81,15 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
|||||||
private fun onUpdateAvailable(version: AppVersion?) {
|
private fun onUpdateAvailable(version: AppVersion?) {
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
Snackbar.make(listView, R.string.no_update_available, Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(listView, R.string.no_update_available, Snackbar.LENGTH_SHORT).show()
|
||||||
return
|
} else {
|
||||||
|
startActivity(Intent(requireContext(), AppUpdateActivity::class.java))
|
||||||
}
|
}
|
||||||
startActivity(Intent(requireContext(), AppUpdateActivity::class.java))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openLink(url: String, title: CharSequence?) {
|
private fun openLink(@StringRes url: Int, title: CharSequence?): Boolean {
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
intent.data = url.toUri()
|
intent.data = getString(url).toUri()
|
||||||
startActivitySafe(
|
return startActivitySafe(
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
Intent.createChooser(intent, title)
|
Intent.createChooser(intent, title)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ data class SourceCatalogPage(
|
|||||||
return other is SourceCatalogPage && other.type == type
|
return other is SourceCatalogPage && other.type == type
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getChangePayload(previousState: ListModel): Any? {
|
override fun getChangePayload(previousState: ListModel): Any {
|
||||||
return ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED
|
return ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.koitharu.kotatsu.parsers.util.names
|
|||||||
import org.koitharu.kotatsu.settings.tracker.categories.TrackerCategoriesConfigSheet
|
import org.koitharu.kotatsu.settings.tracker.categories.TrackerCategoriesConfigSheet
|
||||||
import org.koitharu.kotatsu.settings.utils.DozeHelper
|
import org.koitharu.kotatsu.settings.utils.DozeHelper
|
||||||
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
|
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
|
||||||
|
import org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity
|
||||||
import org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper
|
import org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -116,6 +117,11 @@ class TrackerSettingsFragment :
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_TRACKER_DEBUG -> {
|
||||||
|
startActivity(Intent(preference.context, TrackerDebugActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> super.onPreferenceTreeClick(preference)
|
else -> super.onPreferenceTreeClick(preference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.utils
|
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.View
|
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.view.forEach
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceViewHolder
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.databinding.PreferenceAboutLinksBinding
|
|
||||||
|
|
||||||
class AboutLinksPreference @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
) : Preference(context, attrs), View.OnClickListener {
|
|
||||||
|
|
||||||
init {
|
|
||||||
layoutResource = R.layout.preference_about_links
|
|
||||||
isSelectable = false
|
|
||||||
isPersistent = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
|
||||||
super.onBindViewHolder(holder)
|
|
||||||
|
|
||||||
val binding = PreferenceAboutLinksBinding.bind(holder.itemView)
|
|
||||||
binding.root.forEach { button ->
|
|
||||||
TooltipCompat.setTooltipText(button, button.contentDescription)
|
|
||||||
button.setOnClickListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
val urlResId = when (v.id) {
|
|
||||||
R.id.btn_discord -> R.string.url_discord
|
|
||||||
R.id.btn_telegram -> R.string.url_telegram
|
|
||||||
R.id.btn_github -> R.string.url_github
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
openLink(v, v.context.getString(urlResId), v.contentDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openLink(v: View, url: String, title: CharSequence?) {
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
|
||||||
try {
|
|
||||||
context.startActivity(
|
|
||||||
if (title != null) {
|
|
||||||
Intent.createChooser(intent, title)
|
|
||||||
} else {
|
|
||||||
intent
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} catch (_: ActivityNotFoundException) {
|
|
||||||
Snackbar.make(v, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ class PercentSummaryProvider : Preference.SummaryProvider<SliderPreference> {
|
|||||||
|
|
||||||
private var percentPattern: String? = null
|
private var percentPattern: String? = null
|
||||||
|
|
||||||
override fun provideSummary(preference: SliderPreference): CharSequence? {
|
override fun provideSummary(preference: SliderPreference): CharSequence {
|
||||||
val pattern = percentPattern ?: preference.context.getString(R.string.percent_string_pattern).also {
|
val pattern = percentPattern ?: preference.context.getString(R.string.percent_string_pattern).also {
|
||||||
percentPattern = it
|
percentPattern = it
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.content.SyncResult
|
|||||||
import android.content.SyncStats
|
import android.content.SyncStats
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.core.content.contentValuesOf
|
import androidx.core.content.contentValuesOf
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
@@ -18,9 +19,9 @@ import dagger.assisted.AssistedInject
|
|||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
|
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
|
||||||
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
|
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
|
||||||
@@ -28,10 +29,9 @@ import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
|||||||
import org.koitharu.kotatsu.core.db.TABLE_MANGA
|
import org.koitharu.kotatsu.core.db.TABLE_MANGA
|
||||||
import org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS
|
import org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS
|
||||||
import org.koitharu.kotatsu.core.db.TABLE_TAGS
|
import org.koitharu.kotatsu.core.db.TABLE_TAGS
|
||||||
import org.koitharu.kotatsu.core.logs.FileLogger
|
|
||||||
import org.koitharu.kotatsu.core.logs.SyncLogger
|
|
||||||
import org.koitharu.kotatsu.core.network.BaseHttpClient
|
import org.koitharu.kotatsu.core.network.BaseHttpClient
|
||||||
import org.koitharu.kotatsu.core.util.ext.parseJsonOrNull
|
import org.koitharu.kotatsu.core.util.ext.parseJsonOrNull
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.toContentValues
|
import org.koitharu.kotatsu.core.util.ext.toContentValues
|
||||||
import org.koitharu.kotatsu.core.util.ext.toJson
|
import org.koitharu.kotatsu.core.util.ext.toJson
|
||||||
import org.koitharu.kotatsu.core.util.ext.toRequestBody
|
import org.koitharu.kotatsu.core.util.ext.toRequestBody
|
||||||
@@ -50,7 +50,6 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
@Assisted private val account: Account,
|
@Assisted private val account: Account,
|
||||||
@Assisted private val provider: ContentProviderClient,
|
@Assisted private val provider: ContentProviderClient,
|
||||||
private val settings: SyncSettings,
|
private val settings: SyncSettings,
|
||||||
@SyncLogger private val logger: FileLogger,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val authorityHistory = context.getString(R.string.sync_authority_history)
|
private val authorityHistory = context.getString(R.string.sync_authority_history)
|
||||||
@@ -75,7 +74,7 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
.url("$baseUrl/resource/$TABLE_FAVOURITES")
|
.url("$baseUrl/resource/$TABLE_FAVOURITES")
|
||||||
.post(data.toRequestBody())
|
.post(data.toRequestBody())
|
||||||
.build()
|
.build()
|
||||||
val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
|
val response = httpClient.newCall(request).execute().parseJsonOrNull()
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES))
|
val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES))
|
||||||
stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
|
stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
|
||||||
@@ -97,7 +96,7 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
.url("$baseUrl/resource/$TABLE_HISTORY")
|
.url("$baseUrl/resource/$TABLE_HISTORY")
|
||||||
.post(data.toRequestBody())
|
.post(data.toRequestBody())
|
||||||
.build()
|
.build()
|
||||||
val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
|
val response = httpClient.newCall(request).execute().parseJsonOrNull()
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
val result = upsertHistory(
|
val result = upsertHistory(
|
||||||
json = response.getJSONArray(TABLE_HISTORY),
|
json = response.getJSONArray(TABLE_HISTORY),
|
||||||
@@ -110,15 +109,12 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onError(e: Throwable) {
|
fun onError(e: Throwable) {
|
||||||
if (logger.isEnabled) {
|
e.printStackTraceDebug()
|
||||||
logger.log("Sync error", e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSyncComplete(result: SyncResult) {
|
fun onSyncComplete(result: SyncResult) {
|
||||||
if (logger.isEnabled) {
|
if (BuildConfig.DEBUG) {
|
||||||
logger.log("Sync finished: ${result.toDebugString()}")
|
Log.i("Sync", "Sync finished: ${result.toDebugString()}")
|
||||||
logger.flushBlocking()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,12 +294,6 @@ class SyncHelper @AssistedInject constructor(
|
|||||||
|
|
||||||
private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray
|
private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray
|
||||||
|
|
||||||
private fun Response.log() = apply {
|
|
||||||
if (logger.isEnabled) {
|
|
||||||
logger.log("$code ${request.url}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
||||||
|
|||||||
@@ -45,13 +45,12 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
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.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.TrackerDownloadStrategy
|
import org.koitharu.kotatsu.core.prefs.TrackerDownloadStrategy
|
||||||
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
|
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
|
||||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||||
import org.koitharu.kotatsu.core.util.ext.onEachIndexed
|
import org.koitharu.kotatsu.core.util.ext.onEachIndexed
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.trySetForeground
|
import org.koitharu.kotatsu.core.util.ext.trySetForeground
|
||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||||
@@ -80,7 +79,6 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
private val getTracksUseCase: GetTracksUseCase,
|
private val getTracksUseCase: GetTracksUseCase,
|
||||||
private val checkNewChaptersUseCase: CheckNewChaptersUseCase,
|
private val checkNewChaptersUseCase: CheckNewChaptersUseCase,
|
||||||
private val workManager: WorkManager,
|
private val workManager: WorkManager,
|
||||||
@TrackerLogger private val logger: FileLogger,
|
|
||||||
private val localRepositoryLazy: Lazy<LocalMangaRepository>,
|
private val localRepositoryLazy: Lazy<LocalMangaRepository>,
|
||||||
private val downloadSchedulerLazy: Lazy<DownloadWorker.Scheduler>,
|
private val downloadSchedulerLazy: Lazy<DownloadWorker.Scheduler>,
|
||||||
) : CoroutineWorker(context, workerParams) {
|
) : CoroutineWorker(context, workerParams) {
|
||||||
@@ -90,17 +88,15 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
notificationHelper.updateChannels()
|
notificationHelper.updateChannels()
|
||||||
val isForeground = trySetForeground()
|
val isForeground = trySetForeground()
|
||||||
logger.log("doWork(): attempt $runAttemptCount")
|
|
||||||
return try {
|
return try {
|
||||||
doWorkImpl(isFullRun = isForeground && TAG_ONESHOT in tags)
|
doWorkImpl(isFullRun = isForeground && TAG_ONESHOT in tags)
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logger.log("fatal", e)
|
e.printStackTraceDebug()
|
||||||
Result.failure()
|
Result.failure()
|
||||||
} finally {
|
} finally {
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
logger.flush()
|
|
||||||
notificationManager.cancel(WORKER_NOTIFICATION_ID)
|
notificationManager.cancel(WORKER_NOTIFICATION_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +107,6 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
val tracks = getTracksUseCase(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE)
|
val tracks = getTracksUseCase(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE)
|
||||||
logger.log("Total ${tracks.size} tracks")
|
|
||||||
if (tracks.isEmpty()) {
|
if (tracks.isEmpty()) {
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
@@ -154,7 +149,6 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
when (it) {
|
when (it) {
|
||||||
is MangaUpdates.Failure -> {
|
is MangaUpdates.Failure -> {
|
||||||
val e = it.error
|
val e = it.error
|
||||||
logger.log("checkUpdatesAsync", e)
|
|
||||||
if (e is CloudFlareProtectedException) {
|
if (e is CloudFlareProtectedException) {
|
||||||
CaptchaNotifier(applicationContext).notify(e)
|
CaptchaNotifier(applicationContext).notify(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -316,7 +316,7 @@
|
|||||||
<string name="history_shortcuts_summary">Hacer que los mangas recientes estén disponibles mediante una pulsación larga en el icono de la aplicación</string>
|
<string name="history_shortcuts_summary">Hacer que los mangas recientes estén disponibles mediante una pulsación larga en el icono de la aplicación</string>
|
||||||
<string name="feed">Fuente</string>
|
<string name="feed">Fuente</string>
|
||||||
<string name="history_shortcuts">Mostrar los accesos directos a los mangas recientes</string>
|
<string name="history_shortcuts">Mostrar los accesos directos a los mangas recientes</string>
|
||||||
<string name="reader_control_ltr_summary">No modifique el método de paso de página al modo de lectura ya configurado. Por ejemplo: presionar el botón derecho siempre pasa a la página siguiente. Esta configuración solo es válida para dispositivos de hardware.</string>
|
<string name="reader_control_ltr_summary">No modifique el método de paso de página al modo de lectura ya configurado. Por ejemplo: presionar el botón derecho siempre pasa a la página siguiente. Esta configuración solo es válida para dispositivos de hardware</string>
|
||||||
<string name="reader_control_ltr">Control ergonómico del lector</string>
|
<string name="reader_control_ltr">Control ergonómico del lector</string>
|
||||||
<string name="color_correction">Corrección del color</string>
|
<string name="color_correction">Corrección del color</string>
|
||||||
<string name="brightness">Brillo</string>
|
<string name="brightness">Brillo</string>
|
||||||
@@ -719,4 +719,8 @@
|
|||||||
<string name="content_type_doujinshi">Dōjinshi</string>
|
<string name="content_type_doujinshi">Dōjinshi</string>
|
||||||
<string name="content_type_game_cg">Juego CG</string>
|
<string name="content_type_game_cg">Juego CG</string>
|
||||||
<string name="content_type_image_set">Conjunto de imágenes</string>
|
<string name="content_type_image_set">Conjunto de imágenes</string>
|
||||||
|
<string name="user_manual">Manual de usuario</string>
|
||||||
|
<string name="source_code">Código fuente</string>
|
||||||
|
<string name="telegram_group">Grupo de Telegram</string>
|
||||||
|
<string name="debug">Depurar</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -21,4 +21,10 @@
|
|||||||
<plurals name="months_ago">
|
<plurals name="months_ago">
|
||||||
<item quantity="other">%1$d ヶ月前</item>
|
<item quantity="other">%1$d ヶ月前</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="hours">
|
||||||
|
<item quantity="other">%1$d時間</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="other">%1$d分</item>
|
||||||
|
</plurals>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -705,4 +705,11 @@
|
|||||||
<string name="sort_order_asc">Ascendente</string>
|
<string name="sort_order_asc">Ascendente</string>
|
||||||
<string name="by_date">Data</string>
|
<string name="by_date">Data</string>
|
||||||
<string name="popularity">Popularidade</string>
|
<string name="popularity">Popularidade</string>
|
||||||
|
<string name="content_type_artist_cg">Artista CG</string>
|
||||||
|
<string name="debug">Depurar</string>
|
||||||
|
<string name="source_code">Código fonte</string>
|
||||||
|
<string name="user_manual">Manual do usuário</string>
|
||||||
|
<string name="telegram_group">Grupo Telegram</string>
|
||||||
|
<string name="content_type_image_set">Conjunto de imagens</string>
|
||||||
|
<string name="content_type_game_cg">Jogo CG</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -715,4 +715,12 @@
|
|||||||
<string name="filter_search_warning">Bu kaynak filtrelerle aramayı desteklemiyor. Filtreleriniz temizlendi</string>
|
<string name="filter_search_warning">Bu kaynak filtrelerle aramayı desteklemiyor. Filtreleriniz temizlendi</string>
|
||||||
<string name="demographic_kodomo">Kodomo</string>
|
<string name="demographic_kodomo">Kodomo</string>
|
||||||
<string name="content_type_one_shot">Bir kerelik</string>
|
<string name="content_type_one_shot">Bir kerelik</string>
|
||||||
|
<string name="content_type_image_set">Resim kümesi</string>
|
||||||
|
<string name="content_type_doujinshi">Doujinshi</string>
|
||||||
|
<string name="content_type_artist_cg">Sanatçı CG</string>
|
||||||
|
<string name="content_type_game_cg">Oyun CG</string>
|
||||||
|
<string name="debug">Hata ayıkla</string>
|
||||||
|
<string name="user_manual">Kullanıcı kılavuzu</string>
|
||||||
|
<string name="source_code">Kaynak kodu</string>
|
||||||
|
<string name="telegram_group">Telegram grubu</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -715,4 +715,12 @@
|
|||||||
<string name="filter_search_warning">Bộ lọc của bạn đã bị xóa do nguồn đọc này không hỗ trợ cho việc tìm kiếm bằng bộ lọc</string>
|
<string name="filter_search_warning">Bộ lọc của bạn đã bị xóa do nguồn đọc này không hỗ trợ cho việc tìm kiếm bằng bộ lọc</string>
|
||||||
<string name="demographic_kodomo">Dành cho trẻ em</string>
|
<string name="demographic_kodomo">Dành cho trẻ em</string>
|
||||||
<string name="content_type_one_shot">One shot</string>
|
<string name="content_type_one_shot">One shot</string>
|
||||||
|
<string name="content_type_image_set">Đặt hình ảnh</string>
|
||||||
|
<string name="content_type_game_cg">Game CG</string>
|
||||||
|
<string name="content_type_doujinshi">Doujinshi</string>
|
||||||
|
<string name="content_type_artist_cg">Họa sĩ CG</string>
|
||||||
|
<string name="source_code">Mã nguồn</string>
|
||||||
|
<string name="user_manual">Hướng dẫn sử dụng</string>
|
||||||
|
<string name="telegram_group">Nhóm Telegram</string>
|
||||||
|
<string name="debug">Gỡ lỗi</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -714,4 +714,13 @@
|
|||||||
<string name="popular_in_hour">一小时内热门</string>
|
<string name="popular_in_hour">一小时内热门</string>
|
||||||
<string name="demographic_kodomo">子供向</string>
|
<string name="demographic_kodomo">子供向</string>
|
||||||
<string name="content_type_one_shot">短篇</string>
|
<string name="content_type_one_shot">短篇</string>
|
||||||
|
<string name="content_type_doujinshi">同人志</string>
|
||||||
|
<string name="content_type_image_set">图片集</string>
|
||||||
|
<string name="content_type_game_cg">游戏CG</string>
|
||||||
|
<string name="content_type_artist_cg">艺术家CG</string>
|
||||||
|
<string name="debug">调试</string>
|
||||||
|
<string name="source_code">源代码</string>
|
||||||
|
<string name="user_manual">用户手册</string>
|
||||||
|
<string name="telegram_group">Telegram 群</string>
|
||||||
|
<string name="manga_with_downloaded_chapters">有已下载章节的漫画</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="url_github" translatable="false">https://github.com/KotatsuApp/Kotatsu</string>
|
<string name="url_github" translatable="false">https://github.com/KotatsuApp/Kotatsu</string>
|
||||||
<string name="url_discord" translatable="false">https://discord.gg/NNJ5RgVBC5</string>
|
<string name="url_telegram_web" translatable="false">https://t.me/kotatsuapp</string>
|
||||||
<string name="url_telegram" translatable="false">https://t.me/kotatsuapp</string>
|
<string name="url_telegram" translatable="false">tg://resolve?domain=kotatsuapp</string>
|
||||||
<string name="url_weblate" translatable="false">https://hosted.weblate.org/engage/kotatsu</string>
|
<string name="url_weblate" translatable="false">https://hosted.weblate.org/engage/kotatsu</string>
|
||||||
|
<string name="url_user_manual" translatable="false">https://kotatsu.app/manuals/guides/getting-started/</string>
|
||||||
<string name="url_error_report" translatable="false">https://bugs.kotatsu.app/report</string>
|
<string name="url_error_report" translatable="false">https://bugs.kotatsu.app/report</string>
|
||||||
<string name="account_type_sync" translatable="false">org.kotatsu.sync</string>
|
<string name="account_type_sync" translatable="false">org.kotatsu.sync</string>
|
||||||
<string name="sync_url_default" translatable="false">https://sync.kotatsu.app</string>
|
<string name="sync_url_default" translatable="false">https://sync.kotatsu.app</string>
|
||||||
|
|||||||
@@ -733,4 +733,8 @@
|
|||||||
<string name="content_type_image_set">Image set</string>
|
<string name="content_type_image_set">Image set</string>
|
||||||
<string name="content_type_artist_cg">Artist CG</string>
|
<string name="content_type_artist_cg">Artist CG</string>
|
||||||
<string name="content_type_game_cg">Game CG</string>
|
<string name="content_type_game_cg">Game CG</string>
|
||||||
|
<string name="debug">Debug</string>
|
||||||
|
<string name="source_code">Source code</string>
|
||||||
|
<string name="user_manual">User manual</string>
|
||||||
|
<string name="telegram_group">Telegram group</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -3,45 +3,40 @@
|
|||||||
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">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/app_name">
|
<Preference
|
||||||
|
android:key="app_version"
|
||||||
|
android:persistent="false"
|
||||||
|
android:summary="@string/check_for_updates" />
|
||||||
|
|
||||||
<Preference
|
<SwitchPreferenceCompat
|
||||||
android:key="app_version"
|
android:defaultValue="false"
|
||||||
android:persistent="false"
|
android:key="updates_unstable"
|
||||||
android:summary="@string/check_for_updates" />
|
android:summary="@string/allow_unstable_updates_summary"
|
||||||
|
android:title="@string/allow_unstable_updates" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<Preference
|
||||||
android:defaultValue="false"
|
android:key="about_help"
|
||||||
android:key="updates_unstable"
|
android:persistent="false"
|
||||||
android:summary="@string/allow_unstable_updates_summary"
|
android:summary="@string/url_user_manual"
|
||||||
android:title="@string/allow_unstable_updates" />
|
android:title="@string/user_manual"
|
||||||
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<Preference
|
||||||
android:defaultValue="false"
|
android:key="about_github"
|
||||||
android:key="logging"
|
android:persistent="false"
|
||||||
android:summary="@string/enable_logging_summary"
|
android:summary="@string/url_github"
|
||||||
android:title="@string/enable_logging"
|
android:title="@string/source_code" />
|
||||||
app:allowDividerAbove="true" />
|
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:dependency="logging"
|
android:key="about_app_translation"
|
||||||
android:key="logs_share"
|
android:persistent="false"
|
||||||
android:title="@string/share_logs" />
|
android:summary="@string/url_weblate"
|
||||||
|
android:title="@string/about_app_translation_summary" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="tracker_debug"
|
android:key="about_telegram"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:summary="@string/tracker_debug_info_summary"
|
android:summary="@string/url_telegram_web"
|
||||||
android:title="@string/tracker_debug_info" />
|
android:title="@string/telegram_group" />
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:key="about_app_translation"
|
|
||||||
android:summary="@string/about_app_translation_summary"
|
|
||||||
android:title="@string/about_app_translation"
|
|
||||||
app:allowDividerAbove="true" />
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.settings.utils.AboutLinksPreference />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen
|
<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"
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
@@ -59,22 +58,29 @@
|
|||||||
android:title="@string/download_new_chapters"
|
android:title="@string/download_new_chapters"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<Preference
|
<PreferenceCategory android:title="@string/debug">
|
||||||
android:dependency="tracker_enabled"
|
|
||||||
android:key="ignore_dose"
|
|
||||||
android:persistent="false"
|
|
||||||
android:summary="@string/disable_battery_optimization_summary"
|
|
||||||
android:title="@string/disable_battery_optimization"
|
|
||||||
app:allowDividerAbove="true"
|
|
||||||
app:isPreferenceVisible="false"
|
|
||||||
tools:isPrefrenceVisible="true" />
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.settings.utils.LinksPreference
|
<Preference
|
||||||
android:icon="@drawable/ic_info_outline"
|
android:dependency="tracker_enabled"
|
||||||
android:key="track_warning"
|
android:key="tracker_debug"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:selectable="false"
|
android:summary="@string/tracker_debug_info_summary"
|
||||||
android:summary="@string/tracker_warning"
|
android:title="@string/tracker_debug_info" />
|
||||||
app:allowDividerAbove="true" />
|
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:dependency="tracker_enabled"
|
||||||
|
android:key="ignore_dose"
|
||||||
|
android:persistent="false"
|
||||||
|
android:summary="@string/disable_battery_optimization_summary"
|
||||||
|
android:title="@string/disable_battery_optimization"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.settings.utils.LinksPreference
|
||||||
|
android:icon="@drawable/ic_info_outline"
|
||||||
|
android:key="track_warning"
|
||||||
|
android:persistent="false"
|
||||||
|
android:selectable="false"
|
||||||
|
android:summary="@string/tracker_warning" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
Reference in New Issue
Block a user