mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-27 01:45: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 acknowledge that:
|
||||||
|
|
||||||
- I have updated:
|
- 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
|
- All extensions
|
||||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
- 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
|
- 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
|
label: Tachiyomi version
|
||||||
description: You can find your Tachiyomi version in **More → About**.
|
description: You can find your Tachiyomi version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "0.14.4"
|
Example: "0.14.7"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
||||||
required: true
|
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
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
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
|
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).
|
- 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
|
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
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
10
.github/workflows/build_push.yml
vendored
10
.github/workflows/build_push.yml
vendored
@ -104,3 +104,13 @@ jobs:
|
|||||||
prerelease: false
|
prerelease: false
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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 {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
applicationId = "eu.kanade.tachiyomi"
|
||||||
versionCode = 95
|
versionCode = 102
|
||||||
versionName = "0.14.4"
|
versionName = "0.14.7"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
|
@ -22,7 +22,7 @@ class LibraryPreferences(
|
|||||||
|
|
||||||
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
|
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 libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
|
||||||
|
|
||||||
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
|
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 trackManager = Injekt.get<TrackManager>()
|
||||||
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
||||||
|
|
||||||
withIOContext {
|
val results = withIOContext {
|
||||||
val tracks = delayedTrackingStore.getItems().mapNotNull {
|
delayedTrackingStore.getItems()
|
||||||
val track = getTracks.awaitOne(it.trackId)
|
.mapNotNull {
|
||||||
if (track == null) {
|
val track = getTracks.awaitOne(it.trackId)
|
||||||
delayedTrackingStore.remove(it.trackId)
|
if (track == null) {
|
||||||
}
|
delayedTrackingStore.remove(it.trackId)
|
||||||
track
|
}
|
||||||
}
|
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||||
|
}
|
||||||
tracks.forEach { track ->
|
.mapNotNull { track ->
|
||||||
try {
|
try {
|
||||||
val service = trackManager.getService(track.syncId)
|
val service = trackManager.getService(track.syncId)
|
||||||
if (service != null && service.isLogged) {
|
if (service != null && service.isLogged) {
|
||||||
service.update(track.toDbTrack(), true)
|
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
|
||||||
insertTrack.await(track)
|
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 {
|
companion object {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
@ -11,6 +12,7 @@ import androidx.compose.material3.SnackbarHostState
|
|||||||
import androidx.compose.material3.SnackbarResult
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
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.BrowseSourceComfortableGrid
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceList
|
import eu.kanade.presentation.browse.components.BrowseSourceList
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.EmptyScreenAction
|
import eu.kanade.presentation.components.EmptyScreenAction
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceContent(
|
fun BrowseSourceContent(
|
||||||
source: CatalogueSource?,
|
source: Source?,
|
||||||
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
columns: GridCells,
|
columns: GridCells,
|
||||||
displayMode: LibraryDisplayMode,
|
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.RadioMenuItem
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceToolbar(
|
fun BrowseSourceToolbar(
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onSearchQueryChange: (String?) -> Unit,
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
source: CatalogueSource?,
|
source: Source?,
|
||||||
displayMode: LibraryDisplayMode,
|
displayMode: LibraryDisplayMode,
|
||||||
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
|
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
|
@ -64,9 +64,11 @@ fun DeleteLibraryMangaDialog(
|
|||||||
list.forEach { state ->
|
list.forEach { state ->
|
||||||
val onCheck = {
|
val onCheck = {
|
||||||
val index = list.indexOf(state)
|
val index = list.indexOf(state)
|
||||||
val mutableList = list.toMutableList()
|
if (index != -1) {
|
||||||
mutableList[index] = state.next() as CheckboxState.State<Int>
|
val mutableList = list.toMutableList()
|
||||||
list = mutableList.toList()
|
mutableList[index] = state.next() as CheckboxState.State<Int>
|
||||||
|
list = mutableList.toList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
|
@ -240,13 +240,16 @@ private fun ScaffoldLayout(
|
|||||||
)
|
)
|
||||||
}.fastMap { it.measure(looseConstraints) }
|
}.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 {
|
val fabOffsetFromBottom = fabPlacement?.let {
|
||||||
max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx()
|
max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx()
|
||||||
}
|
}
|
||||||
|
|
||||||
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
|
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
|
||||||
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight ?: bottomInset)
|
snackbarHeight + (fabOffsetFromBottom ?: max(bottomBarHeight ?: 0, bottomInset))
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,9 @@ fun LibraryContent(
|
|||||||
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
||||||
|
|
||||||
if (showPageTabs && categories.size > 1) {
|
if (showPageTabs && categories.size > 1) {
|
||||||
|
if (categories.size <= pagerState.currentPage) {
|
||||||
|
pagerState.currentPage = categories.size - 1
|
||||||
|
}
|
||||||
LibraryTabs(
|
LibraryTabs(
|
||||||
categories = categories,
|
categories = categories,
|
||||||
currentPageIndex = pagerState.currentPage,
|
currentPageIndex = pagerState.currentPage,
|
||||||
|
@ -40,6 +40,7 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import coil.imageLoader
|
import coil.imageLoader
|
||||||
|
import coil.request.CachePolicy
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Size
|
import coil.size.Size
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
@ -162,6 +163,7 @@ fun MangaCoverDialog(
|
|||||||
val request = ImageRequest.Builder(view.context)
|
val request = ImageRequest.Builder(view.context)
|
||||||
.data(coverDataProvider())
|
.data(coverDataProvider())
|
||||||
.size(Size.ORIGINAL)
|
.size(Size.ORIGINAL)
|
||||||
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.target { drawable ->
|
.target { drawable ->
|
||||||
// Copy bitmap in case it came from memory cache
|
// Copy bitmap in case it came from memory cache
|
||||||
// Because SSIV needs to thoroughly read the image
|
// Because SSIV needs to thoroughly read the image
|
||||||
|
@ -62,7 +62,7 @@ fun MoreScreen(
|
|||||||
WarningBanner(
|
WarningBanner(
|
||||||
textRes = R.string.fdroid_warning,
|
textRes = R.string.fdroid_warning,
|
||||||
modifier = Modifier.clickable {
|
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 {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.help_translate),
|
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
|
@Composable
|
||||||
override fun RowScope.AppBarAction() {
|
override fun RowScope.AppBarAction() {
|
||||||
val uriHandler = LocalUriHandler.current
|
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(
|
Icon(
|
||||||
imageVector = Icons.Outlined.HelpOutline,
|
imageVector = Icons.Outlined.HelpOutline,
|
||||||
contentDescription = stringResource(R.string.tracking_guide),
|
contentDescription = stringResource(R.string.tracking_guide),
|
||||||
|
@ -124,6 +124,12 @@ fun WebViewScreenContent(
|
|||||||
request: WebResourceRequest?,
|
request: WebResourceRequest?,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
request?.let {
|
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)
|
view?.loadUrl(it.url.toString(), headers)
|
||||||
}
|
}
|
||||||
return super.shouldOverrideUrlLoading(view, request)
|
return super.shouldOverrideUrlLoading(view, request)
|
||||||
|
@ -339,7 +339,6 @@ class Downloader(
|
|||||||
?.filter { it.name!!.endsWith(".tmp") }
|
?.filter { it.name!!.endsWith(".tmp") }
|
||||||
?.forEach { it.delete() }
|
?.forEach { it.delete() }
|
||||||
|
|
||||||
download.downloadedImages = 0
|
|
||||||
download.status = Download.State.DOWNLOADING
|
download.status = Download.State.DOWNLOADING
|
||||||
}
|
}
|
||||||
// Get all the URLs to the source images, fetch pages if necessary
|
// Get all the URLs to the source images, fetch pages if necessary
|
||||||
@ -403,7 +402,6 @@ class Downloader(
|
|||||||
}
|
}
|
||||||
page.uri = file.uri
|
page.uri = file.uri
|
||||||
page.progress = 100
|
page.progress = 100
|
||||||
download.downloadedImages++
|
|
||||||
page.status = Page.State.READY
|
page.status = Page.State.READY
|
||||||
}
|
}
|
||||||
.map { page }
|
.map { page }
|
||||||
|
@ -21,9 +21,8 @@ data class Download(
|
|||||||
val totalProgress: Int
|
val totalProgress: Int
|
||||||
get() = pages?.sumOf(Page::progress) ?: 0
|
get() = pages?.sumOf(Page::progress) ?: 0
|
||||||
|
|
||||||
@Volatile
|
val downloadedImages: Int
|
||||||
@Transient
|
get() = pages?.count { it.status == Page.State.READY } ?: 0
|
||||||
var downloadedImages: Int = 0
|
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
@Transient
|
@Transient
|
||||||
|
@ -340,11 +340,11 @@ class LibraryUpdateNotifier(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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_MAX_CHAPTERS = 5
|
||||||
private const val NOTIF_TITLE_MAX_LEN = 45
|
private const val NOTIF_TITLE_MAX_LEN = 45
|
||||||
private const val NOTIF_ICON_SIZE = 192
|
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 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))
|
setContentTitle(context.getString(R.string.update_check_notification_update_available))
|
||||||
setContentText(context.getString(R.string.update_check_fdroid_migration_info))
|
setContentText(context.getString(R.string.update_check_fdroid_migration_info))
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
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)
|
notificationBuilder.show(Notifications.ID_APP_UPDATE_PROMPT)
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ internal class ExtensionGithubApi {
|
|||||||
hasChangelog = it.hasChangelog == 1,
|
hasChangelog = it.hasChangelog == 1,
|
||||||
sources = it.sources?.toExtensionSources() ?: emptyList(),
|
sources = it.sources?.toExtensionSources() ?: emptyList(),
|
||||||
apkName = it.apk,
|
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
|
// Validate lib version
|
||||||
val libVersion = versionName.substringBeforeLast('.').toDouble()
|
val libVersion = versionName.substringBeforeLast('.').toDoubleOrNull()
|
||||||
if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
|
if (libVersion == null || libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
|
||||||
logcat(LogPriority.WARN) {
|
logcat(LogPriority.WARN) {
|
||||||
"Lib version is $libVersion, while only versions " +
|
"Lib version is $libVersion, while only versions " +
|
||||||
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
|
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
|
||||||
@ -136,7 +136,6 @@ internal object ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val signatureHash = getSignatureHash(pkgInfo)
|
val signatureHash = getSignatureHash(pkgInfo)
|
||||||
|
|
||||||
if (signatureHash == null) {
|
if (signatureHash == null) {
|
||||||
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
||||||
return LoadResult.Error
|
return LoadResult.Error
|
||||||
|
@ -384,7 +384,7 @@ class LocalSource(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ID = 0L
|
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 const val DEFAULT_COVER_NAME = "cover.jpg"
|
||||||
private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS)
|
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))
|
Exception(context.getString(R.string.source_not_installed, sourceString))
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ class ExtensionDetailsScreenModel(
|
|||||||
val extension = state.value.extension ?: return ""
|
val extension = state.value.extension ?: return ""
|
||||||
|
|
||||||
if (!extension.hasReadme) {
|
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.")
|
val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
|
||||||
|
@ -61,7 +61,7 @@ class SourcePreferencesScreen(val sourceId: Long) : Screen {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(text = Injekt.get<SourceManager>().get(sourceId)!!.toString()) },
|
title = { Text(text = Injekt.get<SourceManager>().getOrStub(sourceId).toString()) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = navigator::pop) {
|
IconButton(onClick = navigator::pop) {
|
||||||
Icon(
|
Icon(
|
||||||
|
@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
|
|||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -23,6 +25,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEachIndexed
|
import androidx.compose.ui.util.fastForEachIndexed
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||||
@ -111,7 +114,9 @@ internal fun MigrateDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Row {
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onClickTitle()
|
onClickTitle()
|
||||||
|
@ -49,7 +49,7 @@ class MigrateSearchScreenModel(
|
|||||||
.filter { it.lang in enabledLanguages }
|
.filter { it.lang in enabledLanguages }
|
||||||
.filterNot { "${it.id}" in disabledSources }
|
.filterNot { "${it.id}" in disabledSources }
|
||||||
.sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" }))
|
.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?) {
|
override fun updateSearchQuery(query: String?) {
|
||||||
|
@ -31,7 +31,7 @@ fun Screen.migrateSourceTab(): TabContent {
|
|||||||
title = stringResource(R.string.migration_help_guide),
|
title = stringResource(R.string.migration_help_guide),
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = {
|
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.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.BrowseSourceContent
|
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.BrowseSourceToolbar
|
||||||
import eu.kanade.presentation.browse.components.RemoveMangaDialog
|
import eu.kanade.presentation.browse.components.RemoveMangaDialog
|
||||||
import eu.kanade.presentation.components.ChangeCategoryDialog
|
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.AssistContentScreen
|
||||||
import eu.kanade.presentation.util.padding
|
import eu.kanade.presentation.util.padding
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
@ -73,17 +76,10 @@ data class BrowseSourceScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
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 screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId, listingQuery) }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val navigateUp: () -> Unit = {
|
val navigateUp: () -> Unit = {
|
||||||
when {
|
when {
|
||||||
!state.isUserQuery && state.toolbarQuery != null -> screenModel.setToolbarQuery(null)
|
!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 onWebViewClick = f@{
|
||||||
val source = screenModel.source as? HttpSource ?: return@f
|
val source = screenModel.source as? HttpSource ?: return@f
|
||||||
navigator.push(
|
navigator.push(
|
||||||
@ -147,7 +156,7 @@ data class BrowseSourceScreen(
|
|||||||
Text(text = stringResource(R.string.popular))
|
Text(text = stringResource(R.string.popular))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (screenModel.source.supportsLatest) {
|
if ((screenModel.source as CatalogueSource).supportsLatest) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = state.listing == Listing.Latest,
|
selected = state.listing == Listing.Latest,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
@ -102,23 +102,25 @@ class BrowseSourceScreenModel(
|
|||||||
|
|
||||||
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
|
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
|
||||||
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
val source = sourceManager.getOrStub(sourceId)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mutableState.update {
|
if (source is CatalogueSource) {
|
||||||
var query: String? = null
|
mutableState.update {
|
||||||
var listing = it.listing
|
var query: String? = null
|
||||||
|
var listing = it.listing
|
||||||
|
|
||||||
if (listing is Listing.Search) {
|
if (listing is Listing.Search) {
|
||||||
query = listing.query
|
query = listing.query
|
||||||
listing = Listing.Search(query, source.getFilterList())
|
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() {
|
fun resetFilters() {
|
||||||
|
if (source !is CatalogueSource) return
|
||||||
|
|
||||||
mutableState.update { it.copy(filters = source.getFilterList()) }
|
mutableState.update { it.copy(filters = source.getFilterList()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +174,8 @@ class BrowseSourceScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String? = null, filters: FilterList? = null) {
|
fun search(query: String? = null, filters: FilterList? = null) {
|
||||||
|
if (source !is CatalogueSource) return
|
||||||
|
|
||||||
val input = state.value.listing as? Listing.Search
|
val input = state.value.listing as? Listing.Search
|
||||||
?: Listing.Search(query = null, filters = source.getFilterList())
|
?: Listing.Search(query = null, filters = source.getFilterList())
|
||||||
|
|
||||||
@ -185,6 +191,8 @@ class BrowseSourceScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun searchGenre(genreName: String) {
|
fun searchGenre(genreName: String) {
|
||||||
|
if (source !is CatalogueSource) return
|
||||||
|
|
||||||
val defaultFilters = source.getFilterList()
|
val defaultFilters = source.getFilterList()
|
||||||
var genreExists = false
|
var genreExists = false
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import androidx.compose.animation.fadeOut
|
|||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
@ -226,7 +225,6 @@ object DownloadQueueScreen : Screen {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
expanded = fabExpanded,
|
expanded = fabExpanded,
|
||||||
modifier = Modifier.navigationBarsPadding(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -156,7 +156,7 @@ object LibraryTab : Tab {
|
|||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.getting_started_guide,
|
stringResId = R.string.getting_started_guide,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
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.animation.ValueAnimator
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.app.assist.AssistContent
|
import android.app.assist.AssistContent
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -41,6 +43,7 @@ import androidx.core.animation.doOnEnd
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.splashscreen.SplashScreen
|
import androidx.core.splashscreen.SplashScreen
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
|
import androidx.core.util.Consumer
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
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.system.toast
|
||||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@ -119,8 +125,7 @@ class MainActivity : BaseActivity() {
|
|||||||
*/
|
*/
|
||||||
private var settingsSheet: LibrarySettingsSheet? = null
|
private var settingsSheet: LibrarySettingsSheet? = null
|
||||||
|
|
||||||
private var isHandlingShortcut: Boolean = false
|
private var navigator: Navigator? = null
|
||||||
private lateinit var navigator: Navigator
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// Prevent splash screen showing up on configuration changes
|
// Prevent splash screen showing up on configuration changes
|
||||||
@ -210,7 +215,7 @@ class MainActivity : BaseActivity() {
|
|||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
// Set start screen
|
// Set start screen
|
||||||
handleIntentAction(intent)
|
handleIntentAction(intent, navigator)
|
||||||
|
|
||||||
// Reset Incognito Mode on relaunch
|
// Reset Incognito Mode on relaunch
|
||||||
preferences.incognitoMode().set(false)
|
preferences.incognitoMode().set(false)
|
||||||
@ -257,6 +262,7 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CheckForUpdate()
|
CheckForUpdate()
|
||||||
|
HandleOnNewIntent(context = context, navigator = navigator)
|
||||||
}
|
}
|
||||||
|
|
||||||
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
|
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
|
||||||
@ -288,7 +294,7 @@ class MainActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onProvideAssistContent(outContent: AssistContent) {
|
override fun onProvideAssistContent(outContent: AssistContent) {
|
||||||
super.onProvideAssistContent(outContent)
|
super.onProvideAssistContent(outContent)
|
||||||
when (val screen = navigator.lastItem) {
|
when (val screen = navigator?.lastItem) {
|
||||||
is AssistContentScreen -> {
|
is AssistContentScreen -> {
|
||||||
screen.onProvideAssistUrl()?.let { outContent.webUri = it.toUri() }
|
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
|
@Composable
|
||||||
private fun CheckForUpdate() {
|
private fun CheckForUpdate() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -387,37 +405,26 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
private fun handleIntentAction(intent: Intent, navigator: Navigator): Boolean {
|
||||||
lifecycleScope.launch {
|
|
||||||
val handle = handleIntentAction(intent)
|
|
||||||
if (!handle) {
|
|
||||||
super.onNewIntent(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun handleIntentAction(intent: Intent): Boolean {
|
|
||||||
val notificationId = intent.getIntExtra("notificationId", -1)
|
val notificationId = intent.getIntExtra("notificationId", -1)
|
||||||
if (notificationId > -1) {
|
if (notificationId > -1) {
|
||||||
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
|
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
isHandlingShortcut = true
|
val tabToOpen = when (intent.action) {
|
||||||
|
Constants.SHORTCUT_LIBRARY -> HomeScreen.Tab.Library()
|
||||||
when (intent.action) {
|
|
||||||
Constants.SHORTCUT_LIBRARY -> HomeScreen.openTab(HomeScreen.Tab.Library())
|
|
||||||
Constants.SHORTCUT_MANGA -> {
|
Constants.SHORTCUT_MANGA -> {
|
||||||
val idToOpen = intent.extras?.getLong(Constants.MANGA_EXTRA) ?: return false
|
val idToOpen = intent.extras?.getLong(Constants.MANGA_EXTRA) ?: return false
|
||||||
navigator.popUntilRoot()
|
navigator.popUntilRoot()
|
||||||
HomeScreen.openTab(HomeScreen.Tab.Library(idToOpen))
|
HomeScreen.Tab.Library(idToOpen)
|
||||||
}
|
}
|
||||||
Constants.SHORTCUT_UPDATES -> HomeScreen.openTab(HomeScreen.Tab.Updates)
|
Constants.SHORTCUT_UPDATES -> HomeScreen.Tab.Updates
|
||||||
Constants.SHORTCUT_HISTORY -> HomeScreen.openTab(HomeScreen.Tab.History)
|
Constants.SHORTCUT_HISTORY -> HomeScreen.Tab.History
|
||||||
Constants.SHORTCUT_SOURCES -> HomeScreen.openTab(HomeScreen.Tab.Browse(false))
|
Constants.SHORTCUT_SOURCES -> HomeScreen.Tab.Browse(false)
|
||||||
Constants.SHORTCUT_EXTENSIONS -> HomeScreen.openTab(HomeScreen.Tab.Browse(true))
|
Constants.SHORTCUT_EXTENSIONS -> HomeScreen.Tab.Browse(true)
|
||||||
Constants.SHORTCUT_DOWNLOADS -> {
|
Constants.SHORTCUT_DOWNLOADS -> {
|
||||||
navigator.popUntilRoot()
|
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" -> {
|
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
|
||||||
// If the intent match the "standard" Android search intent
|
// If the intent match the "standard" Android search intent
|
||||||
@ -429,6 +436,7 @@ class MainActivity : BaseActivity() {
|
|||||||
navigator.popUntilRoot()
|
navigator.popUntilRoot()
|
||||||
navigator.push(GlobalSearchScreen(query))
|
navigator.push(GlobalSearchScreen(query))
|
||||||
}
|
}
|
||||||
|
null
|
||||||
}
|
}
|
||||||
INTENT_SEARCH -> {
|
INTENT_SEARCH -> {
|
||||||
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
||||||
@ -437,15 +445,16 @@ class MainActivity : BaseActivity() {
|
|||||||
navigator.popUntilRoot()
|
navigator.popUntilRoot()
|
||||||
navigator.push(GlobalSearchScreen(query, filter))
|
navigator.push(GlobalSearchScreen(query, filter))
|
||||||
}
|
}
|
||||||
|
null
|
||||||
}
|
}
|
||||||
else -> {
|
else -> return false
|
||||||
isHandlingShortcut = false
|
}
|
||||||
return false
|
|
||||||
}
|
if (tabToOpen != null) {
|
||||||
|
lifecycleScope.launch { HomeScreen.openTab(tabToOpen) }
|
||||||
}
|
}
|
||||||
|
|
||||||
ready = true
|
ready = true
|
||||||
isHandlingShortcut = false
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +465,7 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (navigator.size == 1 &&
|
if (navigator?.size == 1 &&
|
||||||
!onBackPressedDispatcher.hasEnabledCallbacks() &&
|
!onBackPressedDispatcher.hasEnabledCallbacks() &&
|
||||||
libraryPreferences.autoClearChapterCache().get()
|
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 {
|
sealed class MangaScreenState {
|
||||||
@ -1055,10 +1063,3 @@ val chapterDecimalFormat = DecimalFormat(
|
|||||||
DecimalFormatSymbols()
|
DecimalFormatSymbols()
|
||||||
.apply { decimalSeparator = '.' },
|
.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.abs
|
||||||
import kotlin.math.max
|
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() {
|
class ReaderActivity : BaseActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -661,7 +657,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
* Called from the presenter when a manga is ready. Used to instantiate the appropriate viewer
|
* Called from the presenter when a manga is ready. Used to instantiate the appropriate viewer
|
||||||
* and the toolbar title.
|
* and the toolbar title.
|
||||||
*/
|
*/
|
||||||
fun setManga(manga: Manga) {
|
private fun setManga(manga: Manga) {
|
||||||
val prevViewer = viewer
|
val prevViewer = viewer
|
||||||
|
|
||||||
val viewerMode = ReadingModeType.fromPreference(viewModel.getMangaReadingMode(resolveDefault = false))
|
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
|
* 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.
|
* 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)
|
logcat(LogPriority.ERROR, error)
|
||||||
finish()
|
finish()
|
||||||
toast(error.message)
|
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.
|
* the viewer is reaching the beginning or end of a chapter or the transition page is active.
|
||||||
*/
|
*/
|
||||||
fun requestPreloadChapter(chapter: ReaderChapter) {
|
fun requestPreloadChapter(chapter: ReaderChapter) {
|
||||||
lifecycleScope.launch { viewModel.preloadChapter(chapter) }
|
lifecycleScope.launchIO { viewModel.preloadChapter(chapter) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -177,10 +177,11 @@ class ReaderViewModel(
|
|||||||
}.run {
|
}.run {
|
||||||
if (readerPreferences.skipDupe().get()) {
|
if (readerPreferences.skipDupe().get()) {
|
||||||
groupBy { it.chapterNumber }
|
groupBy { it.chapterNumber }
|
||||||
.mapValues { (_, chapters) ->
|
.map { (_, chapters) ->
|
||||||
chapters.find { it.id == chapterId || it.scanlator == selectedChapter.scanlator } ?: chapters.first()
|
chapters.find { it.id == selectedChapter.id }
|
||||||
|
?: chapters.find { it.scanlator == selectedChapter.scanlator }
|
||||||
|
?: chapters.first()
|
||||||
}
|
}
|
||||||
.values
|
|
||||||
} else {
|
} else {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
@ -200,17 +201,6 @@ class ReaderViewModel(
|
|||||||
|
|
||||||
private val incognitoMode = preferences.incognitoMode().get()
|
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 {
|
init {
|
||||||
// To save state
|
// To save state
|
||||||
state.map { it.viewerChapters?.currChapter }
|
state.map { it.viewerChapters?.currChapter }
|
||||||
@ -225,6 +215,17 @@ class ReaderViewModel(
|
|||||||
.launchIn(viewModelScope)
|
.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
|
* Called when the user pressed the back button and is going to leave the reader. Used to
|
||||||
* trigger deletion of the downloaded chapters.
|
* 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) {
|
private suspend fun loadAdjacent(chapter: ReaderChapter) {
|
||||||
val loader = loader ?: return
|
val loader = loader ?: return
|
||||||
|
saveCurrentChapterReadingProgress()
|
||||||
|
|
||||||
logcat { "Loading adjacent ${chapter.chapter.url}" }
|
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.
|
* that the user doesn't have to wait too long to continue reading.
|
||||||
*/
|
*/
|
||||||
private suspend fun preload(chapter: ReaderChapter) {
|
private suspend fun preload(chapter: ReaderChapter) {
|
||||||
|
if (chapter.state is ReaderChapter.State.Loaded || chapter.state == ReaderChapter.State.Loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (chapter.pageLoader is HttpPageLoader) {
|
if (chapter.pageLoader is HttpPageLoader) {
|
||||||
val manga = manga ?: return
|
val manga = manga ?: return
|
||||||
val dbChapter = chapter.chapter
|
val dbChapter = chapter.chapter
|
||||||
@ -383,20 +389,17 @@ class ReaderViewModel(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logcat { "Preloading ${chapter.chapter.url}" }
|
|
||||||
|
|
||||||
val loader = loader ?: return
|
val loader = loader ?: return
|
||||||
withIOContext {
|
try {
|
||||||
try {
|
logcat { "Preloading ${chapter.chapter.url}" }
|
||||||
loader.loadChapter(chapter)
|
loader.loadChapter(chapter)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
if (e is CancellationException) {
|
if (e is CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
}
|
|
||||||
return@withIOContext
|
|
||||||
}
|
}
|
||||||
eventChannel.trySend(Event.ReloadViewerChapters)
|
return
|
||||||
}
|
}
|
||||||
|
eventChannel.trySend(Event.ReloadViewerChapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -580,7 +583,12 @@ class ReaderViewModel(
|
|||||||
val sChapter = getCurrentChapter()?.chapter ?: return null
|
val sChapter = getCurrentChapter()?.chapter ?: return null
|
||||||
val source = getSource() ?: 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.toShareIntent
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||||
|
import logcat.LogPriority
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@ -47,7 +48,11 @@ class WebViewActivity : BaseActivity() {
|
|||||||
|
|
||||||
var headers = emptyMap<String, String>()
|
var headers = emptyMap<String, String>()
|
||||||
(sourceManager.get(intent.extras!!.getLong(SOURCE_KEY)) as? HttpSource)?.let { source ->
|
(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 {
|
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.openInBrowser
|
||||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import logcat.LogPriority
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -25,7 +26,11 @@ class WebViewScreenModel(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
sourceId?.let { sourceManager.get(it) as? HttpSource }?.let { source ->
|
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.api.tasks.TaskProvider
|
||||||
import org.gradle.kotlin.dsl.TaskContainerScope
|
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> {
|
fun TaskContainerScope.registerLocalesConfigTask(project: Project): TaskProvider<Task> {
|
||||||
return with(project) {
|
return with(project) {
|
||||||
register("generateLocalesConfig") {
|
register("generateLocalesConfig") {
|
||||||
val emptyResourcesElement = "<resources>\\s*</resources>|<resources/>".toRegex()
|
|
||||||
val valuesPrefix = "values-?".toRegex()
|
|
||||||
|
|
||||||
val languages = fileTree("$projectDir/src/main/res/")
|
val languages = fileTree("$projectDir/src/main/res/")
|
||||||
.matching {
|
.matching { include("**/strings.xml") }
|
||||||
include("**/strings.xml")
|
.filterNot { it.readText().contains(emptyResourcesElement) }
|
||||||
}
|
|
||||||
.filterNot {
|
|
||||||
it.readText().contains(emptyResourcesElement)
|
|
||||||
}
|
|
||||||
.map { it.parentFile.name }
|
.map { it.parentFile.name }
|
||||||
.sorted()
|
.sorted()
|
||||||
.joinToString(separator = "\n") {
|
.joinToString(separator = "\n") {
|
||||||
val language = it
|
val language = it
|
||||||
.replace(valuesPrefix, "")
|
.replace(valuesPrefix, "")
|
||||||
.replace("-r", "-")
|
.replace("-r", "-")
|
||||||
|
.replace("+", "-")
|
||||||
.takeIf(String::isNotBlank) ?: "en"
|
.takeIf(String::isNotBlank) ?: "en"
|
||||||
" <locale android:name=\"$language\"/>"
|
" <locale android:name=\"$language\"/>"
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.network
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
|
||||||
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -29,6 +30,7 @@ class NetworkHelper(context: Context) {
|
|||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
.callTimeout(2, TimeUnit.MINUTES)
|
.callTimeout(2, TimeUnit.MINUTES)
|
||||||
|
.addInterceptor(UncaughtExceptionInterceptor())
|
||||||
.addInterceptor(userAgentInterceptor)
|
.addInterceptor(userAgentInterceptor)
|
||||||
|
|
||||||
if (preferences.verboseLogging().get()) {
|
if (preferences.verboseLogging().get()) {
|
||||||
|
@ -17,6 +17,6 @@ class NetworkPreferences(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun defaultUserAgent(): Preference<String> {
|
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 {
|
object WebViewUtil {
|
||||||
const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
|
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 {
|
fun supportsWebView(context: Context): Boolean {
|
||||||
try {
|
try {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package tachiyomi.core
|
package tachiyomi.core
|
||||||
|
|
||||||
object Constants {
|
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"
|
const val MANGA_EXTRA = "manga"
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ sealed class LibraryDisplayMode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val values = setOf(CompactGrid, ComfortableGrid, List, CoverOnlyGrid)
|
val values by lazy { setOf(CompactGrid, ComfortableGrid, List, CoverOnlyGrid) }
|
||||||
val default = CompactGrid
|
val default = CompactGrid
|
||||||
|
|
||||||
fun valueOf(flag: Long?): LibraryDisplayMode {
|
fun valueOf(flag: Long?): LibraryDisplayMode {
|
||||||
|
@ -65,8 +65,8 @@ data class LibrarySort(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val types = setOf(Type.Alphabetical, Type.LastRead, Type.LastUpdate, Type.UnreadCount, Type.TotalChapters, Type.LatestChapter, Type.ChapterFetchDate, Type.DateAdded)
|
val types by lazy { 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 directions by lazy { setOf(Direction.Ascending, Direction.Descending) }
|
||||||
val default = LibrarySort(Type.Alphabetical, Direction.Ascending)
|
val default = LibrarySort(Type.Alphabetical, Direction.Ascending)
|
||||||
|
|
||||||
fun valueOf(flag: Long): LibrarySort {
|
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-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil_version" }
|
||||||
coil-compose = { module = "io.coil-kt:coil-compose", 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"
|
image-decoder = "com.github.tachiyomiorg:image-decoder:7879b45"
|
||||||
|
|
||||||
natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
|
natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
|
||||||
|
Reference in New Issue
Block a user