mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-27 18:05:53 +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 |
.github
app
build.gradle.kts
src
main
java
eu
kanade
domain
presentation
browse
components
library
components
manga
components
more
webview
tachiyomi
data
download
library
updater
extension
source
ui
browse
extension
migration
source
download
library
main
manga
reader
webview
buildSrc/src/main/kotlin
core/src/main/java
eu
kanade
tachiyomi
network
util
system
tachiyomi
core
domain/src/main/java/tachiyomi/domain/library/model
gradle
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 results = withIOContext {
|
||||
delayedTrackingStore.getItems()
|
||||
.mapNotNull {
|
||||
val track = getTracks.awaitOne(it.trackId)
|
||||
if (track == null) {
|
||||
delayedTrackingStore.remove(it.trackId)
|
||||
}
|
||||
track
|
||||
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||
}
|
||||
|
||||
tracks.forEach { track ->
|
||||
.mapNotNull { track ->
|
||||
try {
|
||||
val service = trackManager.getService(track.syncId)
|
||||
if (service != null && service.isLogged) {
|
||||
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
|
||||
service.update(track.toDbTrack(), true)
|
||||
insertTrack.await(track)
|
||||
}
|
||||
delayedTrackingStore.remove(track.id)
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,10 +64,12 @@ fun DeleteLibraryMangaDialog(
|
||||
list.forEach { state ->
|
||||
val onCheck = {
|
||||
val index = list.indexOf(state)
|
||||
if (index != -1) {
|
||||
val mutableList = list.toMutableList()
|
||||
mutableList[index] = state.next() as CheckboxState.State<Int>
|
||||
list = mutableList.toList()
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
@ -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,9 +102,10 @@ class BrowseSourceScreenModel(
|
||||
|
||||
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
|
||||
|
||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||
val source = sourceManager.getOrStub(sourceId)
|
||||
|
||||
init {
|
||||
if (source is CatalogueSource) {
|
||||
mutableState.update {
|
||||
var query: String? = null
|
||||
var listing = it.listing
|
||||
@ -121,6 +122,7 @@ class BrowseSourceScreenModel(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sheet containing filter items.
|
||||
@ -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,21 +389,18 @@ class ReaderViewModel(
|
||||
return
|
||||
}
|
||||
|
||||
logcat { "Preloading ${chapter.chapter.url}" }
|
||||
|
||||
val loader = loader ?: return
|
||||
withIOContext {
|
||||
try {
|
||||
logcat { "Preloading ${chapter.chapter.url}" }
|
||||
loader.loadChapter(chapter)
|
||||
} catch (e: Throwable) {
|
||||
if (e is CancellationException) {
|
||||
throw e
|
||||
}
|
||||
return@withIOContext
|
||||
return
|
||||
}
|
||||
eventChannel.trySend(Event.ReloadViewerChapters)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every time a page changes on the reader. Used to mark the flag of chapters being
|
||||
@ -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 ->
|
||||
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 ->
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
24
core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt
Normal file
24
core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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