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 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
@@ -12,16 +12,15 @@ Kotatsu is a free and open source manga reader for Android.
### Main Features
* 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
* Favourites organized by user-defined categories
* Favorites organized by user-defined categories
* Downloading manga and reading it offline. Third-party CBZ archives also supported
* Tablet-optimized Material You UI
* Standard and Webtoon-optimized reader
* Standard and Webtoon-optimized customizable reader
* Notifications about new chapters with updates feed
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu
* Password/fingerprint protect access to the app
* History and favourites [synchronization](https://github.com/KotatsuApp/kotatsu-syncserver) across devices
* Password/fingerprint-protected access to the app
### Screenshots

View File

@@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 35
versionCode = 673
versionName = '7.6'
versionCode = 674
versionName = '7.6.1'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
@@ -83,7 +83,7 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:3cdd391410') {
implementation('com.github.KotatsuApp:kotatsu-parsers:1.1') {
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-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 '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) {
disableCertificateVerification()
} else {
installExtraCertsificates(contextProvider.get())
installExtraCertificates(contextProvider.get())
}
cache(cache)
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()
.addPlatformTrustedCertificates()
val assets = context.assets.list("").orEmpty()

View File

@@ -239,9 +239,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
}
} ?: EnumSet.allOf(SearchSuggestionType::class.java)
val isLoggingEnabled: Boolean
get() = prefs.getBoolean(KEY_LOGGING_ENABLED, false)
var isBiometricProtectionEnabled: Boolean
get() = prefs.getBoolean(KEY_PROTECT_APP_BIOMETRIC, true)
set(value) = prefs.edit { putBoolean(KEY_PROTECT_APP_BIOMETRIC, value) }
@@ -665,7 +662,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_WEBTOON_ZOOM_OUT = "webtoon_zoom_out"
const val KEY_PREFETCH_CONTENT = "prefetch_content"
const val KEY_APP_LOCALE = "app_locale"
const val KEY_LOGGING_ENABLED = "logging"
const val KEY_SOURCES_GRID = "sources_grid"
const val KEY_UPDATES_UNSTABLE = "updates_unstable"
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_IGNORE_DOZE = "ignore_dose"
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_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"
// 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)
}
protected fun startActivitySafe(intent: Intent) {
try {
startActivity(intent)
} catch (_: ActivityNotFoundException) {
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
}
protected fun startActivitySafe(intent: Intent): Boolean = try {
startActivity(intent)
true
} catch (_: ActivityNotFoundException) {
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.net.Uri
import android.widget.Toast
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.logs.FileLogger
import org.koitharu.kotatsu.core.model.appUrl
import org.koitharu.kotatsu.parsers.model.Manga
import java.io.File
@@ -84,25 +82,4 @@ class ShareHelper(private val context: Context) {
.setChooserTitle(R.string.share)
.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 androidx.annotation.DrawableRes
import coil.network.HttpException
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
import okio.FileNotFoundException
import okio.IOException
import okio.ProtocolException
@@ -80,6 +81,7 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is UnknownHostException,
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 IncompatiblePluginException -> resources.getString(R.string.plugin_incompatible)
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)
for (file in files) {
launch(dispatcher) {
val m = LocalMangaInput.ofOrNull(file)?.getManga()
if (m != null) {
send(m)
runCatchingCancellable {
LocalMangaInput.ofOrNull(file)?.getManga()
}.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.os.Bundle
import android.view.View
import androidx.annotation.StringRes
import androidx.core.net.toUri
import androidx.fragment.app.viewModels
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.VersionId
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.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity
import javax.inject.Inject
@AndroidEntryPoint
class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
private val viewModel by viewModels<AboutSettingsViewModel>()
@Inject
lateinit var loggers: Set<@JvmSuppressWildcards FileLogger>
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_about)
findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run {
@@ -41,12 +35,6 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
isEnabled = VersionId(BuildConfig.VERSION_NAME).isStable
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?) {
@@ -64,21 +52,27 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
true
}
AppSettings.KEY_APP_TRANSLATION -> {
openLink(getString(R.string.url_weblate), preference.title)
AppSettings.KEY_LINK_WEBLATE -> {
openLink(R.string.url_weblate, preference.title)
true
}
AppSettings.KEY_LOGS_SHARE -> {
ShareHelper(preference.context).shareLogs(loggers)
AppSettings.KEY_LINK_GITHUB -> {
openLink(R.string.url_github, preference.title)
true
}
AppSettings.KEY_TRACKER_DEBUG -> {
startActivity(Intent(preference.context, TrackerDebugActivity::class.java))
AppSettings.KEY_LINK_MANUAL -> {
openLink(R.string.url_user_manual, preference.title)
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)
}
@@ -87,15 +81,15 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
private fun onUpdateAvailable(version: AppVersion?) {
if (version == null) {
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)
intent.data = url.toUri()
startActivitySafe(
intent.data = getString(url).toUri()
return startActivitySafe(
if (title != null) {
Intent.createChooser(intent, title)
} else {

View File

@@ -13,7 +13,7 @@ data class SourceCatalogPage(
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
}
}

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.utils.DozeHelper
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity
import org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper
import javax.inject.Inject
@@ -116,6 +117,11 @@ class TrackerSettingsFragment :
true
}
AppSettings.KEY_TRACKER_DEBUG -> {
startActivity(Intent(preference.context, TrackerDebugActivity::class.java))
true
}
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
override fun provideSummary(preference: SliderPreference): CharSequence? {
override fun provideSummary(preference: SliderPreference): CharSequence {
val pattern = percentPattern ?: preference.context.getString(R.string.percent_string_pattern).also {
percentPattern = it
}

View File

@@ -10,6 +10,7 @@ import android.content.SyncResult
import android.content.SyncStats
import android.database.Cursor
import android.net.Uri
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.core.content.contentValuesOf
import dagger.assisted.Assisted
@@ -18,9 +19,9 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
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_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.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.toJson
import org.koitharu.kotatsu.core.util.ext.toRequestBody
@@ -50,7 +50,6 @@ class SyncHelper @AssistedInject constructor(
@Assisted private val account: Account,
@Assisted private val provider: ContentProviderClient,
private val settings: SyncSettings,
@SyncLogger private val logger: FileLogger,
) {
private val authorityHistory = context.getString(R.string.sync_authority_history)
@@ -75,7 +74,7 @@ class SyncHelper @AssistedInject constructor(
.url("$baseUrl/resource/$TABLE_FAVOURITES")
.post(data.toRequestBody())
.build()
val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
val response = httpClient.newCall(request).execute().parseJsonOrNull()
if (response != null) {
val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES))
stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
@@ -97,7 +96,7 @@ class SyncHelper @AssistedInject constructor(
.url("$baseUrl/resource/$TABLE_HISTORY")
.post(data.toRequestBody())
.build()
val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
val response = httpClient.newCall(request).execute().parseJsonOrNull()
if (response != null) {
val result = upsertHistory(
json = response.getJSONArray(TABLE_HISTORY),
@@ -110,15 +109,12 @@ class SyncHelper @AssistedInject constructor(
}
fun onError(e: Throwable) {
if (logger.isEnabled) {
logger.log("Sync error", e)
}
e.printStackTraceDebug()
}
fun onSyncComplete(result: SyncResult) {
if (logger.isEnabled) {
logger.log("Sync finished: ${result.toDebugString()}")
logger.flushBlocking()
if (BuildConfig.DEBUG) {
Log.i("Sync", "Sync finished: ${result.toDebugString()}")
}
}
@@ -298,12 +294,6 @@ class SyncHelper @AssistedInject constructor(
private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray
private fun Response.log() = apply {
if (logger.isEnabled) {
logger.log("$code ${request.url}")
}
}
@AssistedFactory
interface Factory {

View File

@@ -45,13 +45,12 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
import org.koitharu.kotatsu.core.db.MangaDatabase
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.TrackerDownloadStrategy
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
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.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.local.data.LocalMangaRepository
@@ -80,7 +79,6 @@ class TrackWorker @AssistedInject constructor(
private val getTracksUseCase: GetTracksUseCase,
private val checkNewChaptersUseCase: CheckNewChaptersUseCase,
private val workManager: WorkManager,
@TrackerLogger private val logger: FileLogger,
private val localRepositoryLazy: Lazy<LocalMangaRepository>,
private val downloadSchedulerLazy: Lazy<DownloadWorker.Scheduler>,
) : CoroutineWorker(context, workerParams) {
@@ -90,17 +88,15 @@ class TrackWorker @AssistedInject constructor(
override suspend fun doWork(): Result {
notificationHelper.updateChannels()
val isForeground = trySetForeground()
logger.log("doWork(): attempt $runAttemptCount")
return try {
doWorkImpl(isFullRun = isForeground && TAG_ONESHOT in tags)
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
logger.log("fatal", e)
e.printStackTraceDebug()
Result.failure()
} finally {
withContext(NonCancellable) {
logger.flush()
notificationManager.cancel(WORKER_NOTIFICATION_ID)
}
}
@@ -111,7 +107,6 @@ class TrackWorker @AssistedInject constructor(
return Result.success()
}
val tracks = getTracksUseCase(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE)
logger.log("Total ${tracks.size} tracks")
if (tracks.isEmpty()) {
return Result.success()
}
@@ -154,7 +149,6 @@ class TrackWorker @AssistedInject constructor(
when (it) {
is MangaUpdates.Failure -> {
val e = it.error
logger.log("checkUpdatesAsync", e)
if (e is CloudFlareProtectedException) {
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="feed">Fuente</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="color_correction">Corrección del color</string>
<string name="brightness">Brillo</string>
@@ -719,4 +719,8 @@
<string name="content_type_doujinshi">Dōjinshi</string>
<string name="content_type_game_cg">Juego CG</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>

View File

@@ -21,4 +21,10 @@
<plurals name="months_ago">
<item quantity="other">%1$d ヶ月前</item>
</plurals>
<plurals name="hours">
<item quantity="other">%1$d時間</item>
</plurals>
<plurals name="minutes">
<item quantity="other">%1$d分</item>
</plurals>
</resources>

View File

@@ -705,4 +705,11 @@
<string name="sort_order_asc">Ascendente</string>
<string name="by_date">Data</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>

View File

@@ -715,4 +715,12 @@
<string name="filter_search_warning">Bu kaynak filtrelerle aramayı desteklemiyor. Filtreleriniz temizlendi</string>
<string name="demographic_kodomo">Kodomo</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>

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="demographic_kodomo">Dành cho trẻ em</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>

View File

@@ -714,4 +714,13 @@
<string name="popular_in_hour">一小时内热门</string>
<string name="demographic_kodomo">子供向</string>
<string name="content_type_one_shot">短篇</string>
<string name="content_type_doujinshi">同人志</string>
<string name="content_type_image_set">图片集</string>
<string name="content_type_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>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<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" translatable="false">https://t.me/kotatsuapp</string>
<string name="url_telegram_web" 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_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="account_type_sync" translatable="false">org.kotatsu.sync</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_artist_cg">Artist 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>

View File

@@ -3,45 +3,40 @@
xmlns:android="http://schemas.android.com/apk/res/android"
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
android:key="app_version"
android:persistent="false"
android:summary="@string/check_for_updates" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="updates_unstable"
android:summary="@string/allow_unstable_updates_summary"
android:title="@string/allow_unstable_updates" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="updates_unstable"
android:summary="@string/allow_unstable_updates_summary"
android:title="@string/allow_unstable_updates" />
<Preference
android:key="about_help"
android:persistent="false"
android:summary="@string/url_user_manual"
android:title="@string/user_manual"
app:allowDividerAbove="true" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="logging"
android:summary="@string/enable_logging_summary"
android:title="@string/enable_logging"
app:allowDividerAbove="true" />
<Preference
android:key="about_github"
android:persistent="false"
android:summary="@string/url_github"
android:title="@string/source_code" />
<Preference
android:dependency="logging"
android:key="logs_share"
android:title="@string/share_logs" />
<Preference
android:key="about_app_translation"
android:persistent="false"
android:summary="@string/url_weblate"
android:title="@string/about_app_translation_summary" />
<Preference
android:key="tracker_debug"
android:persistent="false"
android:summary="@string/tracker_debug_info_summary"
android:title="@string/tracker_debug_info" />
<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>
<Preference
android:key="about_telegram"
android:persistent="false"
android:summary="@string/url_telegram_web"
android:title="@string/telegram_group" />
</PreferenceScreen>

View File

@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:defaultValue="true"
@@ -59,22 +58,29 @@
android:title="@string/download_new_chapters"
app:useSimpleSummaryProvider="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:allowDividerAbove="true"
app:isPreferenceVisible="false"
tools:isPrefrenceVisible="true" />
<PreferenceCategory android:title="@string/debug">
<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"
app:allowDividerAbove="true" />
<Preference
android:dependency="tracker_enabled"
android:key="tracker_debug"
android:persistent="false"
android:summary="@string/tracker_debug_info_summary"
android:title="@string/tracker_debug_info" />
<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>