Compare commits

...
16 Commits
Author SHA1 Message Date
arkon c615f4d458 Release v0.14.6 2023-04-16 11:00:14 -04:00
arkon 9e09a20e65 Avoid uncaught exceptions from OkHttp interceptors crashing entire app
(cherry picked from commit 26d422b0ae)
2023-04-16 10:57:40 -04:00
Eshlender 7115a9b9fe Update track domain shikimori.me (#9333)
shikimori.me

(cherry picked from commit 564a0980b9)
2023-04-16 10:53:01 -04:00
arkon fd8b97fc87 Better handle overflowing content in MigrateDialog actions
Fixes #9207

(cherry picked from commit b7cd7b8b4e)
2023-04-16 10:52:53 -04:00
arkon 4dd67e4348 Save current chapter progress when navigating to adjacent chapters
Fixes #9295

(cherry picked from commit 776d36caf1)
2023-04-16 10:52:42 -04:00
arkon 10973bf3cd Fix Spanish (Latin America) being missing from in-app language selection
(cherry picked from commit 290efb0283)
2023-04-16 10:51:29 -04:00
arkon 934ed0551a Bump subsampling-scale-image-view
(cherry picked from commit e5e18c2030)
2023-04-16 10:51:16 -04:00
arkon 38428c6ebe Show proper string in manga detail screen for SourceNotInstalledException
(cherry picked from commit 14d1bcacc9)
2023-04-16 10:51:05 -04:00
arkon bf85e147e7 Set default automatic library updates to off
(cherry picked from commit abd23b6826)
2023-04-16 10:50:55 -04:00
arkon d2dd34c2e5 Use queued last chapter read number when performing delayed tracker update
Fixes #8876

(cherry picked from commit f7f2072621)
2023-04-16 10:50:24 -04:00
arkon c4ab2b4675 Bump default user agent string and minimum WebView version
(cherry picked from commit c6e5f8abd9)
2023-04-16 10:49:28 -04:00
arkon aa2ec5940f Avoid crashing in SourcePreferencesScreen if source can't be loaded
(cherry picked from commit 4efca04765)
2023-04-16 10:49:11 -04:00
arkon 79323de326 Avoid crash in DeleteLibraryMangaDialog
No clue why it ever gets a -1 index though.

(cherry picked from commit b12c7cf963)
2023-04-16 10:49:05 -04:00
Two-Ai 08e6487a9a Fix download queue page count display bug (#9126)
When restarting a download, the page count would display as 0 until
the first page download completion, after all the existing pages were
rechecked.

To fix, calculate downloadedImages from pages instead of relying on
the downloader to reset and increment the count.

(cherry picked from commit 779df32e98)
2023-04-16 10:48:16 -04:00
arkon 4498b10a10 Fix occasional crash when opening library settings sheet
See https://stackoverflow.com/questions/47648689/sealed-classs-objects-mysteriously-becoming-null-when-referenced-by-other-compa

(cherry picked from commit c0e2eb211d)
2023-04-16 10:48:05 -04:00
arkon 6f2bb18d72 Avoid crash when loading invalid extension package
(cherry picked from commit 3d7c136320)
2023-04-16 10:47:58 -04:00
24 changed files with 107 additions and 80 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
I acknowledge that:
- I have updated:
- To the latest version of the app (stable is v0.14.5)
- To the latest version of the app (stable is v0.14.6)
- All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
+2 -2
View File
@@ -53,7 +53,7 @@ body:
label: Tachiyomi version
description: You can find your Tachiyomi version in **More → About**.
placeholder: |
Example: "0.14.5"
Example: "0.14.6"
validations:
required: true
@@ -98,7 +98,7 @@ body:
required: true
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[0.14.5](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true
+1 -1
View File
@@ -33,7 +33,7 @@ body:
required: true
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true
- label: I have updated the app to version **[0.14.5](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+2 -2
View File
@@ -22,8 +22,8 @@ android {
defaultConfig {
applicationId = "eu.kanade.tachiyomi"
versionCode = 98
versionName = "0.14.5"
versionCode = 101
versionName = "0.14.6"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -22,7 +22,7 @@ class LibraryPreferences(
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 24)
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
@@ -31,30 +31,33 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
val trackManager = Injekt.get<TrackManager>()
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
withIOContext {
val tracks = delayedTrackingStore.getItems().mapNotNull {
val track = getTracks.awaitOne(it.trackId)
if (track == null) {
delayedTrackingStore.remove(it.trackId)
val results = withIOContext {
delayedTrackingStore.getItems()
.mapNotNull {
val track = getTracks.awaitOne(it.trackId)
if (track == null) {
delayedTrackingStore.remove(it.trackId)
}
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
}
track
}
tracks.forEach { track ->
try {
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged) {
service.update(track.toDbTrack(), true)
insertTrack.await(track)
.mapNotNull { track ->
try {
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged) {
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
service.update(track.toDbTrack(), true)
insertTrack.await(track)
}
delayedTrackingStore.remove(track.id)
null
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
false
}
delayedTrackingStore.remove(track.id)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
}
return Result.success()
return if (results.isNotEmpty()) Result.failure() else Result.success()
}
companion object {
@@ -64,9 +64,11 @@ fun DeleteLibraryMangaDialog(
list.forEach { state ->
val onCheck = {
val index = list.indexOf(state)
val mutableList = list.toMutableList()
mutableList[index] = state.next() as CheckboxState.State<Int>
list = mutableList.toList()
if (index != -1) {
val mutableList = list.toMutableList()
mutableList[index] = state.next() as CheckboxState.State<Int>
list = mutableList.toList()
}
}
Row(
@@ -339,7 +339,6 @@ class Downloader(
?.filter { it.name!!.endsWith(".tmp") }
?.forEach { it.delete() }
download.downloadedImages = 0
download.status = Download.State.DOWNLOADING
}
// Get all the URLs to the source images, fetch pages if necessary
@@ -403,7 +402,6 @@ class Downloader(
}
page.uri = file.uri
page.progress = 100
download.downloadedImages++
page.status = Page.State.READY
}
.map { page }
@@ -21,9 +21,8 @@ data class Download(
val totalProgress: Int
get() = pages?.sumOf(Page::progress) ?: 0
@Volatile
@Transient
var downloadedImages: Int = 0
val downloadedImages: Int
get() = pages?.count { it.status == Page.State.READY } ?: 0
@Volatile
@Transient
@@ -158,7 +158,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
private const val baseUrl = "https://shikimori.one"
private const val baseUrl = "https://shikimori.me"
private const val apiUrl = "$baseUrl/api"
private const val oauthUrl = "$baseUrl/oauth/token"
private const val loginUrl = "$baseUrl/oauth/authorize"
@@ -126,8 +126,8 @@ internal object ExtensionLoader {
}
// Validate lib version
val libVersion = versionName.substringBeforeLast('.').toDouble()
if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
val libVersion = versionName.substringBeforeLast('.').toDoubleOrNull()
if (libVersion == null || libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
logcat(LogPriority.WARN) {
"Lib version is $libVersion, while only versions " +
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
@@ -136,7 +136,6 @@ internal object ExtensionLoader {
}
val signatureHash = getSignatureHash(pkgInfo)
if (signatureHash == null) {
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
return LoadResult.Error
@@ -61,7 +61,7 @@ class SourcePreferencesScreen(val sourceId: Long) : Screen {
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Injekt.get<SourceManager>().get(sourceId)!!.toString()) },
title = { Text(text = Injekt.get<SourceManager>().getOrStub(sourceId).toString()) },
navigationIcon = {
IconButton(onClick = navigator::pop) {
Icon(
@@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -23,6 +25,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import cafe.adriel.voyager.core.model.StateScreenModel
import eu.kanade.domain.category.interactor.SetMangaCategories
@@ -111,7 +114,9 @@ internal fun MigrateDialog(
}
},
confirmButton = {
Row {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
TextButton(
onClick = {
onClickTitle()
@@ -977,6 +977,14 @@ class MangaInfoScreenModel(
}
}
}
private val Throwable.snackbarMessage: String
get() = when (val className = this::class.simpleName) {
null -> message ?: ""
"SourceNotInstalledException" -> context.getString(R.string.loader_not_implemented_error)
"Exception", "HttpException", "IOException" -> message ?: className
else -> "$className: $message"
}
}
sealed class MangaScreenState {
@@ -1055,10 +1063,3 @@ val chapterDecimalFormat = DecimalFormat(
DecimalFormatSymbols()
.apply { decimalSeparator = '.' },
)
private val Throwable.snackbarMessage: String
get() = when (val className = this::class.simpleName) {
null -> message ?: ""
"Exception", "HttpException", "IOException", "SourceNotInstalledException" -> message ?: className
else -> "$className: $message"
}
@@ -99,10 +99,6 @@ import uy.kohesive.injekt.injectLazy
import kotlin.math.abs
import kotlin.math.max
/**
* Activity containing the reader of Tachiyomi. This activity is mostly a container of the
* viewers, to which calls from the presenter or UI events are delegated.
*/
class ReaderActivity : BaseActivity() {
companion object {
@@ -661,7 +657,7 @@ class ReaderActivity : BaseActivity() {
* Called from the presenter when a manga is ready. Used to instantiate the appropriate viewer
* and the toolbar title.
*/
fun setManga(manga: Manga) {
private fun setManga(manga: Manga) {
val prevViewer = viewer
val viewerMode = ReadingModeType.fromPreference(viewModel.getMangaReadingMode(resolveDefault = false))
@@ -776,7 +772,7 @@ class ReaderActivity : BaseActivity() {
* Called from the presenter if the initial load couldn't load the pages of the chapter. In
* this case the activity is closed and a toast is shown to the user.
*/
fun setInitialChapterError(error: Throwable) {
private fun setInitialChapterError(error: Throwable) {
logcat(LogPriority.ERROR, error)
finish()
toast(error.message)
@@ -201,17 +201,6 @@ class ReaderViewModel(
private val incognitoMode = preferences.incognitoMode().get()
override fun onCleared() {
val currentChapters = state.value.viewerChapters
if (currentChapters != null) {
currentChapters.unref()
saveReadingProgress(currentChapters.currChapter)
chapterToDownload?.let {
downloadManager.addDownloadsToStartOfQueue(listOf(it))
}
}
}
init {
// To save state
state.map { it.viewerChapters?.currChapter }
@@ -226,6 +215,17 @@ class ReaderViewModel(
.launchIn(viewModelScope)
}
override fun onCleared() {
val currentChapters = state.value.viewerChapters
if (currentChapters != null) {
currentChapters.unref()
saveReadingProgress(currentChapters.currChapter)
chapterToDownload?.let {
downloadManager.addDownloadsToStartOfQueue(listOf(it))
}
}
}
/**
* Called when the user pressed the back button and is going to leave the reader. Used to
* trigger deletion of the downloaded chapters.
@@ -338,10 +338,11 @@ class ReaderViewModel(
}
/**
* Called when the user is going to load the prev/next chapter through the menu button.
* Called when the user is going to load the prev/next chapter through the toolbar buttons.
*/
private suspend fun loadAdjacent(chapter: ReaderChapter) {
val loader = loader ?: return
saveCurrentChapterReadingProgress()
logcat { "Loading adjacent ${chapter.chapter.url}" }
@@ -3,25 +3,22 @@ import org.gradle.api.Task
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.TaskContainerScope
private val emptyResourcesElement = "<resources>\\s*</resources>|<resources/>".toRegex()
private val valuesPrefix = "values(-(b\\+)?)?".toRegex()
fun TaskContainerScope.registerLocalesConfigTask(project: Project): TaskProvider<Task> {
return with(project) {
register("generateLocalesConfig") {
val emptyResourcesElement = "<resources>\\s*</resources>|<resources/>".toRegex()
val valuesPrefix = "values-?".toRegex()
val languages = fileTree("$projectDir/src/main/res/")
.matching {
include("**/strings.xml")
}
.filterNot {
it.readText().contains(emptyResourcesElement)
}
.matching { include("**/strings.xml") }
.filterNot { it.readText().contains(emptyResourcesElement) }
.map { it.parentFile.name }
.sorted()
.joinToString(separator = "\n") {
val language = it
.replace(valuesPrefix, "")
.replace("-r", "-")
.replace("+", "-")
.takeIf(String::isNotBlank) ?: "en"
" <locale android:name=\"$language\"/>"
}
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.network
import android.content.Context
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
import okhttp3.Cache
import okhttp3.OkHttpClient
@@ -29,6 +30,7 @@ class NetworkHelper(context: Context) {
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.callTimeout(2, TimeUnit.MINUTES)
.addInterceptor(UncaughtExceptionInterceptor())
.addInterceptor(userAgentInterceptor)
if (preferences.verboseLogging().get()) {
@@ -17,6 +17,6 @@ class NetworkPreferences(
}
fun defaultUserAgent(): Preference<String> {
return preferenceStore.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0")
return preferenceStore.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0")
}
}
@@ -0,0 +1,24 @@
package eu.kanade.tachiyomi.network.interceptor
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
/**
* Catches any uncaught exceptions from later in the chain and rethrows as a non-fatal
* IOException to avoid catastrophic failure.
*
* This should be the first interceptor in the client.
*
* See https://square.github.io/okhttp/4.x/okhttp/okhttp3/-interceptor/
*/
class UncaughtExceptionInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
return try {
chain.proceed(chain.request())
} catch (e: Exception) {
throw IOException(e)
}
}
}
@@ -12,7 +12,7 @@ import tachiyomi.core.util.system.logcat
object WebViewUtil {
const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
const val MINIMUM_WEBVIEW_VERSION = 105
const val MINIMUM_WEBVIEW_VERSION = 108
fun supportsWebView(context: Context): Boolean {
try {
@@ -24,7 +24,7 @@ sealed class LibraryDisplayMode(
}
companion object {
val values = setOf(CompactGrid, ComfortableGrid, List, CoverOnlyGrid)
val values by lazy { setOf(CompactGrid, ComfortableGrid, List, CoverOnlyGrid) }
val default = CompactGrid
fun valueOf(flag: Long?): LibraryDisplayMode {
@@ -65,8 +65,8 @@ data class LibrarySort(
}
companion object {
val types = setOf(Type.Alphabetical, Type.LastRead, Type.LastUpdate, Type.UnreadCount, Type.TotalChapters, Type.LatestChapter, Type.ChapterFetchDate, Type.DateAdded)
val directions = setOf(Direction.Ascending, Direction.Descending)
val types by lazy { setOf(Type.Alphabetical, Type.LastRead, Type.LastUpdate, Type.UnreadCount, Type.TotalChapters, Type.LatestChapter, Type.ChapterFetchDate, Type.DateAdded) }
val directions by lazy { setOf(Direction.Ascending, Direction.Descending) }
val default = LibrarySort(Type.Alphabetical, Direction.Ascending)
fun valueOf(flag: Long): LibrarySort {
+1 -1
View File
@@ -46,7 +46,7 @@ coil-core = { module = "io.coil-kt:coil", version.ref = "coil_version" }
coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil_version" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil_version" }
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:846abe0"
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:c8e2650"
image-decoder = "com.github.tachiyomiorg:image-decoder:7879b45"
natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"