mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-26 11:07:51 +02:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
c8d68590db | |||
94448faf97 | |||
28028c789c | |||
f8834ee764 | |||
7c703b17d3 | |||
f77ade7dda | |||
91712daee8 | |||
c615f4d458 | |||
9e09a20e65 | |||
7115a9b9fe | |||
fd8b97fc87 | |||
4dd67e4348 | |||
10973bf3cd | |||
934ed0551a | |||
38428c6ebe | |||
bf85e147e7 | |||
d2dd34c2e5 | |||
c4ab2b4675 | |||
aa2ec5940f | |||
79323de326 | |||
08e6487a9a | |||
4498b10a10 | |||
6f2bb18d72 | |||
b690de55e5 | |||
83fda20078 | |||
f656a37045 | |||
c58b495433 | |||
242aeb6a68 | |||
d9969cea8a | |||
d61db5931e | |||
0ea3ac9807 | |||
f9e43f574f | |||
5ef11e61d0 | |||
48546c3db4 | |||
4d87ed496c | |||
06d12e6562 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -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
|
||||
|
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -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
|
||||
|
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -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
|
||||
|
10
.github/workflows/build_push.yml
vendored
10
.github/workflows/build_push.yml
vendored
@ -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 }}
|
||||
|
@ -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()}\"")
|
||||
|
@ -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)
|
||||
}
|
||||
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 {
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -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") },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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/"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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.")
|
||||
|
@ -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()
|
||||
|
@ -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?) {
|
||||
|
@ -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")
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -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") },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -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()
|
||||
) {
|
||||
|
@ -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)
|
||||
@ -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) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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:109.0) Gecko/20100101 Firefox/118.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 = 114
|
||||
|
||||
fun supportsWebView(context: Context): Boolean {
|
||||
try {
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 {
|
@ -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"
|
||||
|
Reference in New Issue
Block a user