Compare commits

...

11 Commits
v7.6 ... v7.6.1

Author SHA1 Message Date
Koitharu
f518acb8ee Skip error for local manga list (close #1113, close #1115) 2024-09-29 19:46:48 +03:00
大王叫我来巡山
b39a51d497 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (728 of 728 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
Felipe Nascimento
8819d8b1ee Translated using Weblate (Portuguese)
Currently translated at 98.6% (718 of 728 strings)

Co-authored-by: Felipe Nascimento <f.kgb@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
Draken
05a502b89a Translated using Weblate (Vietnamese)
Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
gekka
c320e3c26a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (723 of 724 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
Matt
938849c31e Translated using Weblate (Japanese)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Matt <contact.mattdev@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ja/
Translation: Kotatsu/plurals
2024-09-29 19:43:34 +03:00
Oğuz Ersen
95c243daa1 Translated using Weblate (Turkish)
Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
gallegonovato
6ce6a02b56 Translated using Weblate (Spanish)
Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
Koitharu
e92e9fb393 Update SSIV 2024-09-29 19:43:09 +03:00
Koitharu
f4186a2787 Remove loggers and reorganize settings 2024-09-27 14:40:31 +03:00
Koitharu
8b93b699d3 Update readme 2024-09-26 16:02:52 +03:00
29 changed files with 170 additions and 420 deletions

View File

@@ -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.
![Android 5.0](https://img.shields.io/badge/android-5.0+-brightgreen) ![Kotlin](https://img.shields.io/github/languages/top/KotatsuApp/Kotatsu) ![License](https://img.shields.io/github/license/KotatsuApp/Kotatsu) [![weblate](https://hosted.weblate.org/widgets/kotatsu/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/kotatsu/) [![Telegram](https://img.shields.io/badge/chat-telegram-60ACFF)](https://t.me/kotatsuapp) [![Discord](https://img.shields.io/discord/898363402467045416?color=5865f2&label=discord)](https://discord.gg/NNJ5RgVBC5) [![Sources count](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2FKotatsuApp%2Fkotatsu-parsers%2Frefs%2Fheads%2Fmaster%2F.github%2Fsummary.yaml&query=total&label=manga%20sources&color=%23E9321C)](https://github.com/KotatsuApp/kotatsu-parsers) ![Android 5.0](https://img.shields.io/badge/android-5.0+-brightgreen) [![weblate](https://hosted.weblate.org/widgets/kotatsu/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/kotatsu/) [![Telegram](https://img.shields.io/badge/chat-telegram-60ACFF)](https://t.me/kotatsuapp) [![Discord](https://img.shields.io/discord/898363402467045416?color=5865f2&label=discord)](https://discord.gg/NNJ5RgVBC5) [![License](https://img.shields.io/github/license/KotatsuApp/Kotatsu)](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

View File

@@ -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'

View File

@@ -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) {
}
}

View File

@@ -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

View File

@@ -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,
)
}

View File

@@ -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())

View File

@@ -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()

View File

@@ -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

View File

@@ -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
} }
} }

View File

@@ -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()
}
}
} }

View File

@@ -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)

View File

@@ -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)
} }
} }
} }

View File

@@ -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 {

View File

@@ -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
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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()
}
}
}

View File

@@ -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
} }

View File

@@ -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 {

View File

@@ -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)
} }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>