Compare commits

...

36 Commits

Author SHA1 Message Date
c8d68590db Release v0.14.7 2023-10-25 12:04:09 -04:00
94448faf97 Update website links 2023-10-25 11:58:00 -04:00
28028c789c Update default user agent string 2023-10-25 11:54:41 -04:00
f8834ee764 Avoid opening blobs as webpages
Fixes #10060

(cherry picked from commit 548f7f415a)
2023-10-25 11:53:40 -04:00
7c703b17d3 Change Shikimori domain from ".me" to ".one" (#10027)
(cherry picked from commit 8f3681d79f)
2023-10-25 11:52:24 -04:00
f77ade7dda Run Netlify Build Hook after Release (#9937)
* Run Netlify Build Hook after Release

* Add if statement

* Move if statement to job level instead of step

(cherry picked from commit 9e04f14a7b)
2023-10-25 11:51:23 -04:00
91712daee8 Use consistent extension icon URLs
Better caching between versions.

(cherry picked from commit 30f845139d)
2023-10-25 11:49:33 -04:00
c615f4d458 Release v0.14.6 2023-04-16 11:00:14 -04:00
9e09a20e65 Avoid uncaught exceptions from OkHttp interceptors crashing entire app
(cherry picked from commit 26d422b0ae)
2023-04-16 10:57:40 -04:00
7115a9b9fe Update track domain shikimori.me (#9333)
shikimori.me

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

(cherry picked from commit b7cd7b8b4e)
2023-04-16 10:52:53 -04:00
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
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
934ed0551a Bump subsampling-scale-image-view
(cherry picked from commit e5e18c2030)
2023-04-16 10:51:16 -04:00
38428c6ebe Show proper string in manga detail screen for SourceNotInstalledException
(cherry picked from commit 14d1bcacc9)
2023-04-16 10:51:05 -04:00
bf85e147e7 Set default automatic library updates to off
(cherry picked from commit abd23b6826)
2023-04-16 10:50:55 -04:00
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
c4ab2b4675 Bump default user agent string and minimum WebView version
(cherry picked from commit c6e5f8abd9)
2023-04-16 10:49:28 -04:00
aa2ec5940f Avoid crashing in SourcePreferencesScreen if source can't be loaded
(cherry picked from commit 4efca04765)
2023-04-16 10:49:11 -04:00
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
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
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
6f2bb18d72 Avoid crash when loading invalid extension package
(cherry picked from commit 3d7c136320)
2023-04-16 10:47:58 -04:00
b690de55e5 Release v0.14.5 2023-02-19 15:25:35 -05:00
83fda20078 Avoid crashes if headers can't be built for usage in WebView
(cherry picked from commit ec49411bee)
2023-02-19 11:52:09 -05:00
f656a37045 Avoid crashing if getChapterUrl is not implemented
Fixes #9105

(cherry picked from commit ceaf579cb0)
2023-02-19 11:51:53 -05:00
c58b495433 MainActivity: Avoid navigator-related crash when handling onNewIntent (#9104)
(cherry picked from commit d3dadf71e8)
2023-02-19 11:51:44 -05:00
242aeb6a68 Avoid crashing if opening browse with unavailable source
(cherry picked from commit 0ef7650c1a)
2023-02-19 11:50:33 -05:00
d9969cea8a Fix ID type mismatch in MigrateSearchScreenModel (#9090)
`it.id` is the source ID of the source being sorted.
`state.value.manga!!.id` is the manga ID of the selected manga.
`state.value.manga!!.source` is the source ID of the selected manga.

(cherry picked from commit dc2eaf0788)
2023-02-19 11:50:26 -05:00
d61db5931e Move reader preloading to IO scope
Maybe fixes #8440

(cherry picked from commit e052bdef96)
2023-02-19 11:50:05 -05:00
0ea3ac9807 Avoid preload download check if chapter is already loaded or loading
Maybe fixes #8953, #9060

(cherry picked from commit d522d6d545)
2023-02-19 11:49:58 -05:00
f9e43f574f MangaCoverDialog: Disable memory cache (#9066)
(cherry picked from commit 1671a56f42)
2023-02-19 11:49:51 -05:00
5ef11e61d0 Prioritize finding selected chapter when deduping reader chapters
Fixes #9054

(cherry picked from commit 23432e4405)
2023-02-19 11:49:44 -05:00
48546c3db4 Scaffold: Fix snackbar bottom inset (#9052)
(cherry picked from commit 34a586ce48)
2023-02-19 11:49:38 -05:00
4d87ed496c Remove FAB extra padding in DownloadQueueScreen (#9053)
(cherry picked from commit ad762f8303)
2023-02-19 11:49:32 -05:00
06d12e6562 Fix crash in library when selected category is deleted (#9044)
(cherry picked from commit 13bb45b4be)
2023-02-19 11:49:24 -05:00
50 changed files with 292 additions and 174 deletions

View File

@ -3,7 +3,7 @@
I acknowledge that:
- I have updated:
- To the latest version of the app (stable is v0.14.4)
- To the latest version of the app (stable is v0.14.7)
- 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

View File

@ -53,7 +53,7 @@ body:
label: Tachiyomi version
description: You can find your Tachiyomi version in **More → About**.
placeholder: |
Example: "0.14.4"
Example: "0.14.7"
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.4](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true

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.4](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true

View File

@ -104,3 +104,13 @@ jobs:
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update-website:
needs: [build]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
steps:
- name: Trigger Netlify build hook
run: curl -s -X POST -d {} "https://api.netlify.com/build_hooks/${TOKEN}"
env:
TOKEN: ${{ secrets.NETLIFY_HOOK_RELEASE }}

View File

@ -22,8 +22,8 @@ android {
defaultConfig {
applicationId = "eu.kanade.tachiyomi"
versionCode = 95
versionName = "0.14.4"
versionCode = 102
versionName = "0.14.7"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")

View File

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

View File

@ -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)
}
track
}
tracks.forEach { track ->
try {
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged) {
service.update(track.toDbTrack(), true)
insertTrack.await(track)
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())
}
.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 {

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.HelpOutline
@ -11,6 +12,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
@ -18,19 +20,22 @@ import eu.kanade.data.source.NoResultsException
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
import eu.kanade.presentation.browse.components.BrowseSourceList
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.EmptyScreenAction
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import kotlinx.coroutines.flow.StateFlow
import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.manga.model.Manga
@Composable
fun BrowseSourceContent(
source: CatalogueSource?,
source: Source?,
mangaList: LazyPagingItems<StateFlow<Manga>>,
columns: GridCells,
displayMode: LibraryDisplayMode,
@ -139,3 +144,24 @@ fun BrowseSourceContent(
}
}
}
@Composable
fun MissingSourceScreen(
source: SourceManager.StubSource,
navigateUp: () -> Unit,
) {
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = source.name,
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
) { paddingValues ->
EmptyScreen(
message = source.getSourceNotInstalledException().message!!,
modifier = Modifier.padding(paddingValues),
)
}
}

View File

@ -20,15 +20,15 @@ import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.RadioMenuItem
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import tachiyomi.domain.library.model.LibraryDisplayMode
@Composable
fun BrowseSourceToolbar(
searchQuery: String?,
onSearchQueryChange: (String?) -> Unit,
source: CatalogueSource?,
source: Source?,
displayMode: LibraryDisplayMode,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
navigateUp: () -> Unit,

View File

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

View File

@ -240,13 +240,16 @@ private fun ScaffoldLayout(
)
}.fastMap { it.measure(looseConstraints) }
val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
val bottomBarHeight = bottomBarPlaceables
.fastMaxBy { it.height }
?.height
?.takeIf { it != 0 }
val fabOffsetFromBottom = fabPlacement?.let {
max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx()
}
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight ?: bottomInset)
snackbarHeight + (fabOffsetFromBottom ?: max(bottomBarHeight ?: 0, bottomInset))
} else {
0
}

View File

@ -60,6 +60,9 @@ fun LibraryContent(
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
if (showPageTabs && categories.size > 1) {
if (categories.size <= pagerState.currentPage) {
pagerState.currentPage = categories.size - 1
}
LibraryTabs(
categories = categories,
currentPageIndex = pagerState.currentPage,

View File

@ -40,6 +40,7 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.core.view.updatePadding
import coil.imageLoader
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.size.Size
import eu.kanade.presentation.components.DropdownMenu
@ -162,6 +163,7 @@ fun MangaCoverDialog(
val request = ImageRequest.Builder(view.context)
.data(coverDataProvider())
.size(Size.ORIGINAL)
.memoryCachePolicy(CachePolicy.DISABLED)
.target { drawable ->
// Copy bitmap in case it came from memory cache
// Because SSIV needs to thoroughly read the image

View File

@ -62,7 +62,7 @@ fun MoreScreen(
WarningBanner(
textRes = R.string.fdroid_warning,
modifier = Modifier.clickable {
uriHandler.openUri("https://tachiyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version")
uriHandler.openUri("https://tachiyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds")
},
)
}

View File

@ -118,7 +118,7 @@ object AboutScreen : Screen {
item {
TextPreferenceWidget(
title = stringResource(R.string.help_translate),
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/help/contribution/#translation") },
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/docs/contribute#translation") },
)
}

View File

@ -70,7 +70,7 @@ object SettingsTrackingScreen : SearchableSettings {
@Composable
override fun RowScope.AppBarAction() {
val uriHandler = LocalUriHandler.current
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/help/guides/tracking/") }) {
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) {
Icon(
imageVector = Icons.Outlined.HelpOutline,
contentDescription = stringResource(R.string.tracking_guide),

View File

@ -124,6 +124,12 @@ fun WebViewScreenContent(
request: WebResourceRequest?,
): Boolean {
request?.let {
// Don't attempt to open blobs as webpages
if (it.url.toString().startsWith("blob:http")) {
return false
}
// Continue with request, but with custom headers
view?.loadUrl(it.url.toString(), headers)
}
return super.shouldOverrideUrlLoading(view, request)

View File

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

View File

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

View File

@ -340,11 +340,11 @@ class LibraryUpdateNotifier(private val context: Context) {
}
companion object {
const val HELP_WARNING_URL = "https://tachiyomi.org/help/faq/#why-does-the-app-warn-about-large-bulk-updates-and-downloads"
const val HELP_WARNING_URL = "https://tachiyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads"
}
}
private const val NOTIF_MAX_CHAPTERS = 5
private const val NOTIF_TITLE_MAX_LEN = 45
private const val NOTIF_ICON_SIZE = 192
private const val HELP_SKIPPED_URL = "https://tachiyomi.org/help/faq/#why-does-global-update-skip-some-entries"
private const val HELP_SKIPPED_URL = "https://tachiyomi.org/docs/faq/library#why-is-global-update-skipping-entries"

View File

@ -604,4 +604,4 @@ class LibraryUpdateService(
}
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
private const val ERROR_LOG_HELP_URL = "https://tachiyomi.org/help/guides/troubleshooting"
private const val ERROR_LOG_HELP_URL = "https://tachiyomi.org/docs/guides/troubleshooting/"

View File

@ -138,7 +138,7 @@ internal class AppUpdateNotifier(private val context: Context) {
setContentTitle(context.getString(R.string.update_check_notification_update_available))
setContentText(context.getString(R.string.update_check_fdroid_migration_info))
setSmallIcon(R.drawable.ic_tachi)
setContentIntent(NotificationHandler.openUrl(context, "https://tachiyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version"))
setContentIntent(NotificationHandler.openUrl(context, "https://tachiyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds"))
}
notificationBuilder.show(Notifications.ID_APP_UPDATE_PROMPT)
}

View File

@ -117,7 +117,7 @@ internal class ExtensionGithubApi {
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toExtensionSources() ?: emptyList(),
apkName = it.apk,
iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}",
iconUrl = "${getUrlPrefix()}icon/${it.pkg}.png",
)
}
}

View File

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

View File

@ -384,7 +384,7 @@ class LocalSource(
companion object {
const val ID = 0L
const val HELP_URL = "https://tachiyomi.org/help/guides/local-manga/"
const val HELP_URL = "https://tachiyomi.org/docs/guides/local-source/"
private const val DEFAULT_COVER_NAME = "cover.jpg"
private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS)

View File

@ -152,6 +152,6 @@ class SourceManager(
}
}
inner class SourceNotInstalledException(val sourceString: String) :
inner class SourceNotInstalledException(sourceString: String) :
Exception(context.getString(R.string.source_not_installed, sourceString))
}

View File

@ -102,7 +102,7 @@ class ExtensionDetailsScreenModel(
val extension = state.value.extension ?: return ""
if (!extension.hasReadme) {
return "https://tachiyomi.org/help/faq/#extensions"
return "https://tachiyomi.org/docs/faq/browse/extensions"
}
val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")

View File

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

View File

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

View File

@ -49,7 +49,7 @@ class MigrateSearchScreenModel(
.filter { it.lang in enabledLanguages }
.filterNot { "${it.id}" in disabledSources }
.sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" }))
.sortedByDescending { it.id == state.value.manga!!.id }
.sortedByDescending { it.id == state.value.manga!!.source }
}
override fun updateSearchQuery(query: String?) {

View File

@ -31,7 +31,7 @@ fun Screen.migrateSourceTab(): TabContent {
title = stringResource(R.string.migration_help_guide),
icon = Icons.Outlined.HelpOutline,
onClick = {
uriHandler.openUri("https://tachiyomi.org/help/guides/source-migration/")
uriHandler.openUri("https://tachiyomi.org/docs/guides/source-migration")
},
),
),

View File

@ -39,6 +39,7 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.BrowseSourceContent
import eu.kanade.presentation.browse.MissingSourceScreen
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
import eu.kanade.presentation.browse.components.RemoveMangaDialog
import eu.kanade.presentation.components.ChangeCategoryDialog
@ -48,7 +49,9 @@ import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
import eu.kanade.tachiyomi.ui.category.CategoryScreen
@ -73,17 +76,10 @@ data class BrowseSourceScreen(
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val scope = rememberCoroutineScope()
val context = LocalContext.current
val haptic = LocalHapticFeedback.current
val uriHandler = LocalUriHandler.current
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId, listingQuery) }
val state by screenModel.state.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
val navigator = LocalNavigator.currentOrThrow
val navigateUp: () -> Unit = {
when {
!state.isUserQuery && state.toolbarQuery != null -> screenModel.setToolbarQuery(null)
@ -91,8 +87,21 @@ data class BrowseSourceScreen(
}
}
val onHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }
if (screenModel.source is SourceManager.StubSource) {
MissingSourceScreen(
source = screenModel.source,
navigateUp = navigateUp,
)
return
}
val scope = rememberCoroutineScope()
val context = LocalContext.current
val haptic = LocalHapticFeedback.current
val uriHandler = LocalUriHandler.current
val snackbarHostState = remember { SnackbarHostState() }
val onHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }
val onWebViewClick = f@{
val source = screenModel.source as? HttpSource ?: return@f
navigator.push(
@ -147,7 +156,7 @@ data class BrowseSourceScreen(
Text(text = stringResource(R.string.popular))
},
)
if (screenModel.source.supportsLatest) {
if ((screenModel.source as CatalogueSource).supportsLatest) {
FilterChip(
selected = state.listing == Listing.Latest,
onClick = {

View File

@ -102,23 +102,25 @@ class BrowseSourceScreenModel(
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
val source = sourceManager.get(sourceId) as CatalogueSource
val source = sourceManager.getOrStub(sourceId)
init {
mutableState.update {
var query: String? = null
var listing = it.listing
if (source is CatalogueSource) {
mutableState.update {
var query: String? = null
var listing = it.listing
if (listing is Listing.Search) {
query = listing.query
listing = Listing.Search(query, source.getFilterList())
if (listing is Listing.Search) {
query = listing.query
listing = Listing.Search(query, source.getFilterList())
}
it.copy(
listing = listing,
filters = source.getFilterList(),
toolbarQuery = query,
)
}
it.copy(
listing = listing,
filters = source.getFilterList(),
toolbarQuery = query,
)
}
}
@ -162,6 +164,8 @@ class BrowseSourceScreenModel(
}
fun resetFilters() {
if (source !is CatalogueSource) return
mutableState.update { it.copy(filters = source.getFilterList()) }
}
@ -170,6 +174,8 @@ class BrowseSourceScreenModel(
}
fun search(query: String? = null, filters: FilterList? = null) {
if (source !is CatalogueSource) return
val input = state.value.listing as? Listing.Search
?: Listing.Search(query = null, filters = source.getFilterList())
@ -185,6 +191,8 @@ class BrowseSourceScreenModel(
}
fun searchGenre(genreName: String) {
if (source !is CatalogueSource) return
val defaultFilters = source.getFilterList()
var genreExists = false

View File

@ -8,7 +8,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
@ -226,7 +225,6 @@ object DownloadQueueScreen : Screen {
}
},
expanded = fabExpanded,
modifier = Modifier.navigationBarsPadding(),
)
}
},

View File

@ -156,7 +156,7 @@ object LibraryTab : Tab {
EmptyScreenAction(
stringResId = R.string.getting_started_guide,
icon = Icons.Outlined.HelpOutline,
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") },
),
),
)

View File

@ -3,12 +3,14 @@ package eu.kanade.tachiyomi.ui.main
import android.animation.ValueAnimator
import android.app.SearchManager
import android.app.assist.AssistContent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
@ -41,6 +43,7 @@ import androidx.core.animation.doOnEnd
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.util.Consumer
import androidx.core.view.WindowCompat
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
@ -85,7 +88,10 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.setComposeContent
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
@ -119,8 +125,7 @@ class MainActivity : BaseActivity() {
*/
private var settingsSheet: LibrarySettingsSheet? = null
private var isHandlingShortcut: Boolean = false
private lateinit var navigator: Navigator
private var navigator: Navigator? = null
override fun onCreate(savedInstanceState: Bundle?) {
// Prevent splash screen showing up on configuration changes
@ -210,7 +215,7 @@ class MainActivity : BaseActivity() {
if (savedInstanceState == null) {
// Set start screen
handleIntentAction(intent)
handleIntentAction(intent, navigator)
// Reset Incognito Mode on relaunch
preferences.incognitoMode().set(false)
@ -257,6 +262,7 @@ class MainActivity : BaseActivity() {
}
CheckForUpdate()
HandleOnNewIntent(context = context, navigator = navigator)
}
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
@ -288,7 +294,7 @@ class MainActivity : BaseActivity() {
override fun onProvideAssistContent(outContent: AssistContent) {
super.onProvideAssistContent(outContent)
when (val screen = navigator.lastItem) {
when (val screen = navigator?.lastItem) {
is AssistContentScreen -> {
screen.onProvideAssistUrl()?.let { outContent.webUri = it.toUri() }
}
@ -319,6 +325,18 @@ class MainActivity : BaseActivity() {
}
}
@Composable
fun HandleOnNewIntent(context: Context, navigator: Navigator) {
LaunchedEffect(Unit) {
callbackFlow<Intent> {
val componentActivity = context as ComponentActivity
val consumer = Consumer<Intent> { trySend(it) }
componentActivity.addOnNewIntentListener(consumer)
awaitClose { componentActivity.removeOnNewIntentListener(consumer) }
}.collectLatest { handleIntentAction(it, navigator) }
}
}
@Composable
private fun CheckForUpdate() {
val context = LocalContext.current
@ -387,37 +405,26 @@ class MainActivity : BaseActivity() {
}
}
override fun onNewIntent(intent: Intent) {
lifecycleScope.launch {
val handle = handleIntentAction(intent)
if (!handle) {
super.onNewIntent(intent)
}
}
}
private suspend fun handleIntentAction(intent: Intent): Boolean {
private fun handleIntentAction(intent: Intent, navigator: Navigator): Boolean {
val notificationId = intent.getIntExtra("notificationId", -1)
if (notificationId > -1) {
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
}
isHandlingShortcut = true
when (intent.action) {
Constants.SHORTCUT_LIBRARY -> HomeScreen.openTab(HomeScreen.Tab.Library())
val tabToOpen = when (intent.action) {
Constants.SHORTCUT_LIBRARY -> HomeScreen.Tab.Library()
Constants.SHORTCUT_MANGA -> {
val idToOpen = intent.extras?.getLong(Constants.MANGA_EXTRA) ?: return false
navigator.popUntilRoot()
HomeScreen.openTab(HomeScreen.Tab.Library(idToOpen))
HomeScreen.Tab.Library(idToOpen)
}
Constants.SHORTCUT_UPDATES -> HomeScreen.openTab(HomeScreen.Tab.Updates)
Constants.SHORTCUT_HISTORY -> HomeScreen.openTab(HomeScreen.Tab.History)
Constants.SHORTCUT_SOURCES -> HomeScreen.openTab(HomeScreen.Tab.Browse(false))
Constants.SHORTCUT_EXTENSIONS -> HomeScreen.openTab(HomeScreen.Tab.Browse(true))
Constants.SHORTCUT_UPDATES -> HomeScreen.Tab.Updates
Constants.SHORTCUT_HISTORY -> HomeScreen.Tab.History
Constants.SHORTCUT_SOURCES -> HomeScreen.Tab.Browse(false)
Constants.SHORTCUT_EXTENSIONS -> HomeScreen.Tab.Browse(true)
Constants.SHORTCUT_DOWNLOADS -> {
navigator.popUntilRoot()
HomeScreen.openTab(HomeScreen.Tab.More(toDownloads = true))
HomeScreen.Tab.More(toDownloads = true)
}
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
// If the intent match the "standard" Android search intent
@ -429,6 +436,7 @@ class MainActivity : BaseActivity() {
navigator.popUntilRoot()
navigator.push(GlobalSearchScreen(query))
}
null
}
INTENT_SEARCH -> {
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
@ -437,15 +445,16 @@ class MainActivity : BaseActivity() {
navigator.popUntilRoot()
navigator.push(GlobalSearchScreen(query, filter))
}
null
}
else -> {
isHandlingShortcut = false
return false
}
else -> return false
}
if (tabToOpen != null) {
lifecycleScope.launch { HomeScreen.openTab(tabToOpen) }
}
ready = true
isHandlingShortcut = false
return true
}
@ -456,7 +465,7 @@ class MainActivity : BaseActivity() {
}
override fun onBackPressed() {
if (navigator.size == 1 &&
if (navigator?.size == 1 &&
!onBackPressedDispatcher.hasEnabledCallbacks() &&
libraryPreferences.autoClearChapterCache().get()
) {

View File

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

View File

@ -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)
@ -871,7 +867,7 @@ class ReaderActivity : BaseActivity() {
* the viewer is reaching the beginning or end of a chapter or the transition page is active.
*/
fun requestPreloadChapter(chapter: ReaderChapter) {
lifecycleScope.launch { viewModel.preloadChapter(chapter) }
lifecycleScope.launchIO { viewModel.preloadChapter(chapter) }
}
/**

View File

@ -177,10 +177,11 @@ class ReaderViewModel(
}.run {
if (readerPreferences.skipDupe().get()) {
groupBy { it.chapterNumber }
.mapValues { (_, chapters) ->
chapters.find { it.id == chapterId || it.scanlator == selectedChapter.scanlator } ?: chapters.first()
.map { (_, chapters) ->
chapters.find { it.id == selectedChapter.id }
?: chapters.find { it.scanlator == selectedChapter.scanlator }
?: chapters.first()
}
.values
} else {
this
}
@ -200,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 }
@ -225,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.
@ -337,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}" }
@ -364,6 +366,10 @@ class ReaderViewModel(
* that the user doesn't have to wait too long to continue reading.
*/
private suspend fun preload(chapter: ReaderChapter) {
if (chapter.state is ReaderChapter.State.Loaded || chapter.state == ReaderChapter.State.Loading) {
return
}
if (chapter.pageLoader is HttpPageLoader) {
val manga = manga ?: return
val dbChapter = chapter.chapter
@ -383,20 +389,17 @@ class ReaderViewModel(
return
}
logcat { "Preloading ${chapter.chapter.url}" }
val loader = loader ?: return
withIOContext {
try {
loader.loadChapter(chapter)
} catch (e: Throwable) {
if (e is CancellationException) {
throw e
}
return@withIOContext
try {
logcat { "Preloading ${chapter.chapter.url}" }
loader.loadChapter(chapter)
} catch (e: Throwable) {
if (e is CancellationException) {
throw e
}
eventChannel.trySend(Event.ReloadViewerChapters)
return
}
eventChannel.trySend(Event.ReloadViewerChapters)
}
/**
@ -580,7 +583,12 @@ class ReaderViewModel(
val sChapter = getCurrentChapter()?.chapter ?: return null
val source = getSource() ?: return null
return source.getChapterUrl(sChapter)
return try {
source.getChapterUrl(sChapter)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
null
}
}
/**

View File

@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.setComposeContent
import logcat.LogPriority
import okhttp3.HttpUrl.Companion.toHttpUrl
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
@ -47,7 +48,11 @@ class WebViewActivity : BaseActivity() {
var headers = emptyMap<String, String>()
(sourceManager.get(intent.extras!!.getLong(SOURCE_KEY)) as? HttpSource)?.let { source ->
headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
try {
headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to build headers" }
}
}
setComposeContent {

View File

@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import logcat.LogPriority
import okhttp3.HttpUrl.Companion.toHttpUrl
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.Injekt
@ -25,7 +26,11 @@ class WebViewScreenModel(
init {
sourceId?.let { sourceManager.get(it) as? HttpSource }?.let { source ->
headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
try {
headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to build headers" }
}
}
}

View File

@ -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\"/>"
}

View File

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

View File

@ -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:109.0) Gecko/20100101 Firefox/118.0")
}
}

View File

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

View File

@ -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 = 114
fun supportsWebView(context: Context): Boolean {
try {

View File

@ -1,7 +1,7 @@
package tachiyomi.core
object Constants {
const val URL_HELP = "https://tachiyomi.org/help/"
const val URL_HELP = "https://tachiyomi.org/docs/guides/troubleshooting/"
const val MANGA_EXTRA = "manga"

View File

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

View File

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

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"