Compare commits
No commits in common. "master" and "preview-172" have entirely different histories.
master
...
preview-17
@ -7,7 +7,7 @@ indent_style = space
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.{xml,sq,sqm}]
|
[*.xml]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
# noinspection EditorConfigKeyCorrectness
|
# noinspection EditorConfigKeyCorrectness
|
||||||
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: ❌ Help with Extensions
|
|
||||||
url: https://mihon.app/docs/faq/browse/extensions
|
|
||||||
about: For extension-related questions/issues
|
|
||||||
- name: 🖥️ Mihon website
|
- name: 🖥️ Mihon website
|
||||||
url: https://mihon.app/
|
url: https://mihon.app/
|
||||||
about: Guides, troubleshooting, and answers to common questions
|
about: Guides, troubleshooting, and answers to common questions
|
||||||
|
6
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
6
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
label: TachiyomiSY version
|
label: TachiyomiSY version
|
||||||
description: You can find your TachiyomiSY version in **More → About**.
|
description: You can find your TachiyomiSY version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "1.12.0"
|
Example: "1.11.0"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -96,9 +96,9 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[1.12.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
- label: I have updated the app to version **[1.11.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
||||||
required: true
|
required: true
|
||||||
- label: I understand that **Mihon does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions.
|
- label: I understand that extensions are unaffiliated to Mihon, and will not receive any help for any source and/or extension-related issues.
|
||||||
required: true
|
required: true
|
||||||
|
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -31,7 +31,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[1.12.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
- label: I have updated the app to version **[1.11.0](https://github.com/jobobby04/tachiyomisy/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
|
||||||
|
2
.github/workflows/build_push.yml
vendored
2
.github/workflows/build_push.yml
vendored
@ -63,8 +63,6 @@ jobs:
|
|||||||
alias: ${{ secrets.ALIAS }}
|
alias: ${{ secrets.ALIAS }}
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
env:
|
|
||||||
BUILD_TOOLS_VERSION: '35.0.1'
|
|
||||||
|
|
||||||
- name: Clean up build artifacts
|
- name: Clean up build artifacts
|
||||||
run: |
|
run: |
|
||||||
|
13
.github/workflows/build_push_preview.yml
vendored
13
.github/workflows/build_push_preview.yml
vendored
@ -14,14 +14,8 @@ jobs:
|
|||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Validate Gradle Wrapper
|
||||||
uses: actions/setup-java@v4
|
uses: gradle/actions/wrapper-validation@v4
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: temurin
|
|
||||||
|
|
||||||
- name: Set up gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
|
|
||||||
- name: Create Tag
|
- name: Create Tag
|
||||||
run: |
|
run: |
|
||||||
@ -34,6 +28,3 @@ jobs:
|
|||||||
-H 'Accept: application/vnd.github.everest-preview+json' \
|
-H 'Accept: application/vnd.github.everest-preview+json' \
|
||||||
-u ${{ secrets.ACCESS_TOKEN }} \
|
-u ${{ secrets.ACCESS_TOKEN }} \
|
||||||
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
||||||
|
|
||||||
- name: Run unit tests
|
|
||||||
run: ./gradlew testDebugUnitTest testDevDebugUnitTest
|
|
||||||
|
2
.github/workflows/issue_moderator.yml
vendored
2
.github/workflows/issue_moderator.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Moderate issues
|
- name: Moderate issues
|
||||||
uses: tachiyomiorg/issue-moderator-action@v2.6.1
|
uses: tachiyomiorg/issue-moderator-action@v2.6.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
duplicate-label: Duplicate
|
duplicate-label: Duplicate
|
||||||
|
19
.github/workflows/lock.yml
vendored
Normal file
19
.github/workflows/lock.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Lock threads
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Daily
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
# Manual trigger
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
issue-inactive-days: '2'
|
||||||
|
pr-inactive-days: '2'
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,6 +19,3 @@ local.properties
|
|||||||
google-services.json
|
google-services.json
|
||||||
/app/src/main/assets/client_secrets.json
|
/app/src/main/assets/client_secrets.json
|
||||||
*.apk
|
*.apk
|
||||||
|
|
||||||
# Custom ignores
|
|
||||||
/keys
|
|
@ -31,12 +31,12 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi.sy"
|
applicationId = "eu.kanade.tachiyomi.sy"
|
||||||
|
|
||||||
versionCode = 75
|
versionCode = 72
|
||||||
versionName = "1.12.0"
|
versionName = "1.11.0"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
|
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
@ -71,8 +71,6 @@ android {
|
|||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
||||||
|
|
||||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = true)}\"")
|
|
||||||
}
|
}
|
||||||
create("benchmark") {
|
create("benchmark") {
|
||||||
initWith(getByName("release"))
|
initWith(getByName("release"))
|
||||||
@ -239,7 +237,7 @@ dependencies {
|
|||||||
implementation(libs.preferencektx)
|
implementation(libs.preferencektx)
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
implementation(libs.injekt)
|
implementation(libs.injekt.core)
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
implementation(platform(libs.coil.bom))
|
implementation(platform(libs.coil.bom))
|
||||||
@ -257,7 +255,7 @@ dependencies {
|
|||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
}
|
}
|
||||||
implementation(libs.insetter)
|
implementation(libs.insetter)
|
||||||
implementation(libs.richeditor.compose)
|
implementation(libs.bundles.richtext)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
implementation(libs.compose.materialmotion)
|
implementation(libs.compose.materialmotion)
|
||||||
@ -265,7 +263,6 @@ dependencies {
|
|||||||
implementation(libs.compose.webview)
|
implementation(libs.compose.webview)
|
||||||
implementation(libs.compose.grid)
|
implementation(libs.compose.grid)
|
||||||
implementation(libs.reorderable)
|
implementation(libs.reorderable)
|
||||||
implementation(libs.bundles.markdown)
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
@ -310,12 +307,17 @@ dependencies {
|
|||||||
// Koin
|
// Koin
|
||||||
implementation(sylibs.koin.core)
|
implementation(sylibs.koin.core)
|
||||||
implementation(sylibs.koin.android)
|
implementation(sylibs.koin.android)
|
||||||
|
|
||||||
// ZXing Android Embedded
|
|
||||||
implementation(sylibs.zxing.android.embedded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
androidComponents {
|
androidComponents {
|
||||||
|
beforeVariants { variantBuilder ->
|
||||||
|
// Disables standardBenchmark
|
||||||
|
if (variantBuilder.buildType == "benchmark") {
|
||||||
|
variantBuilder.enable = variantBuilder.productFlavors.containsAll(
|
||||||
|
listOf("default" to "dev"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
onVariants(selector().withFlavor("default" to "standard")) {
|
onVariants(selector().withFlavor("default" to "standard")) {
|
||||||
// Only excluding in standard flavor because this breaks
|
// Only excluding in standard flavor because this breaks
|
||||||
// Layout Inspector's Compose tree
|
// Layout Inspector's Compose tree
|
||||||
|
@ -359,7 +359,7 @@
|
|||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:scheme="http" />
|
<data android:scheme="http" />
|
||||||
|
|
||||||
<data android:host="pururin.me" />
|
<data android:host="pururin.io" />
|
||||||
|
|
||||||
<data android:pathPattern="/gallery/..*" />
|
<data android:pathPattern="/gallery/..*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@ -413,10 +413,6 @@
|
|||||||
android:scheme="tachiyomisy" />
|
android:scheme="tachiyomisy" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
|
||||||
tools:remove="screenOrientation" />
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="rikka.shizuku.api"
|
<uses-sdk tools:overrideLibrary="rikka.shizuku.api"
|
||||||
|
@ -82,7 +82,6 @@ import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
|||||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
||||||
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||||
import tachiyomi.domain.release.service.ReleaseService
|
import tachiyomi.domain.release.service.ReleaseService
|
||||||
@ -129,7 +128,6 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { SetMangaViewerFlags(get()) }
|
addFactory { SetMangaViewerFlags(get()) }
|
||||||
addFactory { NetworkToLocalManga(get()) }
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
addFactory { UpdateManga(get(), get()) }
|
addFactory { UpdateManga(get(), get()) }
|
||||||
addFactory { UpdateMangaNotes(get()) }
|
|
||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
addFactory { GetExcludedScanlators(get()) }
|
addFactory { GetExcludedScanlators(get()) }
|
||||||
addFactory { SetExcludedScanlators(get()) }
|
addFactory { SetExcludedScanlators(get()) }
|
||||||
|
@ -28,6 +28,7 @@ import tachiyomi.data.source.SavedSearchRepositoryImpl
|
|||||||
import tachiyomi.domain.chapter.interactor.DeleteChapters
|
import tachiyomi.domain.chapter.interactor.DeleteChapters
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByUrl
|
import tachiyomi.domain.chapter.interactor.GetChapterByUrl
|
||||||
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistoryByMangaId
|
||||||
import tachiyomi.domain.manga.interactor.DeleteByMergeId
|
import tachiyomi.domain.manga.interactor.DeleteByMergeId
|
||||||
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
|
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
|
||||||
import tachiyomi.domain.manga.interactor.DeleteMangaById
|
import tachiyomi.domain.manga.interactor.DeleteMangaById
|
||||||
@ -87,6 +88,7 @@ class SYDomainModule : InjektModule {
|
|||||||
addFactory { DeleteChapters(get()) }
|
addFactory { DeleteChapters(get()) }
|
||||||
addFactory { DeleteMangaById(get()) }
|
addFactory { DeleteMangaById(get()) }
|
||||||
addFactory { FilterSerializer() }
|
addFactory { FilterSerializer() }
|
||||||
|
addFactory { GetHistoryByMangaId(get()) }
|
||||||
addFactory { GetChapterByUrl(get()) }
|
addFactory { GetChapterByUrl(get()) }
|
||||||
addFactory { GetSourceCategories(get()) }
|
addFactory { GetSourceCategories(get()) }
|
||||||
addFactory { CreateSourceCategory(get()) }
|
addFactory { CreateSourceCategory(get()) }
|
||||||
|
@ -173,8 +173,7 @@ class SyncChaptersWithSource(
|
|||||||
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
||||||
.associate { it.chapterNumber to it.dateFetch }
|
.associate { it.chapterNumber to it.dateFetch }
|
||||||
|
|
||||||
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
|
val markDuplicateAsRead = libraryPreferences.markDuplicateChapterRead().get()
|
||||||
.contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_NEW)
|
|
||||||
|
|
||||||
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
|
@ -23,7 +23,7 @@ class GetPagePreviews(
|
|||||||
return try {
|
return try {
|
||||||
val pagePreviews = try {
|
val pagePreviews = try {
|
||||||
pagePreviewCache.getPageListFromCache(manga, chapterIds, page)
|
pagePreviewCache.getPageListFromCache(manga, chapterIds, page)
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also {
|
source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also {
|
||||||
pagePreviewCache.putPageListToCache(manga, chapterIds, it)
|
pagePreviewCache.putPageListToCache(manga, chapterIds, it)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import eu.kanade.domain.manga.model.hasCustomCover
|
|||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
|
||||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
@ -33,8 +32,9 @@ class UpdateManga(
|
|||||||
remoteManga: SManga,
|
remoteManga: SManga,
|
||||||
manualFetch: Boolean,
|
manualFetch: Boolean,
|
||||||
coverCache: CoverCache = Injekt.get(),
|
coverCache: CoverCache = Injekt.get(),
|
||||||
libraryPreferences: LibraryPreferences = Injekt.get(),
|
// SY -->
|
||||||
downloadManager: DownloadManager = Injekt.get(),
|
downloadManager: DownloadManager = Injekt.get(),
|
||||||
|
// SY <--
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val remoteTitle = try {
|
val remoteTitle = try {
|
||||||
remoteManga.title
|
remoteManga.title
|
||||||
@ -42,13 +42,14 @@ class UpdateManga(
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the manga isn't a favorite (or 'update titles' preference is enabled), set its title from source and update in db
|
// SY -->
|
||||||
val title =
|
val title = if (remoteTitle.isNotBlank() && localManga.ogTitle != remoteTitle) {
|
||||||
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles().get())) {
|
downloadManager.renameMangaDir(localManga.ogTitle, remoteTitle, localManga.source)
|
||||||
remoteTitle
|
remoteTitle
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
val coverLastModified =
|
val coverLastModified =
|
||||||
when {
|
when {
|
||||||
@ -68,7 +69,7 @@ class UpdateManga(
|
|||||||
|
|
||||||
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
val success = mangaRepository.update(
|
return mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = localManga.id,
|
id = localManga.id,
|
||||||
title = title,
|
title = title,
|
||||||
@ -83,10 +84,6 @@ class UpdateManga(
|
|||||||
initialized = true,
|
initialized = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (success && title != null) {
|
|
||||||
downloadManager.renameManga(localManga, title)
|
|
||||||
}
|
|
||||||
return success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateFetchInterval(
|
suspend fun awaitUpdateFetchInterval(
|
||||||
|
@ -38,14 +38,12 @@ fun Manga.chaptersFiltered(): Boolean {
|
|||||||
|
|
||||||
fun Manga.toSManga(): SManga = SManga.create().also {
|
fun Manga.toSManga(): SManga = SManga.create().also {
|
||||||
it.url = url
|
it.url = url
|
||||||
// SY -->
|
it.title = title
|
||||||
it.title = ogTitle
|
it.artist = artist
|
||||||
it.artist = ogArtist
|
it.author = author
|
||||||
it.author = ogAuthor
|
it.description = description
|
||||||
it.description = ogDescription
|
it.genre = genre.orEmpty().joinToString()
|
||||||
it.genre = ogGenre.orEmpty().joinToString()
|
it.status = status.toInt()
|
||||||
it.status = ogStatus.toInt()
|
|
||||||
// SY <--
|
|
||||||
it.thumbnail_url = thumbnailUrl
|
it.thumbnail_url = thumbnailUrl
|
||||||
it.initialized = initialized
|
it.initialized = initialized
|
||||||
}
|
}
|
||||||
@ -78,6 +76,24 @@ fun Manga.copyFrom(other: SManga): Manga {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun SManga.toDomainManga(sourceId: Long): Manga {
|
||||||
|
return Manga.create().copy(
|
||||||
|
url = url,
|
||||||
|
// SY -->
|
||||||
|
ogTitle = title,
|
||||||
|
ogArtist = artist,
|
||||||
|
ogAuthor = author,
|
||||||
|
ogThumbnailUrl = thumbnail_url,
|
||||||
|
ogDescription = description,
|
||||||
|
ogGenre = getGenres(),
|
||||||
|
ogStatus = status.toLong(),
|
||||||
|
// SY <--
|
||||||
|
updateStrategy = update_strategy,
|
||||||
|
initialized = initialized,
|
||||||
|
source = sourceId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
||||||
return coverCache.getCustomCoverFile(id).exists()
|
return coverCache.getCustomCoverFile(id).exists()
|
||||||
}
|
}
|
||||||
|
@ -88,32 +88,5 @@ class SourcePreferences(
|
|||||||
BANDWIDTH_HERO,
|
BANDWIDTH_HERO,
|
||||||
WSRV_NL,
|
WSRV_NL,
|
||||||
}
|
}
|
||||||
|
|
||||||
fun migrateFlags() = preferenceStore.getInt("migrate_flags", Int.MAX_VALUE)
|
|
||||||
|
|
||||||
fun defaultMangaOrder() = preferenceStore.getString("default_manga_order", "")
|
|
||||||
|
|
||||||
fun migrationSources() = preferenceStore.getString("migrate_sources", "")
|
|
||||||
|
|
||||||
fun smartMigration() = preferenceStore.getBoolean("smart_migrate", false)
|
|
||||||
|
|
||||||
fun useSourceWithMost() = preferenceStore.getBoolean("use_source_with_most", false)
|
|
||||||
|
|
||||||
fun skipPreMigration() = preferenceStore.getBoolean(Preference.appStateKey("skip_pre_migration"), false)
|
|
||||||
|
|
||||||
fun hideNotFoundMigration() = preferenceStore.getBoolean("hide_not_found_migration", false)
|
|
||||||
|
|
||||||
fun showOnlyUpdatesMigration() = preferenceStore.getBoolean("show_only_updates_migration", false)
|
|
||||||
|
|
||||||
fun allowLocalSourceHiddenFolders() = preferenceStore.getBoolean("allow_local_source_hidden_folders", false)
|
|
||||||
|
|
||||||
fun preferredMangaDexId() = preferenceStore.getString("preferred_mangaDex_id", "0")
|
|
||||||
|
|
||||||
fun mangadexSyncToLibraryIndexes() = preferenceStore.getStringSet(
|
|
||||||
"pref_mangadex_sync_to_library_indexes",
|
|
||||||
emptySet(),
|
|
||||||
)
|
|
||||||
|
|
||||||
fun recommendationSearchFlags() = preferenceStore.getInt("rec_search_flags", Int.MAX_VALUE)
|
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.domain.ui.model
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
enum class AppTheme(val titleRes: StringResource?) {
|
enum class AppTheme(val titleRes: StringResource?) {
|
||||||
@ -9,7 +11,9 @@ enum class AppTheme(val titleRes: StringResource?) {
|
|||||||
GREEN_APPLE(MR.strings.theme_greenapple),
|
GREEN_APPLE(MR.strings.theme_greenapple),
|
||||||
LAVENDER(MR.strings.theme_lavender),
|
LAVENDER(MR.strings.theme_lavender),
|
||||||
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
||||||
NORD(MR.strings.theme_nord),
|
|
||||||
|
// TODO: re-enable for preview
|
||||||
|
NORD(MR.strings.theme_nord.takeIf { isDevFlavor || isPreviewBuildType }),
|
||||||
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
||||||
TAKO(MR.strings.theme_tako),
|
TAKO(MR.strings.theme_tako),
|
||||||
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
||||||
|
@ -82,18 +82,10 @@ fun BrowseSourceContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
LoadingScreen(Modifier.padding(contentPadding))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mangaList.itemCount == 0) {
|
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
message = when (errorState) {
|
message = getErrorMessage(errorState),
|
||||||
is LoadState.Error -> getErrorMessage(errorState)
|
|
||||||
else -> stringResource(MR.strings.no_results_found)
|
|
||||||
},
|
|
||||||
actions = if (source is LocalSource /* SY --> */ && onLocalSourceHelpClick != null /* SY <-- */) {
|
actions = if (source is LocalSource /* SY --> */ && onLocalSourceHelpClick != null /* SY <-- */) {
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
@ -112,7 +104,7 @@ fun BrowseSourceContent(
|
|||||||
// SY -->
|
// SY -->
|
||||||
if (onWebViewClick != null) {
|
if (onWebViewClick != null) {
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringRes = MR.strings.action_open_in_web_view,
|
MR.strings.action_open_in_web_view,
|
||||||
icon = Icons.Outlined.Public,
|
icon = Icons.Outlined.Public,
|
||||||
onClick = onWebViewClick,
|
onClick = onWebViewClick,
|
||||||
)
|
)
|
||||||
@ -121,7 +113,7 @@ fun BrowseSourceContent(
|
|||||||
},
|
},
|
||||||
if (onHelpClick != null) {
|
if (onHelpClick != null) {
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringRes = MR.strings.label_help,
|
MR.strings.label_help,
|
||||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
)
|
)
|
||||||
@ -136,6 +128,13 @@ fun BrowseSourceContent(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
||||||
|
LoadingScreen(
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
if (source?.isEhBasedSource() == true && ehentaiBrowseDisplayMode) {
|
if (source?.isEhBasedSource() == true && ehentaiBrowseDisplayMode) {
|
||||||
BrowseSourceEHentaiList(
|
BrowseSourceEHentaiList(
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.compose.animation.SizeTransform
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
@ -27,14 +28,20 @@ fun NavigatorAdaptiveSheet(
|
|||||||
screen = screen,
|
screen = screen,
|
||||||
content = { sheetNavigator ->
|
content = { sheetNavigator ->
|
||||||
AdaptiveSheet(
|
AdaptiveSheet(
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
) {
|
) {
|
||||||
ScreenTransition(
|
ScreenTransition(
|
||||||
navigator = sheetNavigator,
|
navigator = sheetNavigator,
|
||||||
enterTransition = { fadeIn(animationSpec = tween(220, delayMillis = 90)) },
|
transition = {
|
||||||
exitTransition = { fadeOut(animationSpec = tween(90)) },
|
fadeIn(animationSpec = tween(220, delayMillis = 90)) togetherWith
|
||||||
sizeTransform = { SizeTransform() },
|
fadeOut(animationSpec = tween(90))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
BackHandler(
|
||||||
|
enabled = sheetNavigator.size > 1,
|
||||||
|
onBack = sheetNavigator::pop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,10 +79,10 @@ fun AdaptiveSheet(
|
|||||||
properties = dialogProperties,
|
properties = dialogProperties,
|
||||||
) {
|
) {
|
||||||
AdaptiveSheetImpl(
|
AdaptiveSheetImpl(
|
||||||
|
modifier = modifier,
|
||||||
isTabletUi = isTabletUi,
|
isTabletUi = isTabletUi,
|
||||||
enableSwipeDismiss = enableSwipeDismiss,
|
enableSwipeDismiss = enableSwipeDismiss,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
modifier = modifier,
|
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusDirection
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@ -202,7 +201,6 @@ fun AppBarActions(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
focusable = false,
|
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = it.onClick,
|
onClick = it.onClick,
|
||||||
@ -227,7 +225,6 @@ fun AppBarActions(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
focusable = false,
|
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { showMenu = !showMenu },
|
onClick = { showMenu = !showMenu },
|
||||||
@ -292,7 +289,6 @@ fun SearchToolbar(
|
|||||||
onSearch(searchQuery)
|
onSearch(searchQuery)
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
focusManager.moveFocus(FocusDirection.Next)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
@ -356,7 +352,6 @@ fun SearchToolbar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
focusable = false,
|
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
@ -376,7 +371,6 @@ fun SearchToolbar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
focusable = false,
|
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
@ -310,9 +310,9 @@ private fun ColumnScope.DisplayPage(
|
|||||||
|
|
||||||
val columns by columnPreference.collectAsState()
|
val columns by columnPreference.collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = columns,
|
|
||||||
valueRange = 0..10,
|
|
||||||
label = stringResource(MR.strings.pref_library_columns),
|
label = stringResource(MR.strings.pref_library_columns),
|
||||||
|
max = 10,
|
||||||
|
value = columns,
|
||||||
valueText = if (columns > 0) {
|
valueText = if (columns > 0) {
|
||||||
columns.toString()
|
columns.toString()
|
||||||
} else {
|
} else {
|
||||||
@ -328,10 +328,6 @@ private fun ColumnScope.DisplayPage(
|
|||||||
label = stringResource(MR.strings.action_display_download_badge),
|
label = stringResource(MR.strings.action_display_download_badge),
|
||||||
pref = screenModel.libraryPreferences.downloadBadge(),
|
pref = screenModel.libraryPreferences.downloadBadge(),
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
|
||||||
label = stringResource(MR.strings.action_display_unread_badge),
|
|
||||||
pref = screenModel.libraryPreferences.unreadBadge(),
|
|
||||||
)
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_local_badge),
|
label = stringResource(MR.strings.action_display_local_badge),
|
||||||
pref = screenModel.libraryPreferences.localBadge(),
|
pref = screenModel.libraryPreferences.localBadge(),
|
||||||
|
@ -1,95 +1,44 @@
|
|||||||
package eu.kanade.presentation.manga
|
package eu.kanade.presentation.manga
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
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
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Brush
|
|
||||||
import androidx.compose.material.icons.filled.PersonOutline
|
|
||||||
import androidx.compose.material.icons.filled.Warning
|
|
||||||
import androidx.compose.material.icons.outlined.Add
|
import androidx.compose.material.icons.outlined.Add
|
||||||
import androidx.compose.material.icons.outlined.AttachMoney
|
import androidx.compose.material.icons.outlined.Book
|
||||||
import androidx.compose.material.icons.outlined.Block
|
import androidx.compose.material.icons.outlined.SwapVert
|
||||||
import androidx.compose.material.icons.outlined.Close
|
|
||||||
import androidx.compose.material.icons.outlined.Done
|
|
||||||
import androidx.compose.material.icons.outlined.DoneAll
|
|
||||||
import androidx.compose.material.icons.outlined.Pause
|
|
||||||
import androidx.compose.material.icons.outlined.Schedule
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.Typography
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.text.TextMeasurer
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.Constraints
|
|
||||||
import androidx.compose.ui.unit.Density
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastMaxOfOrNull
|
import androidx.compose.ui.unit.sp
|
||||||
import coil3.request.ImageRequest
|
|
||||||
import coil3.request.crossfade
|
|
||||||
import eu.kanade.presentation.components.AdaptiveSheet
|
import eu.kanade.presentation.components.AdaptiveSheet
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
|
||||||
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
|
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.domain.manga.model.MangaWithChapterCount
|
|
||||||
import tachiyomi.domain.source.model.StubSource
|
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.Badge
|
|
||||||
import tachiyomi.presentation.core.components.BadgeGroup
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DuplicateMangaDialog(
|
fun DuplicateMangaDialog(
|
||||||
duplicates: List<MangaWithChapterCount>,
|
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onConfirm: () -> Unit,
|
onConfirm: () -> Unit,
|
||||||
onOpenManga: (manga: Manga) -> Unit,
|
onOpenManga: () -> Unit,
|
||||||
onMigrate: (manga: Manga) -> Unit,
|
onMigrate: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val sourceManager = remember { Injekt.get<SourceManager>() }
|
|
||||||
val minHeight = LocalPreferenceMinHeight.current
|
val minHeight = LocalPreferenceMinHeight.current
|
||||||
val horizontalPadding = PaddingValues(horizontal = TabbedDialogPaddings.Horizontal)
|
|
||||||
val horizontalPaddingModifier = Modifier.padding(horizontalPadding)
|
|
||||||
|
|
||||||
AdaptiveSheet(
|
AdaptiveSheet(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -97,310 +46,81 @@ fun DuplicateMangaDialog(
|
|||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = TabbedDialogPaddings.Vertical)
|
.padding(
|
||||||
.verticalScroll(rememberScrollState())
|
vertical = TabbedDialogPaddings.Vertical,
|
||||||
|
horizontal = TabbedDialogPaddings.Horizontal,
|
||||||
|
)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(MR.strings.possible_duplicates_title),
|
modifier = Modifier.padding(TitlePadding),
|
||||||
|
text = stringResource(MR.strings.are_you_sure),
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
modifier = Modifier
|
|
||||||
.then(horizontalPaddingModifier)
|
|
||||||
.padding(top = MaterialTheme.padding.small),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(MR.strings.possible_duplicates_summary),
|
text = stringResource(MR.strings.confirm_add_duplicate_manga),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
modifier = Modifier.then(horizontalPaddingModifier),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
LazyRow(
|
Spacer(Modifier.height(PaddingSize))
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
|
||||||
modifier = Modifier.height(getMaximumMangaCardHeight(duplicates)),
|
|
||||||
contentPadding = horizontalPadding,
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = duplicates,
|
|
||||||
key = { it.manga.id },
|
|
||||||
) {
|
|
||||||
DuplicateMangaListItem(
|
|
||||||
duplicate = it,
|
|
||||||
getSource = { sourceManager.getOrStub(it.manga.source) },
|
|
||||||
onMigrate = { onMigrate(it.manga) },
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onOpenManga = { onOpenManga(it.manga) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = horizontalPaddingModifier) {
|
TextPreferenceWidget(
|
||||||
HorizontalDivider()
|
title = stringResource(MR.strings.action_show_manga),
|
||||||
|
icon = Icons.Outlined.Book,
|
||||||
|
onPreferenceClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
onOpenManga()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
TextPreferenceWidget(
|
HorizontalDivider()
|
||||||
title = stringResource(MR.strings.action_add_anyway),
|
|
||||||
icon = Icons.Outlined.Add,
|
|
||||||
onPreferenceClick = {
|
|
||||||
onDismissRequest()
|
|
||||||
onConfirm()
|
|
||||||
},
|
|
||||||
modifier = Modifier.clip(CircleShape),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedButton(
|
TextPreferenceWidget(
|
||||||
onClick = onDismissRequest,
|
title = stringResource(MR.strings.action_migrate_duplicate),
|
||||||
modifier = Modifier
|
icon = Icons.Outlined.SwapVert,
|
||||||
.then(horizontalPaddingModifier)
|
onPreferenceClick = {
|
||||||
.padding(bottom = MaterialTheme.padding.medium)
|
|
||||||
.heightIn(min = minHeight)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(vertical = MaterialTheme.padding.extraSmall),
|
|
||||||
text = stringResource(MR.strings.action_cancel),
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun DuplicateMangaListItem(
|
|
||||||
duplicate: MangaWithChapterCount,
|
|
||||||
getSource: () -> Source,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onOpenManga: () -> Unit,
|
|
||||||
onMigrate: () -> Unit,
|
|
||||||
) {
|
|
||||||
val source = getSource()
|
|
||||||
val manga = duplicate.manga
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(MangaCardWidth)
|
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
|
||||||
.combinedClickable(
|
|
||||||
onLongClick = { onOpenManga() },
|
|
||||||
onClick = {
|
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
onMigrate()
|
onMigrate()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.padding(MaterialTheme.padding.small),
|
|
||||||
) {
|
HorizontalDivider()
|
||||||
Box {
|
|
||||||
MangaCover.Book(
|
TextPreferenceWidget(
|
||||||
data = ImageRequest.Builder(LocalContext.current)
|
title = stringResource(MR.strings.action_add_anyway),
|
||||||
.data(manga)
|
icon = Icons.Outlined.Add,
|
||||||
.crossfade(true)
|
onPreferenceClick = {
|
||||||
.build(),
|
onDismissRequest()
|
||||||
modifier = Modifier.fillMaxWidth(),
|
onConfirm()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
BadgeGroup(
|
|
||||||
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(4.dp)
|
.sizeIn(minHeight = minHeight)
|
||||||
.align(Alignment.TopStart),
|
.clickable { onDismissRequest.invoke() }
|
||||||
|
.padding(ButtonPadding)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
Badge(
|
OutlinedButton(onClick = onDismissRequest, modifier = Modifier.fillMaxWidth()) {
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
Text(
|
||||||
textColor = MaterialTheme.colorScheme.onSecondary,
|
modifier = Modifier
|
||||||
text = pluralStringResource(
|
.padding(vertical = 8.dp),
|
||||||
MR.plurals.manga_num_chapters,
|
text = stringResource(MR.strings.action_cancel),
|
||||||
duplicate.chapterCount.toInt(),
|
color = MaterialTheme.colorScheme.primary,
|
||||||
duplicate.chapterCount,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
),
|
fontSize = 16.sp,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(MaterialTheme.padding.extraSmall))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = manga.title,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 2,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!manga.author.isNullOrBlank()) {
|
|
||||||
MangaDetailRow(
|
|
||||||
text = manga.author!!,
|
|
||||||
iconImageVector = Icons.Filled.PersonOutline,
|
|
||||||
maxLines = 2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!manga.artist.isNullOrBlank() && manga.author != manga.artist) {
|
|
||||||
MangaDetailRow(
|
|
||||||
text = manga.artist!!,
|
|
||||||
iconImageVector = Icons.Filled.Brush,
|
|
||||||
maxLines = 2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
MangaDetailRow(
|
|
||||||
text = when (manga.status) {
|
|
||||||
SManga.ONGOING.toLong() -> stringResource(MR.strings.ongoing)
|
|
||||||
SManga.COMPLETED.toLong() -> stringResource(MR.strings.completed)
|
|
||||||
SManga.LICENSED.toLong() -> stringResource(MR.strings.licensed)
|
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(MR.strings.publishing_finished)
|
|
||||||
SManga.CANCELLED.toLong() -> stringResource(MR.strings.cancelled)
|
|
||||||
SManga.ON_HIATUS.toLong() -> stringResource(MR.strings.on_hiatus)
|
|
||||||
else -> stringResource(MR.strings.unknown)
|
|
||||||
},
|
|
||||||
iconImageVector = when (manga.status) {
|
|
||||||
SManga.ONGOING.toLong() -> Icons.Outlined.Schedule
|
|
||||||
SManga.COMPLETED.toLong() -> Icons.Outlined.DoneAll
|
|
||||||
SManga.LICENSED.toLong() -> Icons.Outlined.AttachMoney
|
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done
|
|
||||||
SManga.CANCELLED.toLong() -> Icons.Outlined.Close
|
|
||||||
SManga.ON_HIATUS.toLong() -> Icons.Outlined.Pause
|
|
||||||
else -> Icons.Outlined.Block
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
if (source is StubSource) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Warning,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = source.name,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
private val PaddingSize = 16.dp
|
||||||
private fun MangaDetailRow(
|
|
||||||
text: String,
|
|
||||||
iconImageVector: ImageVector,
|
|
||||||
maxLines: Int = 1,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = MaterialTheme.padding.extraSmall),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = iconImageVector,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(MangaDetailsIconWidth),
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = maxLines,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
private val ButtonPadding = PaddingValues(top = 16.dp, bottom = 16.dp)
|
||||||
private fun getMaximumMangaCardHeight(duplicates: List<MangaWithChapterCount>): Dp {
|
private val TitlePadding = PaddingValues(bottom = 16.dp, top = 8.dp)
|
||||||
val density = LocalDensity.current
|
|
||||||
val typography = MaterialTheme.typography
|
|
||||||
val textMeasurer = rememberTextMeasurer()
|
|
||||||
|
|
||||||
val smallPadding = with(density) { MaterialTheme.padding.small.roundToPx() }
|
|
||||||
val extraSmallPadding = with(density) { MaterialTheme.padding.extraSmall.roundToPx() }
|
|
||||||
|
|
||||||
val width = with(density) { MangaCardWidth.roundToPx() - (2 * smallPadding) }
|
|
||||||
val iconWidth = with(density) { MangaDetailsIconWidth.roundToPx() }
|
|
||||||
|
|
||||||
val coverHeight = width / MangaCover.Book.ratio
|
|
||||||
val constraints = Constraints(maxWidth = width)
|
|
||||||
val detailsConstraints = Constraints(maxWidth = width - iconWidth - extraSmallPadding)
|
|
||||||
|
|
||||||
return remember(
|
|
||||||
duplicates,
|
|
||||||
density,
|
|
||||||
typography,
|
|
||||||
textMeasurer,
|
|
||||||
smallPadding,
|
|
||||||
extraSmallPadding,
|
|
||||||
coverHeight,
|
|
||||||
constraints,
|
|
||||||
detailsConstraints,
|
|
||||||
) {
|
|
||||||
duplicates.fastMaxOfOrNull {
|
|
||||||
calculateMangaCardHeight(
|
|
||||||
manga = it.manga,
|
|
||||||
density = density,
|
|
||||||
typography = typography,
|
|
||||||
textMeasurer = textMeasurer,
|
|
||||||
smallPadding = smallPadding,
|
|
||||||
extraSmallPadding = extraSmallPadding,
|
|
||||||
coverHeight = coverHeight,
|
|
||||||
constraints = constraints,
|
|
||||||
detailsConstraints = detailsConstraints,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
?: 0.dp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateMangaCardHeight(
|
|
||||||
manga: Manga,
|
|
||||||
density: Density,
|
|
||||||
typography: Typography,
|
|
||||||
textMeasurer: TextMeasurer,
|
|
||||||
smallPadding: Int,
|
|
||||||
extraSmallPadding: Int,
|
|
||||||
coverHeight: Float,
|
|
||||||
constraints: Constraints,
|
|
||||||
detailsConstraints: Constraints,
|
|
||||||
): Dp {
|
|
||||||
val titleHeight = textMeasurer.measureHeight(manga.title, typography.titleSmall, 2, constraints)
|
|
||||||
val authorHeight = if (!manga.author.isNullOrBlank()) {
|
|
||||||
textMeasurer.measureHeight(manga.author!!, typography.bodySmall, 2, detailsConstraints)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
val artistHeight = if (!manga.artist.isNullOrBlank() && manga.author != manga.artist) {
|
|
||||||
textMeasurer.measureHeight(manga.artist!!, typography.bodySmall, 2, detailsConstraints)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
val statusHeight = textMeasurer.measureHeight("", typography.bodySmall, 2, detailsConstraints)
|
|
||||||
val sourceHeight = textMeasurer.measureHeight("", typography.labelSmall, 1, constraints)
|
|
||||||
|
|
||||||
val totalHeight = coverHeight + titleHeight + authorHeight + artistHeight + statusHeight + sourceHeight
|
|
||||||
return with(density) { ((2 * smallPadding) + totalHeight + (5 * extraSmallPadding)).toDp() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TextMeasurer.measureHeight(
|
|
||||||
text: String,
|
|
||||||
style: TextStyle,
|
|
||||||
maxLines: Int,
|
|
||||||
constraints: Constraints,
|
|
||||||
): Int = measure(
|
|
||||||
text = text,
|
|
||||||
style = style,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = maxLines,
|
|
||||||
constraints = constraints,
|
|
||||||
)
|
|
||||||
.size
|
|
||||||
.height
|
|
||||||
|
|
||||||
private val MangaCardWidth = 150.dp
|
|
||||||
private val MangaDetailsIconWidth = 16.dp
|
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
package eu.kanade.presentation.manga
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
|
||||||
import androidx.compose.foundation.layout.imePadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
|
||||||
import eu.kanade.presentation.components.AppBarTitle
|
|
||||||
import eu.kanade.presentation.manga.components.MangaNotesTextArea
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreen
|
|
||||||
import tachiyomi.i18n.MR
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaNotesScreen(
|
|
||||||
state: MangaNotesScreen.State,
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
onUpdate: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
topBar = { topBarScrollBehavior ->
|
|
||||||
AppBar(
|
|
||||||
titleContent = {
|
|
||||||
AppBarTitle(
|
|
||||||
title = stringResource(MR.strings.action_edit_notes),
|
|
||||||
subtitle = state.manga.title,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigateUp = navigateUp,
|
|
||||||
scrollBehavior = topBarScrollBehavior,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { contentPadding ->
|
|
||||||
MangaNotesTextArea(
|
|
||||||
state = state,
|
|
||||||
onUpdate = onUpdate,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(contentPadding)
|
|
||||||
.consumeWindowInsets(contentPadding)
|
|
||||||
.imePadding(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -142,7 +142,6 @@ fun MangaScreen(
|
|||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
onEditFetchIntervalClicked: (() -> Unit)?,
|
onEditFetchIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
onEditNotesClicked: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked: () -> Unit,
|
onMetadataViewerClicked: () -> Unit,
|
||||||
onEditInfoClicked: () -> Unit,
|
onEditInfoClicked: () -> Unit,
|
||||||
@ -202,7 +201,6 @@ fun MangaScreen(
|
|||||||
onEditCategoryClicked = onEditCategoryClicked,
|
onEditCategoryClicked = onEditCategoryClicked,
|
||||||
onEditIntervalClicked = onEditFetchIntervalClicked,
|
onEditIntervalClicked = onEditFetchIntervalClicked,
|
||||||
onMigrateClicked = onMigrateClicked,
|
onMigrateClicked = onMigrateClicked,
|
||||||
onEditNotesClicked = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked = onMetadataViewerClicked,
|
onMetadataViewerClicked = onMetadataViewerClicked,
|
||||||
onEditInfoClicked = onEditInfoClicked,
|
onEditInfoClicked = onEditInfoClicked,
|
||||||
@ -249,7 +247,6 @@ fun MangaScreen(
|
|||||||
onEditCategoryClicked = onEditCategoryClicked,
|
onEditCategoryClicked = onEditCategoryClicked,
|
||||||
onEditIntervalClicked = onEditFetchIntervalClicked,
|
onEditIntervalClicked = onEditFetchIntervalClicked,
|
||||||
onMigrateClicked = onMigrateClicked,
|
onMigrateClicked = onMigrateClicked,
|
||||||
onEditNotesClicked = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked = onMetadataViewerClicked,
|
onMetadataViewerClicked = onMetadataViewerClicked,
|
||||||
onEditInfoClicked = onEditInfoClicked,
|
onEditInfoClicked = onEditInfoClicked,
|
||||||
@ -306,7 +303,6 @@ private fun MangaScreenSmallImpl(
|
|||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
onEditIntervalClicked: (() -> Unit)?,
|
onEditIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
onEditNotesClicked: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked: () -> Unit,
|
onMetadataViewerClicked: () -> Unit,
|
||||||
onEditInfoClicked: () -> Unit,
|
onEditInfoClicked: () -> Unit,
|
||||||
@ -349,9 +345,13 @@ private fun MangaScreenSmallImpl(
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
BackHandler(enabled = isAnySelected) {
|
BackHandler(onBack = {
|
||||||
onAllChapterSelected(false)
|
if (isAnySelected) {
|
||||||
}
|
onAllChapterSelected(false)
|
||||||
|
} else {
|
||||||
|
navigateUp()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -382,7 +382,6 @@ private fun MangaScreenSmallImpl(
|
|||||||
onClickEditCategory = onEditCategoryClicked,
|
onClickEditCategory = onEditCategoryClicked,
|
||||||
onClickRefresh = onRefresh,
|
onClickRefresh = onRefresh,
|
||||||
onClickMigrate = onMigrateClicked,
|
onClickMigrate = onMigrateClicked,
|
||||||
onClickEditNotes = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
||||||
onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow },
|
onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow },
|
||||||
@ -520,10 +519,8 @@ private fun MangaScreenSmallImpl(
|
|||||||
defaultExpandState = state.isFromSource,
|
defaultExpandState = state.isFromSource,
|
||||||
description = state.manga.description,
|
description = state.manga.description,
|
||||||
tagsProvider = { state.manga.genre },
|
tagsProvider = { state.manga.genre },
|
||||||
notes = state.manga.notes,
|
|
||||||
onTagSearch = onTagSearch,
|
onTagSearch = onTagSearch,
|
||||||
onCopyTagToClipboard = onCopyTagToClipboard,
|
onCopyTagToClipboard = onCopyTagToClipboard,
|
||||||
onEditNotes = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
||||||
@ -629,7 +626,6 @@ fun MangaScreenLargeImpl(
|
|||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
onEditIntervalClicked: (() -> Unit)?,
|
onEditIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
onEditNotesClicked: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked: () -> Unit,
|
onMetadataViewerClicked: () -> Unit,
|
||||||
onEditInfoClicked: () -> Unit,
|
onEditInfoClicked: () -> Unit,
|
||||||
@ -676,9 +672,13 @@ fun MangaScreenLargeImpl(
|
|||||||
|
|
||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
BackHandler(enabled = isAnySelected) {
|
BackHandler(onBack = {
|
||||||
onAllChapterSelected(false)
|
if (isAnySelected) {
|
||||||
}
|
onAllChapterSelected(false)
|
||||||
|
} else {
|
||||||
|
navigateUp()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -696,7 +696,6 @@ fun MangaScreenLargeImpl(
|
|||||||
onClickEditCategory = onEditCategoryClicked,
|
onClickEditCategory = onEditCategoryClicked,
|
||||||
onClickRefresh = onRefresh,
|
onClickRefresh = onRefresh,
|
||||||
onClickMigrate = onMigrateClicked,
|
onClickMigrate = onMigrateClicked,
|
||||||
onClickEditNotes = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
||||||
onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow },
|
onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow },
|
||||||
@ -815,10 +814,8 @@ fun MangaScreenLargeImpl(
|
|||||||
defaultExpandState = true,
|
defaultExpandState = true,
|
||||||
description = state.manga.description,
|
description = state.manga.description,
|
||||||
tagsProvider = { state.manga.genre },
|
tagsProvider = { state.manga.genre },
|
||||||
notes = state.manga.notes,
|
|
||||||
onTagSearch = onTagSearch,
|
onTagSearch = onTagSearch,
|
||||||
onCopyTagToClipboard = onCopyTagToClipboard,
|
onCopyTagToClipboard = onCopyTagToClipboard,
|
||||||
onEditNotes = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
||||||
|
@ -3,9 +3,6 @@ package eu.kanade.presentation.manga.components
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.activity.compose.PredictiveBackHandler
|
|
||||||
import androidx.compose.animation.core.animate
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -28,22 +25,18 @@ import androidx.compose.material3.SnackbarHostState
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.lerp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.core.graphics.drawable.toDrawable
|
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import coil3.asDrawable
|
import coil3.asDrawable
|
||||||
import coil3.imageLoader
|
import coil3.imageLoader
|
||||||
@ -56,14 +49,11 @@ import eu.kanade.presentation.components.DropdownMenu
|
|||||||
import eu.kanade.presentation.manga.EditCoverAction
|
import eu.kanade.presentation.manga.EditCoverAction
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import soup.compose.material.motion.MotionConstants
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.PredictiveBack
|
|
||||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaCoverDialog(
|
fun MangaCoverDialog(
|
||||||
@ -162,32 +152,10 @@ fun MangaCoverDialog(
|
|||||||
val statusBarPaddingPx = with(LocalDensity.current) { contentPadding.calculateTopPadding().roundToPx() }
|
val statusBarPaddingPx = with(LocalDensity.current) { contentPadding.calculateTopPadding().roundToPx() }
|
||||||
val bottomPaddingPx = with(LocalDensity.current) { contentPadding.calculateBottomPadding().roundToPx() }
|
val bottomPaddingPx = with(LocalDensity.current) { contentPadding.calculateBottomPadding().roundToPx() }
|
||||||
|
|
||||||
var scale by remember { mutableFloatStateOf(1f) }
|
|
||||||
PredictiveBackHandler { progress ->
|
|
||||||
try {
|
|
||||||
progress.collect { backEvent ->
|
|
||||||
scale = lerp(1f, 0.8f, PredictiveBack.transform(backEvent.progress))
|
|
||||||
}
|
|
||||||
onDismissRequest()
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
animate(
|
|
||||||
initialValue = scale,
|
|
||||||
targetValue = 1f,
|
|
||||||
animationSpec = tween(durationMillis = MotionConstants.DefaultMotionDuration),
|
|
||||||
) { value, _ ->
|
|
||||||
scale = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clickableNoIndication(onClick = onDismissRequest)
|
.clickableNoIndication(onClick = onDismissRequest),
|
||||||
.graphicsLayer {
|
|
||||||
scaleX = scale
|
|
||||||
scaleY = scale
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = {
|
factory = {
|
||||||
@ -204,20 +172,20 @@ fun MangaCoverDialog(
|
|||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.target { image ->
|
.target { image ->
|
||||||
val drawable = image.asDrawable(view.context.resources)
|
val drawable = image.asDrawable(view.context.resources)
|
||||||
|
|
||||||
// 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
|
||||||
val copy = (drawable as? BitmapDrawable)
|
val copy = (drawable as? BitmapDrawable)?.let {
|
||||||
?.bitmap
|
val config = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
?.copy(
|
Bitmap.Config.HARDWARE
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
} else {
|
||||||
Bitmap.Config.HARDWARE
|
Bitmap.Config.ARGB_8888
|
||||||
} else {
|
}
|
||||||
Bitmap.Config.ARGB_8888
|
BitmapDrawable(
|
||||||
},
|
view.context.resources,
|
||||||
false,
|
it.bitmap.copy(config, false),
|
||||||
)
|
)
|
||||||
?.toDrawable(view.context.resources)
|
} ?: drawable
|
||||||
?: drawable
|
|
||||||
view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
|
view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
@ -77,8 +77,6 @@ import androidx.compose.ui.unit.sp
|
|||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import coil3.request.crossfade
|
import coil3.request.crossfade
|
||||||
import com.mikepenz.markdown.model.markdownAnnotator
|
|
||||||
import com.mikepenz.markdown.model.markdownAnnotatorConfig
|
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
@ -97,6 +95,8 @@ import java.time.Instant
|
|||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaInfoBox(
|
fun MangaInfoBox(
|
||||||
isTabletUi: Boolean,
|
isTabletUi: Boolean,
|
||||||
@ -250,10 +250,8 @@ fun ExpandableMangaDescription(
|
|||||||
defaultExpandState: Boolean,
|
defaultExpandState: Boolean,
|
||||||
description: String?,
|
description: String?,
|
||||||
tagsProvider: () -> List<String>?,
|
tagsProvider: () -> List<String>?,
|
||||||
notes: String,
|
|
||||||
onTagSearch: (String) -> Unit,
|
onTagSearch: (String) -> Unit,
|
||||||
onCopyTagToClipboard: (tag: String) -> Unit,
|
onCopyTagToClipboard: (tag: String) -> Unit,
|
||||||
onEditNotes: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
searchMetadataChips: SearchMetadataChips?,
|
searchMetadataChips: SearchMetadataChips?,
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
@ -266,12 +264,15 @@ fun ExpandableMangaDescription(
|
|||||||
}
|
}
|
||||||
val desc =
|
val desc =
|
||||||
description.takeIf { !it.isNullOrBlank() } ?: stringResource(MR.strings.description_placeholder)
|
description.takeIf { !it.isNullOrBlank() } ?: stringResource(MR.strings.description_placeholder)
|
||||||
|
val trimmedDescription = remember(desc) {
|
||||||
|
desc
|
||||||
|
.replace(whitespaceLineRegex, "\n")
|
||||||
|
.trimEnd()
|
||||||
|
}
|
||||||
MangaSummary(
|
MangaSummary(
|
||||||
description = desc,
|
expandedDescription = desc,
|
||||||
|
shrunkDescription = trimmedDescription,
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
notes = notes,
|
|
||||||
onEditNotesClicked = onEditNotes,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
@ -593,26 +594,11 @@ private fun ColumnScope.MangaContentInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val descriptionAnnotator = markdownAnnotator(
|
|
||||||
annotate = { content, child ->
|
|
||||||
if (child.type in DISALLOWED_MARKDOWN_TYPES) {
|
|
||||||
append(content.substring(child.startOffset, child.endOffset))
|
|
||||||
return@markdownAnnotator true
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
},
|
|
||||||
config = markdownAnnotatorConfig(
|
|
||||||
eolAsNewLine = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MangaSummary(
|
private fun MangaSummary(
|
||||||
description: String,
|
expandedDescription: String,
|
||||||
notes: String,
|
shrunkDescription: String,
|
||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
onEditNotesClicked: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val animProgress by animateFloatAsState(
|
val animProgress by animateFloatAsState(
|
||||||
@ -624,40 +610,25 @@ private fun MangaSummary(
|
|||||||
contents = listOf(
|
contents = listOf(
|
||||||
{
|
{
|
||||||
Text(
|
Text(
|
||||||
// Shows at least 3 lines if no notes
|
text = "\n\n", // Shows at least 3 lines
|
||||||
// when there are notes show 6
|
|
||||||
text = if (notes.isBlank()) "\n\n" else "\n\n\n\n\n",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Column {
|
Text(
|
||||||
MangaNotesSection(
|
text = expandedDescription,
|
||||||
content = notes,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
expanded = true,
|
)
|
||||||
onEditNotes = onEditNotesClicked,
|
|
||||||
)
|
|
||||||
MarkdownRender(
|
|
||||||
content = description,
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
annotator = descriptionAnnotator,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Column {
|
SelectionContainer {
|
||||||
MangaNotesSection(
|
Text(
|
||||||
content = notes,
|
text = if (expanded) expandedDescription else shrunkDescription,
|
||||||
expanded = expanded,
|
maxLines = Int.MAX_VALUE,
|
||||||
onEditNotes = onEditNotesClicked,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
)
|
)
|
||||||
SelectionContainer {
|
|
||||||
MarkdownRender(
|
|
||||||
content = description,
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
annotator = descriptionAnnotator,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
|
||||||
import androidx.compose.animation.core.Animatable
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import com.mohamedrejeb.richeditor.model.rememberRichTextState
|
|
||||||
import com.mohamedrejeb.richeditor.ui.material3.RichText
|
|
||||||
|
|
||||||
private val FADE_TIME = tween<Float>(500)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaNotesDisplay(
|
|
||||||
content: String,
|
|
||||||
modifier: Modifier,
|
|
||||||
) {
|
|
||||||
val alpha = remember { Animatable(1f) }
|
|
||||||
var contentUpdatedOnce by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val richTextState = rememberRichTextState()
|
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
|
||||||
LaunchedEffect(content) {
|
|
||||||
richTextState.setMarkdown(content)
|
|
||||||
|
|
||||||
if (!contentUpdatedOnce) {
|
|
||||||
contentUpdatedOnce = true
|
|
||||||
return@LaunchedEffect
|
|
||||||
}
|
|
||||||
|
|
||||||
alpha.snapTo(targetValue = 0f)
|
|
||||||
alpha.animateTo(targetValue = 1f, animationSpec = FADE_TIME)
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
richTextState.config.unorderedListIndent = 4
|
|
||||||
richTextState.config.orderedListIndent = 20
|
|
||||||
}
|
|
||||||
LaunchedEffect(primaryColor) {
|
|
||||||
richTextState.config.linkColor = primaryColor
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectionContainer {
|
|
||||||
RichText(
|
|
||||||
modifier = modifier
|
|
||||||
// Only animate size if the notes changes
|
|
||||||
.then(if (contentUpdatedOnce) Modifier.animateContentSize() else Modifier)
|
|
||||||
.alpha(alpha.value),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
state = richTextState,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.EditNote
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import tachiyomi.i18n.MR
|
|
||||||
import tachiyomi.presentation.core.components.material.Button
|
|
||||||
import tachiyomi.presentation.core.components.material.ButtonDefaults
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaNotesSection(
|
|
||||||
content: String,
|
|
||||||
expanded: Boolean,
|
|
||||||
onEditNotes: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
if (content.isBlank()) return
|
|
||||||
Column(
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
MangaNotesDisplay(
|
|
||||||
content = content,
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
if (expanded) {
|
|
||||||
Button(
|
|
||||||
onClick = onEditNotes,
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
contentColor = MaterialTheme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(8.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.EditNote,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(16.dp),
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
stringResource(MR.strings.action_edit_notes),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
top = if (expanded) 0.dp else 12.dp,
|
|
||||||
bottom = if (expanded) 16.dp else 12.dp,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewLightDark
|
|
||||||
@Composable
|
|
||||||
private fun MangaNotesSectionPreview() {
|
|
||||||
MangaNotesSection(
|
|
||||||
onEditNotes = {},
|
|
||||||
expanded = true,
|
|
||||||
content = "# Hello world\ntest1234 hi there!",
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,224 +0,0 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
|
|
||||||
import androidx.compose.material.icons.outlined.FormatBold
|
|
||||||
import androidx.compose.material.icons.outlined.FormatItalic
|
|
||||||
import androidx.compose.material.icons.outlined.FormatListNumbered
|
|
||||||
import androidx.compose.material.icons.outlined.FormatUnderlined
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.VerticalDivider
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.snapshotFlow
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.semantics.Role
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.mohamedrejeb.richeditor.model.rememberRichTextState
|
|
||||||
import com.mohamedrejeb.richeditor.ui.material3.RichTextEditor
|
|
||||||
import com.mohamedrejeb.richeditor.ui.material3.RichTextEditorDefaults.richTextEditorColors
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreen
|
|
||||||
import kotlinx.coroutines.flow.debounce
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import tachiyomi.i18n.MR
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
private const val MAX_LENGTH = 250
|
|
||||||
private const val MAX_LENGTH_WARN = MAX_LENGTH * 0.9
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaNotesTextArea(
|
|
||||||
state: MangaNotesScreen.State,
|
|
||||||
onUpdate: (String) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val richTextState = rememberRichTextState()
|
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
|
||||||
|
|
||||||
DisposableEffect(scope, richTextState) {
|
|
||||||
snapshotFlow { richTextState.annotatedString }
|
|
||||||
.debounce(0.25.seconds)
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.map { richTextState.toMarkdown() }
|
|
||||||
.onEach { onUpdate(it) }
|
|
||||||
.launchIn(scope)
|
|
||||||
|
|
||||||
onDispose {
|
|
||||||
onUpdate(richTextState.toMarkdown())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
richTextState.setMarkdown(state.notes)
|
|
||||||
richTextState.config.unorderedListIndent = 4
|
|
||||||
richTextState.config.orderedListIndent = 20
|
|
||||||
}
|
|
||||||
LaunchedEffect(primaryColor) {
|
|
||||||
richTextState.config.linkColor = primaryColor
|
|
||||||
}
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
|
||||||
LaunchedEffect(focusRequester) {
|
|
||||||
focusRequester.requestFocus()
|
|
||||||
}
|
|
||||||
val textLength = remember(richTextState.annotatedString) { richTextState.toText().length }
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.padding(horizontal = MaterialTheme.padding.small)
|
|
||||||
.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
RichTextEditor(
|
|
||||||
state = richTextState,
|
|
||||||
textStyle = MaterialTheme.typography.bodyLarge,
|
|
||||||
maxLength = MAX_LENGTH,
|
|
||||||
placeholder = {
|
|
||||||
Text(text = stringResource(MR.strings.notes_placeholder))
|
|
||||||
},
|
|
||||||
colors = richTextEditorColors(
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
focusedIndicatorColor = Color.Transparent,
|
|
||||||
unfocusedIndicatorColor = Color.Transparent,
|
|
||||||
),
|
|
||||||
contentPadding = PaddingValues(
|
|
||||||
horizontal = MaterialTheme.padding.medium,
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.focusRequester(focusRequester),
|
|
||||||
)
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = MaterialTheme.padding.small)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
LazyRow(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
MangaNotesTextAreaButton(
|
|
||||||
onClick = { richTextState.toggleSpanStyle(SpanStyle(fontWeight = FontWeight.Bold)) },
|
|
||||||
isSelected = richTextState.currentSpanStyle.fontWeight == FontWeight.Bold,
|
|
||||||
icon = Icons.Outlined.FormatBold,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
MangaNotesTextAreaButton(
|
|
||||||
onClick = { richTextState.toggleSpanStyle(SpanStyle(fontStyle = FontStyle.Italic)) },
|
|
||||||
isSelected = richTextState.currentSpanStyle.fontStyle == FontStyle.Italic,
|
|
||||||
icon = Icons.Outlined.FormatItalic,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
MangaNotesTextAreaButton(
|
|
||||||
onClick = {
|
|
||||||
richTextState.toggleSpanStyle(SpanStyle(textDecoration = TextDecoration.Underline))
|
|
||||||
},
|
|
||||||
isSelected = richTextState.currentSpanStyle.textDecoration
|
|
||||||
?.contains(TextDecoration.Underline)
|
|
||||||
?: false,
|
|
||||||
icon = Icons.Outlined.FormatUnderlined,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
VerticalDivider(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = MaterialTheme.padding.extraSmall)
|
|
||||||
.height(MaterialTheme.padding.large),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
MangaNotesTextAreaButton(
|
|
||||||
onClick = { richTextState.toggleUnorderedList() },
|
|
||||||
isSelected = richTextState.isUnorderedList,
|
|
||||||
icon = Icons.AutoMirrored.Outlined.FormatListBulleted,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
MangaNotesTextAreaButton(
|
|
||||||
onClick = { richTextState.toggleOrderedList() },
|
|
||||||
isSelected = richTextState.isOrderedList,
|
|
||||||
icon = Icons.Outlined.FormatListNumbered,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = (MAX_LENGTH - textLength).toString(),
|
|
||||||
color = if (textLength > MAX_LENGTH_WARN) {
|
|
||||||
MaterialTheme.colorScheme.error
|
|
||||||
} else {
|
|
||||||
Color.Unspecified
|
|
||||||
},
|
|
||||||
modifier = Modifier.padding(MaterialTheme.padding.extraSmall),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaNotesTextAreaButton(
|
|
||||||
onClick: () -> Unit,
|
|
||||||
icon: ImageVector,
|
|
||||||
isSelected: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = modifier
|
|
||||||
.clip(MaterialTheme.shapes.small)
|
|
||||||
.clickable(
|
|
||||||
onClick = onClick,
|
|
||||||
enabled = true,
|
|
||||||
role = Role.Button,
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = icon.name,
|
|
||||||
tint = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.primary,
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = if (isSelected) MaterialTheme.colorScheme.onBackground else Color.Transparent)
|
|
||||||
.padding(MaterialTheme.padding.extraSmall),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -38,7 +38,6 @@ fun MangaToolbar(
|
|||||||
onClickEditCategory: (() -> Unit)?,
|
onClickEditCategory: (() -> Unit)?,
|
||||||
onClickRefresh: () -> Unit,
|
onClickRefresh: () -> Unit,
|
||||||
onClickMigrate: (() -> Unit)?,
|
onClickMigrate: (() -> Unit)?,
|
||||||
onClickEditNotes: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickEditInfo: (() -> Unit)?,
|
onClickEditInfo: (() -> Unit)?,
|
||||||
onClickRecommend: (() -> Unit)?,
|
onClickRecommend: (() -> Unit)?,
|
||||||
@ -148,12 +147,6 @@ fun MangaToolbar(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_notes),
|
|
||||||
onClick = onClickEditNotes,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// SY -->
|
// SY -->
|
||||||
if (onClickMerge != null) {
|
if (onClickMerge != null) {
|
||||||
add(
|
add(
|
||||||
|
@ -1,253 +0,0 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.FirstBaseline
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
|
||||||
import androidx.compose.ui.text.TextLinkStyles
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl
|
|
||||||
import com.mikepenz.markdown.compose.LocalBulletListHandler
|
|
||||||
import com.mikepenz.markdown.compose.Markdown
|
|
||||||
import com.mikepenz.markdown.compose.components.markdownComponents
|
|
||||||
import com.mikepenz.markdown.compose.elements.MarkdownBulletList
|
|
||||||
import com.mikepenz.markdown.compose.elements.MarkdownDivider
|
|
||||||
import com.mikepenz.markdown.compose.elements.MarkdownOrderedList
|
|
||||||
import com.mikepenz.markdown.compose.elements.MarkdownTable
|
|
||||||
import com.mikepenz.markdown.compose.elements.MarkdownTableHeader
|
|
||||||
import com.mikepenz.markdown.compose.elements.MarkdownTableRow
|
|
||||||
import com.mikepenz.markdown.compose.elements.MarkdownText
|
|
||||||
import com.mikepenz.markdown.compose.elements.listDepth
|
|
||||||
import com.mikepenz.markdown.model.DefaultMarkdownColors
|
|
||||||
import com.mikepenz.markdown.model.DefaultMarkdownTypography
|
|
||||||
import com.mikepenz.markdown.model.MarkdownAnnotator
|
|
||||||
import com.mikepenz.markdown.model.MarkdownColors
|
|
||||||
import com.mikepenz.markdown.model.MarkdownPadding
|
|
||||||
import com.mikepenz.markdown.model.MarkdownTypography
|
|
||||||
import com.mikepenz.markdown.model.markdownAnnotator
|
|
||||||
import com.mikepenz.markdown.model.rememberMarkdownState
|
|
||||||
import org.intellij.markdown.MarkdownTokenTypes.Companion.HTML_TAG
|
|
||||||
import org.intellij.markdown.flavours.MarkdownFlavourDescriptor
|
|
||||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
|
||||||
import org.intellij.markdown.flavours.commonmark.CommonMarkMarkerProcessor
|
|
||||||
import org.intellij.markdown.flavours.gfm.table.GitHubTableMarkerProvider
|
|
||||||
import org.intellij.markdown.parser.MarkerProcessor
|
|
||||||
import org.intellij.markdown.parser.MarkerProcessorFactory
|
|
||||||
import org.intellij.markdown.parser.ProductionHolder
|
|
||||||
import org.intellij.markdown.parser.constraints.CommonMarkdownConstraints
|
|
||||||
import org.intellij.markdown.parser.constraints.MarkdownConstraints
|
|
||||||
import org.intellij.markdown.parser.markerblocks.MarkerBlockProvider
|
|
||||||
import org.intellij.markdown.parser.markerblocks.providers.AtxHeaderProvider
|
|
||||||
import org.intellij.markdown.parser.markerblocks.providers.BlockQuoteProvider
|
|
||||||
import org.intellij.markdown.parser.markerblocks.providers.CodeBlockProvider
|
|
||||||
import org.intellij.markdown.parser.markerblocks.providers.CodeFenceProvider
|
|
||||||
import org.intellij.markdown.parser.markerblocks.providers.HorizontalRuleProvider
|
|
||||||
import org.intellij.markdown.parser.markerblocks.providers.ListMarkerProvider
|
|
||||||
import org.intellij.markdown.parser.markerblocks.providers.SetextHeaderProvider
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MarkdownRender(
|
|
||||||
content: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
flavour: MarkdownFlavourDescriptor = SimpleMarkdownFlavourDescriptor,
|
|
||||||
annotator: MarkdownAnnotator = remember { markdownAnnotator() },
|
|
||||||
) {
|
|
||||||
Markdown(
|
|
||||||
markdownState = rememberMarkdownState(
|
|
||||||
content = content,
|
|
||||||
flavour = flavour,
|
|
||||||
immediate = true,
|
|
||||||
),
|
|
||||||
annotator = annotator,
|
|
||||||
colors = getMarkdownColors(),
|
|
||||||
typography = getMarkdownTypography(),
|
|
||||||
padding = markdownPadding,
|
|
||||||
components = markdownComponents,
|
|
||||||
imageTransformer = Coil3ImageTransformerImpl,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@ReadOnlyComposable
|
|
||||||
private fun getMarkdownColors(): MarkdownColors {
|
|
||||||
val codeBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
|
|
||||||
return DefaultMarkdownColors(
|
|
||||||
text = MaterialTheme.colorScheme.onSurface,
|
|
||||||
codeText = Color.Unspecified,
|
|
||||||
inlineCodeText = Color.Unspecified,
|
|
||||||
linkText = Color.Unspecified,
|
|
||||||
codeBackground = codeBackground,
|
|
||||||
inlineCodeBackground = codeBackground,
|
|
||||||
dividerColor = MaterialTheme.colorScheme.outlineVariant,
|
|
||||||
tableText = Color.Unspecified,
|
|
||||||
tableBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@ReadOnlyComposable
|
|
||||||
private fun getMarkdownTypography(): MarkdownTypography {
|
|
||||||
val link = MaterialTheme.typography.bodyMedium.copy(
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
)
|
|
||||||
return DefaultMarkdownTypography(
|
|
||||||
h1 = MaterialTheme.typography.headlineMedium,
|
|
||||||
h2 = MaterialTheme.typography.headlineSmall,
|
|
||||||
h3 = MaterialTheme.typography.titleLarge,
|
|
||||||
h4 = MaterialTheme.typography.titleMedium,
|
|
||||||
h5 = MaterialTheme.typography.titleSmall,
|
|
||||||
h6 = MaterialTheme.typography.bodyLarge,
|
|
||||||
text = MaterialTheme.typography.bodyMedium,
|
|
||||||
code = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
|
|
||||||
inlineCode = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
|
|
||||||
quote = MaterialTheme.typography.bodyMedium.plus(SpanStyle(fontStyle = FontStyle.Italic)),
|
|
||||||
paragraph = MaterialTheme.typography.bodyMedium,
|
|
||||||
ordered = MaterialTheme.typography.bodyMedium,
|
|
||||||
bullet = MaterialTheme.typography.bodyMedium,
|
|
||||||
list = MaterialTheme.typography.bodyMedium,
|
|
||||||
link = link,
|
|
||||||
textLink = TextLinkStyles(style = link.toSpanStyle()),
|
|
||||||
table = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val markdownPadding = object : MarkdownPadding {
|
|
||||||
override val block: Dp = 2.dp
|
|
||||||
override val blockQuote: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 0.dp)
|
|
||||||
override val blockQuoteBar: PaddingValues.Absolute = PaddingValues.Absolute(
|
|
||||||
left = 4.dp,
|
|
||||||
top = 2.dp,
|
|
||||||
right = 4.dp,
|
|
||||||
bottom = 2.dp,
|
|
||||||
)
|
|
||||||
override val blockQuoteText: PaddingValues = PaddingValues(vertical = 4.dp)
|
|
||||||
override val codeBlock: PaddingValues = PaddingValues(8.dp)
|
|
||||||
override val list: Dp = 0.dp
|
|
||||||
override val listIndent: Dp = 8.dp
|
|
||||||
override val listItemBottom: Dp = 0.dp
|
|
||||||
override val listItemTop: Dp = 0.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
private val markdownComponents = markdownComponents(
|
|
||||||
horizontalRule = {
|
|
||||||
MarkdownDivider(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = MaterialTheme.padding.extraSmall)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
orderedList = { ol ->
|
|
||||||
Column(modifier = Modifier.padding(start = MaterialTheme.padding.small)) {
|
|
||||||
MarkdownOrderedList(
|
|
||||||
content = ol.content,
|
|
||||||
node = ol.node,
|
|
||||||
style = ol.typography.ordered,
|
|
||||||
depth = ol.listDepth,
|
|
||||||
markerModifier = { Modifier.alignBy(FirstBaseline) },
|
|
||||||
listModifier = { Modifier.alignBy(FirstBaseline) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unorderedList = { ul ->
|
|
||||||
val markers = listOf("•", "◦", "▸", "▹")
|
|
||||||
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalBulletListHandler provides { _, _, _, _, _ -> "${markers[ul.listDepth % markers.size]} " },
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(start = MaterialTheme.padding.small)) {
|
|
||||||
MarkdownBulletList(
|
|
||||||
content = ul.content,
|
|
||||||
node = ul.node,
|
|
||||||
style = ul.typography.bullet,
|
|
||||||
markerModifier = { Modifier.alignBy(FirstBaseline) },
|
|
||||||
listModifier = { Modifier.alignBy(FirstBaseline) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
table = { t ->
|
|
||||||
MarkdownTable(
|
|
||||||
content = t.content,
|
|
||||||
node = t.node,
|
|
||||||
style = t.typography.text,
|
|
||||||
headerBlock = { content, header, tableWidth, style ->
|
|
||||||
MarkdownTableHeader(
|
|
||||||
content = content,
|
|
||||||
header = header,
|
|
||||||
tableWidth = tableWidth,
|
|
||||||
style = style,
|
|
||||||
maxLines = Int.MAX_VALUE,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
rowBlock = { content, header, tableWidth, style ->
|
|
||||||
MarkdownTableRow(
|
|
||||||
content = content,
|
|
||||||
header = header,
|
|
||||||
tableWidth = tableWidth,
|
|
||||||
style = style,
|
|
||||||
maxLines = Int.MAX_VALUE,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
custom = { type, model ->
|
|
||||||
if (type in DISALLOWED_MARKDOWN_TYPES) {
|
|
||||||
MarkdownText(
|
|
||||||
content = model.content.substring(model.node.startOffset, model.node.endOffset),
|
|
||||||
style = model.typography.text,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
private object SimpleMarkdownFlavourDescriptor : CommonMarkFlavourDescriptor() {
|
|
||||||
override val markerProcessorFactory: MarkerProcessorFactory = SimpleMarkdownProcessFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
private object SimpleMarkdownProcessFactory : MarkerProcessorFactory {
|
|
||||||
override fun createMarkerProcessor(productionHolder: ProductionHolder): MarkerProcessor<*> {
|
|
||||||
return SimpleMarkdownMarkerProcessor(productionHolder, CommonMarkdownConstraints.BASE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like `CommonMarkFlavour`, but with html blocks and reference links removed and
|
|
||||||
* table support added
|
|
||||||
*/
|
|
||||||
private class SimpleMarkdownMarkerProcessor(
|
|
||||||
productionHolder: ProductionHolder,
|
|
||||||
constraints: MarkdownConstraints,
|
|
||||||
) : CommonMarkMarkerProcessor(productionHolder, constraints) {
|
|
||||||
private val markerBlockProviders = listOf(
|
|
||||||
CodeBlockProvider(),
|
|
||||||
HorizontalRuleProvider(),
|
|
||||||
CodeFenceProvider(),
|
|
||||||
SetextHeaderProvider(),
|
|
||||||
BlockQuoteProvider(),
|
|
||||||
ListMarkerProvider(),
|
|
||||||
AtxHeaderProvider(),
|
|
||||||
GitHubTableMarkerProvider(),
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun getMarkerBlockProviders(): List<MarkerBlockProvider<StateInfo>> {
|
|
||||||
return markerBlockProviders
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val DISALLOWED_MARKDOWN_TYPES = arrayOf(HTML_TAG)
|
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.more
|
package eu.kanade.presentation.more
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -14,10 +13,13 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.presentation.manga.components.MarkdownRender
|
import com.halilibo.richtext.markdown.Markdown
|
||||||
|
import com.halilibo.richtext.ui.RichTextStyle
|
||||||
|
import com.halilibo.richtext.ui.material3.RichText
|
||||||
|
import com.halilibo.richtext.ui.string.RichTextStringStyle
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
@ -40,15 +42,17 @@ fun NewUpdateScreen(
|
|||||||
rejectText = stringResource(MR.strings.action_not_now),
|
rejectText = stringResource(MR.strings.action_not_now),
|
||||||
onRejectClick = onRejectUpdate,
|
onRejectClick = onRejectUpdate,
|
||||||
) {
|
) {
|
||||||
Column(
|
RichText(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = MaterialTheme.padding.large),
|
.padding(vertical = MaterialTheme.padding.large),
|
||||||
|
style = RichTextStyle(
|
||||||
|
stringStyle = RichTextStringStyle(
|
||||||
|
linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
MarkdownRender(
|
Markdown(content = changelogInfo)
|
||||||
content = changelogInfo,
|
|
||||||
flavour = GFMFlavourDescriptor(),
|
|
||||||
)
|
|
||||||
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = onOpenInBrowser,
|
onClick = onOpenInBrowser,
|
||||||
|
@ -42,9 +42,7 @@ fun OnboardingScreen(
|
|||||||
}
|
}
|
||||||
val isLastStep = currentStep == steps.lastIndex
|
val isLastStep = currentStep == steps.lastIndex
|
||||||
|
|
||||||
BackHandler(enabled = currentStep != 0) {
|
BackHandler(enabled = currentStep != 0, onBack = { currentStep-- })
|
||||||
currentStep--
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoScreen(
|
InfoScreen(
|
||||||
icon = Icons.Outlined.RocketLaunch,
|
icon = Icons.Outlined.RocketLaunch,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.more.settings
|
package eu.kanade.presentation.more.settings
|
||||||
|
|
||||||
import androidx.annotation.IntRange
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -54,9 +53,10 @@ sealed class Preference {
|
|||||||
*/
|
*/
|
||||||
data class SliderPreference(
|
data class SliderPreference(
|
||||||
val value: Int,
|
val value: Int,
|
||||||
|
val max: Int,
|
||||||
|
val min: Int = 0,
|
||||||
|
val steps: Int = 0,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
val valueRange: IntProgression = 0..1,
|
|
||||||
@IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 },
|
|
||||||
override val subtitle: String? = null,
|
override val subtitle: String? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (value: Int) -> Boolean = { true },
|
override val onValueChanged: suspend (value: Int) -> Boolean = { true },
|
||||||
|
@ -5,7 +5,6 @@ import androidx.compose.animation.expandVertically
|
|||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@ -14,20 +13,16 @@ import androidx.compose.runtime.compositionLocalOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.structuralEqualityPolicy
|
import androidx.compose.runtime.structuralEqualityPolicy
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.MultiSelectListPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.MultiSelectListPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
|
||||||
import eu.kanade.presentation.more.settings.widget.PrefsVerticalPadding
|
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TitleFontSize
|
|
||||||
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.BaseSliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
|
||||||
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
|
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
|
||||||
@ -82,22 +77,19 @@ internal fun PreferenceItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.SliderPreference -> {
|
is Preference.PreferenceItem.SliderPreference -> {
|
||||||
BaseSliderItem(
|
SliderItem(
|
||||||
label = item.title,
|
label = item.title,
|
||||||
value = item.value,
|
min = item.min,
|
||||||
valueRange = item.valueRange,
|
max = item.max,
|
||||||
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
|
||||||
steps = item.steps,
|
steps = item.steps,
|
||||||
labelStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
|
value = item.value,
|
||||||
|
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
||||||
onChange = {
|
onChange = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
item.onValueChanged(it)
|
item.onValueChanged(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(
|
labelStyle = MaterialTheme.typography.titleLarge,
|
||||||
horizontal = PrefsHorizontalPadding,
|
|
||||||
vertical = PrefsVerticalPadding,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.ListPreference<*> -> {
|
is Preference.PreferenceItem.ListPreference<*> -> {
|
||||||
|
@ -13,13 +13,13 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.log.xLogE
|
import exh.log.xLogE
|
||||||
import exh.source.ExhPreferences
|
|
||||||
import exh.uconfig.EHConfigurator
|
import exh.uconfig.EHConfigurator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import tachiyomi.core.common.util.lang.launchUI
|
import tachiyomi.core.common.util.lang.launchUI
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
@ -29,8 +29,8 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
|
fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
|
||||||
val exhPreferences = remember {
|
val unsortedPreferences = remember {
|
||||||
Injekt.get<ExhPreferences>()
|
Injekt.get<UnsortedPreferences>()
|
||||||
}
|
}
|
||||||
var warnDialogOpen by remember { mutableStateOf(false) }
|
var warnDialogOpen by remember { mutableStateOf(false) }
|
||||||
var configureDialogOpen by remember { mutableStateOf(false) }
|
var configureDialogOpen by remember { mutableStateOf(false) }
|
||||||
@ -38,7 +38,7 @@ fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
|
|||||||
|
|
||||||
LaunchedEffect(run) {
|
LaunchedEffect(run) {
|
||||||
if (run) {
|
if (run) {
|
||||||
if (exhPreferences.exhShowSettingsUploadWarning().get()) {
|
if (unsortedPreferences.exhShowSettingsUploadWarning().get()) {
|
||||||
warnDialogOpen = true
|
warnDialogOpen = true
|
||||||
} else {
|
} else {
|
||||||
configureDialogOpen = true
|
configureDialogOpen = true
|
||||||
@ -57,7 +57,7 @@ fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
|
|||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
exhPreferences.exhShowSettingsUploadWarning().set(false)
|
unsortedPreferences.exhShowSettingsUploadWarning().set(false)
|
||||||
configureDialogOpen = true
|
configureDialogOpen = true
|
||||||
warnDialogOpen = false
|
warnDialogOpen = false
|
||||||
},
|
},
|
||||||
|
@ -72,7 +72,6 @@ import exh.pref.DelegateSourcePreferences
|
|||||||
import exh.source.BlacklistedSources
|
import exh.source.BlacklistedSources
|
||||||
import exh.source.EH_SOURCE_ID
|
import exh.source.EH_SOURCE_ID
|
||||||
import exh.source.EXH_SOURCE_ID
|
import exh.source.EXH_SOURCE_ID
|
||||||
import exh.source.ExhPreferences
|
|
||||||
import exh.util.toAnnotatedString
|
import exh.util.toAnnotatedString
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
@ -87,8 +86,8 @@ import tachiyomi.core.common.util.lang.launchNonCancellable
|
|||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
import tachiyomi.core.common.util.system.ImageUtil
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
|
||||||
import tachiyomi.domain.manga.interactor.GetAllManga
|
import tachiyomi.domain.manga.interactor.GetAllManga
|
||||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
@ -115,7 +114,6 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
|
|
||||||
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
||||||
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
||||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
@ -156,7 +154,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
getBackgroundActivityGroup(),
|
getBackgroundActivityGroup(),
|
||||||
getDataGroup(),
|
getDataGroup(),
|
||||||
getNetworkGroup(networkPreferences = networkPreferences),
|
getNetworkGroup(networkPreferences = networkPreferences),
|
||||||
getLibraryGroup(libraryPreferences = libraryPreferences),
|
getLibraryGroup(),
|
||||||
getReaderGroup(basePreferences = basePreferences),
|
getReaderGroup(basePreferences = basePreferences),
|
||||||
getExtensionsGroup(basePreferences = basePreferences),
|
getExtensionsGroup(basePreferences = basePreferences),
|
||||||
// SY -->
|
// SY -->
|
||||||
@ -324,9 +322,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getLibraryGroup(
|
private fun getLibraryGroup(): Preference.PreferenceGroup {
|
||||||
libraryPreferences: LibraryPreferences,
|
|
||||||
): Preference.PreferenceGroup {
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -354,11 +350,6 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
preference = libraryPreferences.updateMangaTitles(),
|
|
||||||
title = stringResource(MR.strings.pref_update_library_manga_titles),
|
|
||||||
subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -402,7 +393,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = basePreferences.alwaysDecodeLongStripWithSSIV(),
|
preference = basePreferences.alwaysDecodeLongStripWithSSIV(),
|
||||||
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_2),
|
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv),
|
||||||
subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary),
|
subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
@ -701,14 +692,14 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
|
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
|
||||||
val exhPreferences = remember { Injekt.get<ExhPreferences>() }
|
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
||||||
val delegateSourcePreferences = remember { Injekt.get<DelegateSourcePreferences>() }
|
val delegateSourcePreferences = remember { Injekt.get<DelegateSourcePreferences>() }
|
||||||
val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
|
val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(SYMR.strings.developer_tools),
|
title = stringResource(SYMR.strings.developer_tools),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = exhPreferences.isHentaiEnabled(),
|
preference = unsortedPreferences.isHentaiEnabled(),
|
||||||
title = stringResource(SYMR.strings.toggle_hentai_features),
|
title = stringResource(SYMR.strings.toggle_hentai_features),
|
||||||
subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary),
|
subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
@ -733,7 +724,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
preference = exhPreferences.logLevel(),
|
preference = unsortedPreferences.logLevel(),
|
||||||
title = stringResource(SYMR.strings.log_level),
|
title = stringResource(SYMR.strings.log_level),
|
||||||
subtitle = stringResource(SYMR.strings.log_level_summary),
|
subtitle = stringResource(SYMR.strings.log_level_summary),
|
||||||
entries = EHLogLevel.entries.mapIndexed { index, ehLogLevel ->
|
entries = EHLogLevel.entries.mapIndexed { index, ehLogLevel ->
|
||||||
|
@ -189,7 +189,8 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
} else {
|
} else {
|
||||||
stringResource(MR.strings.disabled)
|
stringResource(MR.strings.disabled)
|
||||||
},
|
},
|
||||||
valueRange = 0..10,
|
min = 0,
|
||||||
|
max = 10,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
uiPreferences.previewsRowCount().set(it)
|
uiPreferences.previewsRowCount().set(it)
|
||||||
true
|
true
|
||||||
|
@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
|||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
|
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
@ -48,6 +49,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val hideFeedTab by remember { Injekt.get<UiPreferences>().hideFeedTab().asState(scope) }
|
val hideFeedTab by remember { Injekt.get<UiPreferences>().hideFeedTab().asState(scope) }
|
||||||
val uiPreferences = remember { Injekt.get<UiPreferences>() }
|
val uiPreferences = remember { Injekt.get<UiPreferences>() }
|
||||||
|
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
||||||
// SY <--
|
// SY <--
|
||||||
return listOf(
|
return listOf(
|
||||||
// SY -->
|
// SY -->
|
||||||
@ -75,7 +77,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
subtitle = stringResource(SYMR.strings.pref_source_navigation_summery),
|
subtitle = stringResource(SYMR.strings.pref_source_navigation_summery),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = sourcePreferences.allowLocalSourceHiddenFolders(),
|
preference = unsortedPreferences.allowLocalSourceHiddenFolders(),
|
||||||
title = stringResource(SYMR.strings.pref_local_source_hidden_folders),
|
title = stringResource(SYMR.strings.pref_local_source_hidden_folders),
|
||||||
subtitle = stringResource(SYMR.strings.pref_local_source_hidden_folders_summery),
|
subtitle = stringResource(SYMR.strings.pref_local_source_hidden_folders_summery),
|
||||||
),
|
),
|
||||||
@ -129,24 +131,6 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.parental_controls_info)),
|
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.parental_controls_info)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
getMigrationCategory(sourcePreferences),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun getMigrationCategory(sourcePreferences: SourcePreferences): Preference.PreferenceGroup {
|
|
||||||
val skipPreMigration by sourcePreferences.skipPreMigration().collectAsState()
|
|
||||||
val migrationSources by sourcePreferences.migrationSources().collectAsState()
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
stringResource(SYMR.strings.migration),
|
|
||||||
enabled = skipPreMigration || migrationSources.isNotEmpty(),
|
|
||||||
preferenceItems = persistentListOf(
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
preference = sourcePreferences.skipPreMigration(),
|
|
||||||
title = stringResource(SYMR.strings.skip_pre_migration),
|
|
||||||
subtitle = stringResource(SYMR.strings.pref_skip_pre_migration_summary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.height
|
|||||||
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.automirrored.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.filled.QrCodeScanner
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -43,10 +42,7 @@ import androidx.compose.ui.platform.LocalUriHandler
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
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 com.google.zxing.client.android.Intents
|
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
|
||||||
import eu.kanade.domain.sync.SyncPreferences
|
import eu.kanade.domain.sync.SyncPreferences
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
||||||
@ -55,9 +51,7 @@ import eu.kanade.presentation.more.settings.screen.data.StorageInfo
|
|||||||
import eu.kanade.presentation.more.settings.screen.data.SyncSettingsSelector
|
import eu.kanade.presentation.more.settings.screen.data.SyncSettingsSelector
|
||||||
import eu.kanade.presentation.more.settings.screen.data.SyncTriggerOptionsScreen
|
import eu.kanade.presentation.more.settings.screen.data.SyncTriggerOptionsScreen
|
||||||
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
|
||||||
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
||||||
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
|
||||||
import eu.kanade.presentation.util.relativeTimeSpanString
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||||
@ -88,6 +82,7 @@ import tachiyomi.domain.manga.model.Manga
|
|||||||
import tachiyomi.domain.storage.service.StoragePreferences
|
import tachiyomi.domain.storage.service.StoragePreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
|
import tachiyomi.presentation.core.components.material.TextButton
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -509,7 +504,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
//SY -->
|
||||||
@Composable
|
@Composable
|
||||||
private fun getSyncPreferences(syncPreferences: SyncPreferences, syncService: Int): List<Preference> {
|
private fun getSyncPreferences(syncPreferences: SyncPreferences, syncService: Int): List<Preference> {
|
||||||
return listOf(
|
return listOf(
|
||||||
@ -657,22 +652,6 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun getSelfHostPreferences(syncPreferences: SyncPreferences): List<Preference> {
|
private fun getSelfHostPreferences(syncPreferences: SyncPreferences): List<Preference> {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val qrScanLauncher = rememberLauncherForActivityResult(ScanContract()) {
|
|
||||||
if (it.contents != null && it.contents.isNotEmpty()) {
|
|
||||||
syncPreferences.clientAPIKey().set(it.contents)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scanOptions = remember {
|
|
||||||
ScanOptions().apply {
|
|
||||||
setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
|
||||||
setOrientationLocked(false)
|
|
||||||
setPrompt(SYMR.strings.scan_qr_code.getString(context))
|
|
||||||
addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
title = stringResource(SYMR.strings.pref_sync_host),
|
title = stringResource(SYMR.strings.pref_sync_host),
|
||||||
@ -688,32 +667,11 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.CustomPreference(
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
title = stringResource(SYMR.strings.pref_sync_api_key),
|
title = stringResource(SYMR.strings.pref_sync_api_key),
|
||||||
) {
|
subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ),
|
||||||
val values by syncPreferences.clientAPIKey().collectAsState()
|
preference = syncPreferences.clientAPIKey(),
|
||||||
EditTextPreferenceWidget(
|
),
|
||||||
title = stringResource(SYMR.strings.pref_sync_api_key),
|
|
||||||
subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ),
|
|
||||||
onConfirm = {
|
|
||||||
syncPreferences.clientAPIKey().set(it)
|
|
||||||
true
|
|
||||||
},
|
|
||||||
icon = null,
|
|
||||||
value = values,
|
|
||||||
widget = {
|
|
||||||
IconButton(
|
|
||||||
onClick = { qrScanLauncher.launch(scanOptions) },
|
|
||||||
modifier = Modifier.padding(start = TrailingWidgetBuffer),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.QrCodeScanner,
|
|
||||||
contentDescription = stringResource(SYMR.strings.scan_qr_code),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,6 @@ import exh.eh.EHentaiUpdateWorker
|
|||||||
import exh.eh.EHentaiUpdateWorkerConstants
|
import exh.eh.EHentaiUpdateWorkerConstants
|
||||||
import exh.eh.EHentaiUpdaterStats
|
import exh.eh.EHentaiUpdaterStats
|
||||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||||
import exh.source.ExhPreferences
|
|
||||||
import exh.ui.login.EhLoginActivity
|
import exh.ui.login.EhLoginActivity
|
||||||
import exh.util.nullIfBlank
|
import exh.util.nullIfBlank
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@ -64,6 +63,7 @@ import tachiyomi.core.common.util.lang.launchNonCancellable
|
|||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
|
||||||
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
|
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
|
||||||
@ -88,22 +88,22 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun getTitleRes() = SYMR.strings.pref_category_eh
|
override fun getTitleRes() = SYMR.strings.pref_category_eh
|
||||||
|
|
||||||
override fun isEnabled(): Boolean = Injekt.get<ExhPreferences>().isHentaiEnabled().get()
|
override fun isEnabled(): Boolean = Injekt.get<UnsortedPreferences>().isHentaiEnabled().get()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Reconfigure(
|
fun Reconfigure(
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
openWarnConfigureDialogController: () -> Unit,
|
openWarnConfigureDialogController: () -> Unit,
|
||||||
) {
|
) {
|
||||||
var initialLoadGuard by remember { mutableStateOf(false) }
|
var initialLoadGuard by remember { mutableStateOf(false) }
|
||||||
val useHentaiAtHome by exhPreferences.useHentaiAtHome().collectAsState()
|
val useHentaiAtHome by unsortedPreferences.useHentaiAtHome().collectAsState()
|
||||||
val useJapaneseTitle by exhPreferences.useJapaneseTitle().collectAsState()
|
val useJapaneseTitle by unsortedPreferences.useJapaneseTitle().collectAsState()
|
||||||
val useOriginalImages by exhPreferences.exhUseOriginalImages().collectAsState()
|
val useOriginalImages by unsortedPreferences.exhUseOriginalImages().collectAsState()
|
||||||
val ehTagFilterValue by exhPreferences.ehTagFilterValue().collectAsState()
|
val ehTagFilterValue by unsortedPreferences.ehTagFilterValue().collectAsState()
|
||||||
val ehTagWatchingValue by exhPreferences.ehTagWatchingValue().collectAsState()
|
val ehTagWatchingValue by unsortedPreferences.ehTagWatchingValue().collectAsState()
|
||||||
val settingsLanguages by exhPreferences.exhSettingsLanguages().collectAsState()
|
val settingsLanguages by unsortedPreferences.exhSettingsLanguages().collectAsState()
|
||||||
val enabledCategories by exhPreferences.exhEnabledCategories().collectAsState()
|
val enabledCategories by unsortedPreferences.exhEnabledCategories().collectAsState()
|
||||||
val imageQuality by exhPreferences.imageQuality().collectAsState()
|
val imageQuality by unsortedPreferences.imageQuality().collectAsState()
|
||||||
DisposableEffect(
|
DisposableEffect(
|
||||||
useHentaiAtHome,
|
useHentaiAtHome,
|
||||||
useJapaneseTitle,
|
useJapaneseTitle,
|
||||||
@ -124,15 +124,15 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val exhPreferences: ExhPreferences = remember { Injekt.get() }
|
val unsortedPreferences: UnsortedPreferences = remember { Injekt.get() }
|
||||||
val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() }
|
val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() }
|
||||||
val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() }
|
val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() }
|
||||||
val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() }
|
val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() }
|
||||||
val exhentaiEnabled by exhPreferences.enableExhentai().collectAsState()
|
val exhentaiEnabled by unsortedPreferences.enableExhentai().collectAsState()
|
||||||
var runConfigureDialog by remember { mutableStateOf(false) }
|
var runConfigureDialog by remember { mutableStateOf(false) }
|
||||||
val openWarnConfigureDialogController = { runConfigureDialog = true }
|
val openWarnConfigureDialogController = { runConfigureDialog = true }
|
||||||
|
|
||||||
Reconfigure(exhPreferences, openWarnConfigureDialogController)
|
Reconfigure(unsortedPreferences, openWarnConfigureDialogController)
|
||||||
|
|
||||||
ConfigureExhDialog(run = runConfigureDialog, onRunning = { runConfigureDialog = false })
|
ConfigureExhDialog(run = runConfigureDialog, onRunning = { runConfigureDialog = false })
|
||||||
|
|
||||||
@ -140,36 +140,36 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
stringResource(SYMR.strings.ehentai_prefs_account_settings),
|
stringResource(SYMR.strings.ehentai_prefs_account_settings),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
getLoginPreference(exhPreferences, openWarnConfigureDialogController),
|
getLoginPreference(unsortedPreferences, openWarnConfigureDialogController),
|
||||||
useHentaiAtHome(exhentaiEnabled, exhPreferences),
|
useHentaiAtHome(exhentaiEnabled, unsortedPreferences),
|
||||||
useJapaneseTitle(exhentaiEnabled, exhPreferences),
|
useJapaneseTitle(exhentaiEnabled, unsortedPreferences),
|
||||||
useOriginalImages(exhentaiEnabled, exhPreferences),
|
useOriginalImages(exhentaiEnabled, unsortedPreferences),
|
||||||
watchedTags(exhentaiEnabled),
|
watchedTags(exhentaiEnabled),
|
||||||
tagFilterThreshold(exhentaiEnabled, exhPreferences),
|
tagFilterThreshold(exhentaiEnabled, unsortedPreferences),
|
||||||
tagWatchingThreshold(exhentaiEnabled, exhPreferences),
|
tagWatchingThreshold(exhentaiEnabled, unsortedPreferences),
|
||||||
settingsLanguages(exhentaiEnabled, exhPreferences),
|
settingsLanguages(exhentaiEnabled, unsortedPreferences),
|
||||||
enabledCategories(exhentaiEnabled, exhPreferences),
|
enabledCategories(exhentaiEnabled, unsortedPreferences),
|
||||||
watchedListDefaultState(exhentaiEnabled, exhPreferences),
|
watchedListDefaultState(exhentaiEnabled, unsortedPreferences),
|
||||||
imageQuality(exhentaiEnabled, exhPreferences),
|
imageQuality(exhentaiEnabled, unsortedPreferences),
|
||||||
enhancedEhentaiView(exhPreferences),
|
enhancedEhentaiView(unsortedPreferences),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
stringResource(SYMR.strings.favorites_sync),
|
stringResource(SYMR.strings.favorites_sync),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
readOnlySync(exhPreferences),
|
readOnlySync(unsortedPreferences),
|
||||||
syncFavoriteNotes(),
|
syncFavoriteNotes(),
|
||||||
lenientSync(exhPreferences),
|
lenientSync(unsortedPreferences),
|
||||||
forceSyncReset(deleteFavoriteEntries),
|
forceSyncReset(deleteFavoriteEntries),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
stringResource(SYMR.strings.gallery_update_checker),
|
stringResource(SYMR.strings.gallery_update_checker),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
updateCheckerFrequency(exhPreferences),
|
updateCheckerFrequency(unsortedPreferences),
|
||||||
autoUpdateRequirements(exhPreferences),
|
autoUpdateRequirements(unsortedPreferences),
|
||||||
updaterStatistics(
|
updaterStatistics(
|
||||||
exhPreferences,
|
unsortedPreferences,
|
||||||
getExhFavoriteMangaWithMetadata,
|
getExhFavoriteMangaWithMetadata,
|
||||||
getFlatMetadataById,
|
getFlatMetadataById,
|
||||||
),
|
),
|
||||||
@ -180,7 +180,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun getLoginPreference(
|
fun getLoginPreference(
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
openWarnConfigureDialogController: () -> Unit,
|
openWarnConfigureDialogController: () -> Unit,
|
||||||
): Preference.PreferenceItem.SwitchPreference {
|
): Preference.PreferenceItem.SwitchPreference {
|
||||||
val activityResultContract =
|
val activityResultContract =
|
||||||
@ -191,9 +191,9 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val value by exhPreferences.enableExhentai().collectAsState()
|
val value by unsortedPreferences.enableExhentai().collectAsState()
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = exhPreferences.enableExhentai(),
|
preference = unsortedPreferences.enableExhentai(),
|
||||||
title = stringResource(SYMR.strings.enable_exhentai),
|
title = stringResource(SYMR.strings.enable_exhentai),
|
||||||
subtitle = if (!value) {
|
subtitle = if (!value) {
|
||||||
stringResource(SYMR.strings.requires_login)
|
stringResource(SYMR.strings.requires_login)
|
||||||
@ -202,7 +202,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
onValueChanged = { newVal ->
|
onValueChanged = { newVal ->
|
||||||
if (!newVal) {
|
if (!newVal) {
|
||||||
exhPreferences.enableExhentai().set(false)
|
unsortedPreferences.enableExhentai().set(false)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
activityResultContract.launch(EhLoginActivity.newIntent(context))
|
activityResultContract.launch(EhLoginActivity.newIntent(context))
|
||||||
@ -215,10 +215,10 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun useHentaiAtHome(
|
fun useHentaiAtHome(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.ListPreference<Int> {
|
): Preference.PreferenceItem.ListPreference<Int> {
|
||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
preference = exhPreferences.useHentaiAtHome(),
|
preference = unsortedPreferences.useHentaiAtHome(),
|
||||||
title = stringResource(SYMR.strings.use_hentai_at_home),
|
title = stringResource(SYMR.strings.use_hentai_at_home),
|
||||||
subtitle = stringResource(SYMR.strings.use_hentai_at_home_summary),
|
subtitle = stringResource(SYMR.strings.use_hentai_at_home_summary),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
@ -232,11 +232,11 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun useJapaneseTitle(
|
fun useJapaneseTitle(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.SwitchPreference {
|
): Preference.PreferenceItem.SwitchPreference {
|
||||||
val value by exhPreferences.useJapaneseTitle().collectAsState()
|
val value by unsortedPreferences.useJapaneseTitle().collectAsState()
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = exhPreferences.useJapaneseTitle(),
|
preference = unsortedPreferences.useJapaneseTitle(),
|
||||||
title = stringResource(SYMR.strings.show_japanese_titles),
|
title = stringResource(SYMR.strings.show_japanese_titles),
|
||||||
subtitle = if (value) {
|
subtitle = if (value) {
|
||||||
stringResource(SYMR.strings.show_japanese_titles_option_1)
|
stringResource(SYMR.strings.show_japanese_titles_option_1)
|
||||||
@ -250,11 +250,11 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun useOriginalImages(
|
fun useOriginalImages(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.SwitchPreference {
|
): Preference.PreferenceItem.SwitchPreference {
|
||||||
val value by exhPreferences.exhUseOriginalImages().collectAsState()
|
val value by unsortedPreferences.exhUseOriginalImages().collectAsState()
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = exhPreferences.exhUseOriginalImages(),
|
preference = unsortedPreferences.exhUseOriginalImages(),
|
||||||
title = stringResource(SYMR.strings.use_original_images),
|
title = stringResource(SYMR.strings.use_original_images),
|
||||||
subtitle = if (value) {
|
subtitle = if (value) {
|
||||||
stringResource(SYMR.strings.use_original_images_on)
|
stringResource(SYMR.strings.use_original_images_on)
|
||||||
@ -351,9 +351,9 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun tagFilterThreshold(
|
fun tagFilterThreshold(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.TextPreference {
|
): Preference.PreferenceItem.TextPreference {
|
||||||
val value by exhPreferences.ehTagFilterValue().collectAsState()
|
val value by unsortedPreferences.ehTagFilterValue().collectAsState()
|
||||||
var dialogOpen by remember { mutableStateOf(false) }
|
var dialogOpen by remember { mutableStateOf(false) }
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
TagThresholdDialog(
|
TagThresholdDialog(
|
||||||
@ -364,7 +364,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
outsideRangeError = stringResource(SYMR.strings.tag_filtering_threshhold_error),
|
outsideRangeError = stringResource(SYMR.strings.tag_filtering_threshhold_error),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
dialogOpen = false
|
dialogOpen = false
|
||||||
exhPreferences.ehTagFilterValue().set(it)
|
unsortedPreferences.ehTagFilterValue().set(it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -381,9 +381,9 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun tagWatchingThreshold(
|
fun tagWatchingThreshold(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.TextPreference {
|
): Preference.PreferenceItem.TextPreference {
|
||||||
val value by exhPreferences.ehTagWatchingValue().collectAsState()
|
val value by unsortedPreferences.ehTagWatchingValue().collectAsState()
|
||||||
var dialogOpen by remember { mutableStateOf(false) }
|
var dialogOpen by remember { mutableStateOf(false) }
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
TagThresholdDialog(
|
TagThresholdDialog(
|
||||||
@ -394,7 +394,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
outsideRangeError = stringResource(SYMR.strings.tag_watching_threshhold_error),
|
outsideRangeError = stringResource(SYMR.strings.tag_watching_threshhold_error),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
dialogOpen = false
|
dialogOpen = false
|
||||||
exhPreferences.ehTagWatchingValue().set(it)
|
unsortedPreferences.ehTagWatchingValue().set(it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -604,9 +604,9 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun settingsLanguages(
|
fun settingsLanguages(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.TextPreference {
|
): Preference.PreferenceItem.TextPreference {
|
||||||
val value by exhPreferences.exhSettingsLanguages().collectAsState()
|
val value by unsortedPreferences.exhSettingsLanguages().collectAsState()
|
||||||
var dialogOpen by remember { mutableStateOf(false) }
|
var dialogOpen by remember { mutableStateOf(false) }
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
LanguagesDialog(
|
LanguagesDialog(
|
||||||
@ -614,7 +614,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
initialValue = value,
|
initialValue = value,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
dialogOpen = false
|
dialogOpen = false
|
||||||
exhPreferences.exhSettingsLanguages().set(it)
|
unsortedPreferences.exhSettingsLanguages().set(it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -770,9 +770,9 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun enabledCategories(
|
fun enabledCategories(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.TextPreference {
|
): Preference.PreferenceItem.TextPreference {
|
||||||
val value by exhPreferences.exhEnabledCategories().collectAsState()
|
val value by unsortedPreferences.exhEnabledCategories().collectAsState()
|
||||||
var dialogOpen by remember { mutableStateOf(false) }
|
var dialogOpen by remember { mutableStateOf(false) }
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
FrontPageCategoriesDialog(
|
FrontPageCategoriesDialog(
|
||||||
@ -780,7 +780,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
initialValue = value,
|
initialValue = value,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
dialogOpen = false
|
dialogOpen = false
|
||||||
exhPreferences.exhEnabledCategories().set(it)
|
unsortedPreferences.exhEnabledCategories().set(it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -797,10 +797,10 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun watchedListDefaultState(
|
fun watchedListDefaultState(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.SwitchPreference {
|
): Preference.PreferenceItem.SwitchPreference {
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = exhPreferences.exhWatchedListDefaultState(),
|
preference = unsortedPreferences.exhWatchedListDefaultState(),
|
||||||
title = stringResource(SYMR.strings.watched_list_default),
|
title = stringResource(SYMR.strings.watched_list_default),
|
||||||
subtitle = stringResource(SYMR.strings.watched_list_state_summary),
|
subtitle = stringResource(SYMR.strings.watched_list_state_summary),
|
||||||
enabled = exhentaiEnabled,
|
enabled = exhentaiEnabled,
|
||||||
@ -810,10 +810,10 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun imageQuality(
|
fun imageQuality(
|
||||||
exhentaiEnabled: Boolean,
|
exhentaiEnabled: Boolean,
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.ListPreference<String> {
|
): Preference.PreferenceItem.ListPreference<String> {
|
||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
preference = exhPreferences.imageQuality(),
|
preference = unsortedPreferences.imageQuality(),
|
||||||
title = stringResource(SYMR.strings.eh_image_quality_summary),
|
title = stringResource(SYMR.strings.eh_image_quality_summary),
|
||||||
subtitle = stringResource(SYMR.strings.eh_image_quality),
|
subtitle = stringResource(SYMR.strings.eh_image_quality),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
@ -829,18 +829,18 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun enhancedEhentaiView(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
|
fun enhancedEhentaiView(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = exhPreferences.enhancedEHentaiView(),
|
preference = unsortedPreferences.enhancedEHentaiView(),
|
||||||
title = stringResource(SYMR.strings.pref_enhanced_e_hentai_view),
|
title = stringResource(SYMR.strings.pref_enhanced_e_hentai_view),
|
||||||
subtitle = stringResource(SYMR.strings.pref_enhanced_e_hentai_view_summary),
|
subtitle = stringResource(SYMR.strings.pref_enhanced_e_hentai_view_summary),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun readOnlySync(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
|
fun readOnlySync(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = exhPreferences.exhReadOnlySync(),
|
preference = unsortedPreferences.exhReadOnlySync(),
|
||||||
title = stringResource(SYMR.strings.disable_favorites_uploading),
|
title = stringResource(SYMR.strings.disable_favorites_uploading),
|
||||||
subtitle = stringResource(SYMR.strings.disable_favorites_uploading_summary),
|
subtitle = stringResource(SYMR.strings.disable_favorites_uploading_summary),
|
||||||
)
|
)
|
||||||
@ -863,9 +863,9 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun lenientSync(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
|
fun lenientSync(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = exhPreferences.exhLenientSync(),
|
preference = unsortedPreferences.exhLenientSync(),
|
||||||
title = stringResource(SYMR.strings.ignore_sync_errors),
|
title = stringResource(SYMR.strings.ignore_sync_errors),
|
||||||
subtitle = stringResource(SYMR.strings.ignore_sync_errors_summary),
|
subtitle = stringResource(SYMR.strings.ignore_sync_errors_summary),
|
||||||
)
|
)
|
||||||
@ -935,12 +935,12 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun updateCheckerFrequency(
|
fun updateCheckerFrequency(
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.ListPreference<Int> {
|
): Preference.PreferenceItem.ListPreference<Int> {
|
||||||
val value by exhPreferences.exhAutoUpdateFrequency().collectAsState()
|
val value by unsortedPreferences.exhAutoUpdateFrequency().collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
preference = exhPreferences.exhAutoUpdateFrequency(),
|
preference = unsortedPreferences.exhAutoUpdateFrequency(),
|
||||||
title = stringResource(SYMR.strings.time_between_batches),
|
title = stringResource(SYMR.strings.time_between_batches),
|
||||||
subtitle = if (value == 0) {
|
subtitle = if (value == 0) {
|
||||||
stringResource(SYMR.strings.time_between_batches_summary_1, stringResource(MR.strings.app_name))
|
stringResource(SYMR.strings.time_between_batches_summary_1, stringResource(MR.strings.app_name))
|
||||||
@ -971,12 +971,12 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun autoUpdateRequirements(
|
fun autoUpdateRequirements(
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.MultiSelectListPreference {
|
): Preference.PreferenceItem.MultiSelectListPreference {
|
||||||
val value by exhPreferences.exhAutoUpdateRequirements().collectAsState()
|
val value by unsortedPreferences.exhAutoUpdateRequirements().collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
return Preference.PreferenceItem.MultiSelectListPreference(
|
return Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
preference = exhPreferences.exhAutoUpdateRequirements(),
|
preference = unsortedPreferences.exhAutoUpdateRequirements(),
|
||||||
title = stringResource(SYMR.strings.auto_update_restrictions),
|
title = stringResource(SYMR.strings.auto_update_restrictions),
|
||||||
subtitle = remember(value) {
|
subtitle = remember(value) {
|
||||||
context.stringResource(
|
context.stringResource(
|
||||||
@ -1139,7 +1139,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun updaterStatistics(
|
fun updaterStatistics(
|
||||||
exhPreferences: ExhPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata,
|
getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata,
|
||||||
getFlatMetadataById: GetFlatMetadataById,
|
getFlatMetadataById: GetFlatMetadataById,
|
||||||
): Preference.PreferenceItem.TextPreference {
|
): Preference.PreferenceItem.TextPreference {
|
||||||
@ -1150,7 +1150,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
value = withIOContext {
|
value = withIOContext {
|
||||||
try {
|
try {
|
||||||
val stats =
|
val stats =
|
||||||
exhPreferences.exhAutoUpdateStats().get().nullIfBlank()?.let {
|
unsortedPreferences.exhAutoUpdateStats().get().nullIfBlank()?.let {
|
||||||
Json.decodeFromString<EHentaiUpdaterStats>(it)
|
Json.decodeFromString<EHentaiUpdaterStats>(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
@ -37,8 +38,6 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U
|
|||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MARK_DUPLICATE_CHAPTER_READ_EXISTING
|
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MARK_DUPLICATE_CHAPTER_READ_NEW
|
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
@ -58,13 +57,17 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
val getCategories = remember { Injekt.get<GetCategories>() }
|
val getCategories = remember { Injekt.get<GetCategories>() }
|
||||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
||||||
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
||||||
|
// SY -->
|
||||||
|
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
||||||
|
// SY <--
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
|
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
|
||||||
getGlobalUpdateGroup(allCategories, libraryPreferences),
|
getGlobalUpdateGroup(allCategories, libraryPreferences),
|
||||||
getBehaviorGroup(libraryPreferences),
|
getChapterSwipeActionsGroup(libraryPreferences),
|
||||||
// SY -->
|
// SY -->
|
||||||
getSortingCategory(LocalNavigator.currentOrThrow, libraryPreferences),
|
getSortingCategory(LocalNavigator.currentOrThrow, libraryPreferences),
|
||||||
|
getMigrationCategory(unsortedPreferences),
|
||||||
// SY <--
|
// SY <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -225,16 +228,20 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
preference = libraryPreferences.newShowUpdatesCount(),
|
preference = libraryPreferences.newShowUpdatesCount(),
|
||||||
title = stringResource(MR.strings.pref_library_update_show_tab_badge),
|
title = stringResource(MR.strings.pref_library_update_show_tab_badge),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = libraryPreferences.markDuplicateChapterRead(),
|
||||||
|
title = stringResource(MR.strings.pref_mark_duplicate_chapter_read),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getBehaviorGroup(
|
private fun getChapterSwipeActionsGroup(
|
||||||
libraryPreferences: LibraryPreferences,
|
libraryPreferences: LibraryPreferences,
|
||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.pref_behavior),
|
title = stringResource(MR.strings.pref_chapter_swipe),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
preference = libraryPreferences.swipeToStartAction(),
|
preference = libraryPreferences.swipeToStartAction(),
|
||||||
@ -264,16 +271,6 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
title = stringResource(MR.strings.pref_chapter_swipe_end),
|
title = stringResource(MR.strings.pref_chapter_swipe_end),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
|
||||||
preference = libraryPreferences.markDuplicateReadChapterAsRead(),
|
|
||||||
entries = persistentMapOf(
|
|
||||||
MARK_DUPLICATE_CHAPTER_READ_EXISTING to
|
|
||||||
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_existing),
|
|
||||||
MARK_DUPLICATE_CHAPTER_READ_NEW to
|
|
||||||
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_new),
|
|
||||||
),
|
|
||||||
title = stringResource(MR.strings.pref_mark_duplicate_read_chapter_read),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -295,5 +292,22 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getMigrationCategory(unsortedPreferences: UnsortedPreferences): Preference.PreferenceGroup {
|
||||||
|
val skipPreMigration by unsortedPreferences.skipPreMigration().collectAsState()
|
||||||
|
val migrationSources by unsortedPreferences.migrationSources().collectAsState()
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
stringResource(SYMR.strings.migration),
|
||||||
|
enabled = skipPreMigration || migrationSources.isNotEmpty(),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = unsortedPreferences.skipPreMigration(),
|
||||||
|
title = stringResource(SYMR.strings.skip_pre_migration),
|
||||||
|
subtitle = stringResource(SYMR.strings.pref_skip_pre_migration_summary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ import logcat.LogPriority
|
|||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
@ -64,13 +65,14 @@ object SettingsMangadexScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val sourcePreferences: SourcePreferences = remember { Injekt.get() }
|
val sourcePreferences: SourcePreferences = remember { Injekt.get() }
|
||||||
|
val unsortedPreferences: UnsortedPreferences = remember { Injekt.get() }
|
||||||
val trackPreferences: TrackPreferences = remember { Injekt.get() }
|
val trackPreferences: TrackPreferences = remember { Injekt.get() }
|
||||||
val mdex = remember { MdUtil.getEnabledMangaDex(sourcePreferences) } ?: return emptyList()
|
val mdex = remember { MdUtil.getEnabledMangaDex(unsortedPreferences, sourcePreferences) } ?: return emptyList()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
loginPreference(mdex, trackPreferences),
|
loginPreference(mdex, trackPreferences),
|
||||||
preferredMangaDexId(sourcePreferences),
|
preferredMangaDexId(unsortedPreferences, sourcePreferences),
|
||||||
syncMangaDexIntoThis(sourcePreferences),
|
syncMangaDexIntoThis(unsortedPreferences),
|
||||||
syncLibraryToMangaDex(),
|
syncLibraryToMangaDex(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -172,10 +174,11 @@ object SettingsMangadexScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun preferredMangaDexId(
|
fun preferredMangaDexId(
|
||||||
|
unsortedPreferences: UnsortedPreferences,
|
||||||
sourcePreferences: SourcePreferences,
|
sourcePreferences: SourcePreferences,
|
||||||
): Preference.PreferenceItem.ListPreference<String> {
|
): Preference.PreferenceItem.ListPreference<String> {
|
||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
preference = sourcePreferences.preferredMangaDexId(),
|
preference = unsortedPreferences.preferredMangaDexId(),
|
||||||
title = stringResource(SYMR.strings.mangadex_preffered_source),
|
title = stringResource(SYMR.strings.mangadex_preffered_source),
|
||||||
subtitle = stringResource(SYMR.strings.mangadex_preffered_source_summary),
|
subtitle = stringResource(SYMR.strings.mangadex_preffered_source_summary),
|
||||||
entries = MdUtil.getEnabledMangaDexs(sourcePreferences)
|
entries = MdUtil.getEnabledMangaDexs(sourcePreferences)
|
||||||
@ -247,7 +250,7 @@ object SettingsMangadexScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun syncMangaDexIntoThis(sourcePreferences: SourcePreferences): Preference.PreferenceItem.TextPreference {
|
fun syncMangaDexIntoThis(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.TextPreference {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var dialogOpen by remember { mutableStateOf(false) }
|
var dialogOpen by remember { mutableStateOf(false) }
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
@ -255,7 +258,7 @@ object SettingsMangadexScreen : SearchableSettings {
|
|||||||
onDismissRequest = { dialogOpen = false },
|
onDismissRequest = { dialogOpen = false },
|
||||||
onSelectionConfirmed = { items ->
|
onSelectionConfirmed = { items ->
|
||||||
dialogOpen = false
|
dialogOpen = false
|
||||||
sourcePreferences.mangadexSyncToLibraryIndexes().set(
|
unsortedPreferences.mangadexSyncToLibraryIndexes().set(
|
||||||
List(items.size) { index -> (index + 1).toString() }.toSet(),
|
List(items.size) { index -> (index + 1).toString() }.toSet(),
|
||||||
)
|
)
|
||||||
LibraryUpdateJob.startNow(
|
LibraryUpdateJob.startNow(
|
||||||
|
@ -175,7 +175,9 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||||
valueRange = 1..15,
|
max = 15,
|
||||||
|
min = 1,
|
||||||
|
steps = 13,
|
||||||
title = stringResource(MR.strings.pref_flash_duration),
|
title = stringResource(MR.strings.pref_flash_duration),
|
||||||
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||||
enabled = flashPageState,
|
enabled = flashPageState,
|
||||||
@ -186,7 +188,9 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
value = flashInterval,
|
value = flashInterval,
|
||||||
valueRange = 1..10,
|
max = 10,
|
||||||
|
min = 1,
|
||||||
|
steps = 8,
|
||||||
title = stringResource(MR.strings.pref_flash_page_interval),
|
title = stringResource(MR.strings.pref_flash_page_interval),
|
||||||
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||||
enabled = flashPageState,
|
enabled = flashPageState,
|
||||||
@ -227,6 +231,13 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
preference = readerPreferences.skipDupe(),
|
preference = readerPreferences.skipDupe(),
|
||||||
title = stringResource(MR.strings.pref_skip_dupe_chapters),
|
title = stringResource(MR.strings.pref_skip_dupe_chapters),
|
||||||
),
|
),
|
||||||
|
// SY -->
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = readerPreferences.markReadDupe(),
|
||||||
|
title = stringResource(SYMR.strings.pref_mark_read_dupe_chapters),
|
||||||
|
subtitle = stringResource(SYMR.strings.pref_mark_read_dupe_chapters_summary),
|
||||||
|
),
|
||||||
|
// SY <--
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = readerPreferences.alwaysShowChapterTransition(),
|
preference = readerPreferences.alwaysShowChapterTransition(),
|
||||||
title = stringResource(MR.strings.pref_always_show_chapter_transition),
|
title = stringResource(MR.strings.pref_always_show_chapter_transition),
|
||||||
@ -378,9 +389,8 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
value = webtoonSidePadding,
|
value = webtoonSidePadding,
|
||||||
valueRange = ReaderPreferences.let {
|
max = ReaderPreferences.WEBTOON_PADDING_MAX,
|
||||||
it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX
|
min = ReaderPreferences.WEBTOON_PADDING_MIN,
|
||||||
},
|
|
||||||
title = stringResource(MR.strings.pref_webtoon_side_padding),
|
title = stringResource(MR.strings.pref_webtoon_side_padding),
|
||||||
subtitle = numberFormat.format(webtoonSidePadding / 100f),
|
subtitle = numberFormat.format(webtoonSidePadding / 100f),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
|
@ -30,11 +30,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.autofill.ContentType
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.semantics.contentType
|
|
||||||
import androidx.compose.ui.semantics.semantics
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
@ -231,9 +228,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
text = {
|
text = {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.semantics { contentType = ContentType.Username + ContentType.EmailAddress },
|
|
||||||
value = username,
|
value = username,
|
||||||
onValueChange = { username = it },
|
onValueChange = { username = it },
|
||||||
label = { Text(text = stringResource(uNameStringRes)) },
|
label = { Text(text = stringResource(uNameStringRes)) },
|
||||||
@ -244,9 +239,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
|
|
||||||
var hidePassword by remember { mutableStateOf(true) }
|
var hidePassword by remember { mutableStateOf(true) }
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.semantics { contentType = ContentType.Password },
|
|
||||||
value = password,
|
value = password,
|
||||||
onValueChange = { password = it },
|
onValueChange = { password = it },
|
||||||
label = { Text(text = stringResource(MR.strings.password)) },
|
label = { Text(text = stringResource(MR.strings.password)) },
|
||||||
@ -295,7 +288,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
val id = if (processing) MR.strings.logging_in else MR.strings.login
|
val id = if (processing) MR.strings.loading else MR.strings.login
|
||||||
Text(text = stringResource(id))
|
Text(text = stringResource(id))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -6,7 +6,7 @@ import androidx.compose.ui.Modifier
|
|||||||
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 com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
||||||
import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent
|
import com.mikepenz.aboutlibraries.ui.compose.m3.util.htmlReadyLicenseContent
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen.advanced
|
package eu.kanade.presentation.more.settings.screen.advanced
|
||||||
|
|
||||||
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.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@ -14,7 +12,6 @@ import androidx.compose.material.icons.outlined.SelectAll
|
|||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -45,16 +42,16 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.launchUI
|
import tachiyomi.core.common.util.lang.launchUI
|
||||||
import tachiyomi.core.common.util.lang.toLong
|
|
||||||
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
||||||
import tachiyomi.data.Database
|
import tachiyomi.data.Database
|
||||||
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
|
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.domain.source.model.SourceWithCount
|
import tachiyomi.domain.source.model.SourceWithCount
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.i18n.sy.SYMR
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
@ -76,45 +73,18 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
is ClearDatabaseScreenModel.State.Loading -> LoadingScreen()
|
is ClearDatabaseScreenModel.State.Loading -> LoadingScreen()
|
||||||
is ClearDatabaseScreenModel.State.Ready -> {
|
is ClearDatabaseScreenModel.State.Ready -> {
|
||||||
if (s.showConfirmation) {
|
if (s.showConfirmation) {
|
||||||
|
// SY -->
|
||||||
var keepReadManga by remember { mutableStateOf(true) }
|
var keepReadManga by remember { mutableStateOf(true) }
|
||||||
|
// SY <--
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title = {
|
|
||||||
Text(text = stringResource(MR.strings.are_you_sure))
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(MR.strings.clear_database_text))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(MR.strings.clear_db_exclude_read),
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
Switch(
|
|
||||||
checked = keepReadManga,
|
|
||||||
onCheckedChange = { keepReadManga = it },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (!keepReadManga) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(MR.strings.clear_database_history_warning),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismissRequest = model::hideConfirmation,
|
onDismissRequest = model::hideConfirmation,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launchUI {
|
scope.launchUI {
|
||||||
|
// SY -->
|
||||||
model.removeMangaBySourceId(keepReadManga)
|
model.removeMangaBySourceId(keepReadManga)
|
||||||
|
// SY <--
|
||||||
model.clearSelection()
|
model.clearSelection()
|
||||||
model.hideConfirmation()
|
model.hideConfirmation()
|
||||||
context.toast(MR.strings.clear_database_completed)
|
context.toast(MR.strings.clear_database_completed)
|
||||||
@ -129,6 +99,20 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
Text(text = stringResource(MR.strings.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
text = {
|
||||||
|
// SY -->
|
||||||
|
Column {
|
||||||
|
// SY <--
|
||||||
|
Text(text = stringResource(MR.strings.clear_database_confirmation))
|
||||||
|
// SY -->
|
||||||
|
LabeledCheckbox(
|
||||||
|
label = stringResource(SYMR.strings.clear_db_exclude_read),
|
||||||
|
checked = keepReadManga,
|
||||||
|
onCheckedChange = { keepReadManga = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,9 +224,15 @@ private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenMod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun removeMangaBySourceId(keepReadManga: Boolean) = withNonCancellableContext {
|
suspend fun removeMangaBySourceId(/* SY --> */keepReadManga: Boolean /* SY <-- */) = withNonCancellableContext {
|
||||||
val state = state.value as? State.Ready ?: return@withNonCancellableContext
|
val state = state.value as? State.Ready ?: return@withNonCancellableContext
|
||||||
database.mangasQueries.deleteNonLibraryManga(state.selection, keepReadManga.toLong())
|
// SY -->
|
||||||
|
if (keepReadManga) {
|
||||||
|
database.mangasQueries.deleteMangasNotInLibraryAndNotReadBySourceIds(state.selection)
|
||||||
|
} else {
|
||||||
|
database.mangasQueries.deleteMangasNotInLibraryBySourceIds(state.selection)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
database.historyQueries.removeResettedHistory()
|
database.historyQueries.removeResettedHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -85,8 +86,7 @@ internal fun BasePreferenceWidget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier = composed {
|
||||||
internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier {
|
|
||||||
var highlightFlag by remember { mutableStateOf(false) }
|
var highlightFlag by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (highlighted) {
|
if (highlighted) {
|
||||||
@ -116,7 +116,7 @@ internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier {
|
|||||||
},
|
},
|
||||||
label = "highlight",
|
label = "highlight",
|
||||||
)
|
)
|
||||||
return this.background(color = highlight)
|
Modifier.background(color = highlight)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val TrailingWidgetBuffer = 16.dp
|
internal val TrailingWidgetBuffer = 16.dp
|
||||||
|
@ -31,7 +31,6 @@ fun EditTextPreferenceWidget(
|
|||||||
subtitle: String?,
|
subtitle: String?,
|
||||||
icon: ImageVector?,
|
icon: ImageVector?,
|
||||||
value: String,
|
value: String,
|
||||||
widget: @Composable (() -> Unit)? = null,
|
|
||||||
onConfirm: suspend (String) -> Boolean,
|
onConfirm: suspend (String) -> Boolean,
|
||||||
) {
|
) {
|
||||||
var isDialogShown by remember { mutableStateOf(false) }
|
var isDialogShown by remember { mutableStateOf(false) }
|
||||||
@ -40,7 +39,6 @@ fun EditTextPreferenceWidget(
|
|||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle?.format(value),
|
subtitle = subtitle?.format(value),
|
||||||
icon = icon,
|
icon = icon,
|
||||||
widget = widget,
|
|
||||||
onPreferenceClick = { isDialogShown = true },
|
onPreferenceClick = { isDialogShown = true },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,10 +37,11 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
if (customBrightness) {
|
if (customBrightness) {
|
||||||
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
|
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = customBrightnessValue,
|
|
||||||
valueRange = -75..100,
|
|
||||||
steps = 0,
|
|
||||||
label = stringResource(MR.strings.pref_custom_brightness),
|
label = stringResource(MR.strings.pref_custom_brightness),
|
||||||
|
min = -75,
|
||||||
|
max = 100,
|
||||||
|
value = customBrightnessValue,
|
||||||
|
valueText = customBrightnessValue.toString(),
|
||||||
onChange = { screenModel.preferences.customBrightnessValue().set(it) },
|
onChange = { screenModel.preferences.customBrightnessValue().set(it) },
|
||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
@ -54,10 +55,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
if (colorFilter) {
|
if (colorFilter) {
|
||||||
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
|
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = colorFilterValue.red,
|
|
||||||
valueRange = 0..255,
|
|
||||||
steps = 0,
|
|
||||||
label = stringResource(MR.strings.color_filter_r_value),
|
label = stringResource(MR.strings.color_filter_r_value),
|
||||||
|
max = 255,
|
||||||
|
value = colorFilterValue.red,
|
||||||
|
valueText = colorFilterValue.red.toString(),
|
||||||
onChange = { newRValue ->
|
onChange = { newRValue ->
|
||||||
screenModel.preferences.colorFilterValue().getAndSet {
|
screenModel.preferences.colorFilterValue().getAndSet {
|
||||||
getColorValue(it, newRValue, RED_MASK, 16)
|
getColorValue(it, newRValue, RED_MASK, 16)
|
||||||
@ -66,10 +67,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = colorFilterValue.green,
|
|
||||||
valueRange = 0..255,
|
|
||||||
steps = 0,
|
|
||||||
label = stringResource(MR.strings.color_filter_g_value),
|
label = stringResource(MR.strings.color_filter_g_value),
|
||||||
|
max = 255,
|
||||||
|
value = colorFilterValue.green,
|
||||||
|
valueText = colorFilterValue.green.toString(),
|
||||||
onChange = { newGValue ->
|
onChange = { newGValue ->
|
||||||
screenModel.preferences.colorFilterValue().getAndSet {
|
screenModel.preferences.colorFilterValue().getAndSet {
|
||||||
getColorValue(it, newGValue, GREEN_MASK, 8)
|
getColorValue(it, newGValue, GREEN_MASK, 8)
|
||||||
@ -78,10 +79,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = colorFilterValue.blue,
|
|
||||||
valueRange = 0..255,
|
|
||||||
steps = 0,
|
|
||||||
label = stringResource(MR.strings.color_filter_b_value),
|
label = stringResource(MR.strings.color_filter_b_value),
|
||||||
|
max = 255,
|
||||||
|
value = colorFilterValue.blue,
|
||||||
|
valueText = colorFilterValue.blue.toString(),
|
||||||
onChange = { newBValue ->
|
onChange = { newBValue ->
|
||||||
screenModel.preferences.colorFilterValue().getAndSet {
|
screenModel.preferences.colorFilterValue().getAndSet {
|
||||||
getColorValue(it, newBValue, BLUE_MASK, 0)
|
getColorValue(it, newBValue, BLUE_MASK, 0)
|
||||||
@ -90,10 +91,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = colorFilterValue.alpha,
|
|
||||||
valueRange = 0..255,
|
|
||||||
steps = 0,
|
|
||||||
label = stringResource(MR.strings.color_filter_a_value),
|
label = stringResource(MR.strings.color_filter_a_value),
|
||||||
|
max = 255,
|
||||||
|
value = colorFilterValue.alpha,
|
||||||
|
valueText = colorFilterValue.alpha.toString(),
|
||||||
onChange = { newAValue ->
|
onChange = { newAValue ->
|
||||||
screenModel.preferences.colorFilterValue().getAndSet {
|
screenModel.preferences.colorFilterValue().getAndSet {
|
||||||
getColorValue(it, newAValue, ALPHA_MASK, 24)
|
getColorValue(it, newAValue, ALPHA_MASK, 24)
|
||||||
|
@ -120,20 +120,24 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
if (flashPageState) {
|
if (flashPageState) {
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||||
valueRange = 1..15,
|
|
||||||
label = stringResource(MR.strings.pref_flash_duration),
|
label = stringResource(MR.strings.pref_flash_duration),
|
||||||
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||||
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
|
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
|
||||||
|
min = 1,
|
||||||
|
max = 15,
|
||||||
|
steps = 13,
|
||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = flashInterval,
|
value = flashInterval,
|
||||||
valueRange = 1..10,
|
|
||||||
label = stringResource(MR.strings.pref_flash_page_interval),
|
label = stringResource(MR.strings.pref_flash_page_interval),
|
||||||
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||||
onChange = {
|
onChange = {
|
||||||
flashIntervalPref.set(it)
|
flashIntervalPref.set(it)
|
||||||
},
|
},
|
||||||
|
min = 1,
|
||||||
|
max = 10,
|
||||||
|
steps = 8,
|
||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
SettingsChipRow(MR.strings.pref_flash_with) {
|
SettingsChipRow(MR.strings.pref_flash_with) {
|
||||||
|
@ -193,9 +193,10 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
|
|||||||
|
|
||||||
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = webtoonSidePadding,
|
|
||||||
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
|
|
||||||
label = stringResource(MR.strings.pref_webtoon_side_padding),
|
label = stringResource(MR.strings.pref_webtoon_side_padding),
|
||||||
|
min = ReaderPreferences.WEBTOON_PADDING_MIN,
|
||||||
|
max = ReaderPreferences.WEBTOON_PADDING_MAX,
|
||||||
|
value = webtoonSidePadding,
|
||||||
valueText = numberFormat.format(webtoonSidePadding / 100f),
|
valueText = numberFormat.format(webtoonSidePadding / 100f),
|
||||||
onChange = {
|
onChange = {
|
||||||
screenModel.preferences.webtoonSidePadding().set(it)
|
screenModel.preferences.webtoonSidePadding().set(it)
|
||||||
|
@ -304,15 +304,6 @@ private fun SearchResultItem(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (trackSearch.authors.isNotEmpty() || trackSearch.artists.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = (trackSearch.authors + trackSearch.artists).distinct().joinToString(),
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (type.isNotBlank()) {
|
if (type.isNotBlank()) {
|
||||||
SearchResultItemDetails(
|
SearchResultItemDetails(
|
||||||
title = stringResource(MR.strings.track_type),
|
title = stringResource(MR.strings.track_type),
|
||||||
|
@ -5,11 +5,8 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
||||||
@ -76,8 +73,6 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val formatter: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
|
||||||
|
|
||||||
private fun randTrackSearch() = TrackSearch().let {
|
private fun randTrackSearch() = TrackSearch().let {
|
||||||
it.id = Random.nextLong()
|
it.id = Random.nextLong()
|
||||||
it.manga_id = Random.nextLong()
|
it.manga_id = Random.nextLong()
|
||||||
@ -93,17 +88,11 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
it.finished_reading_date = 0L
|
it.finished_reading_date = 0L
|
||||||
it.tracking_url = "https://example.com/tracker-example"
|
it.tracking_url = "https://example.com/tracker-example"
|
||||||
it.cover_url = "https://example.com/cover.png"
|
it.cover_url = "https://example.com/cover.png"
|
||||||
it.start_date = formatter.format(Date.from(Instant.now().minus((1L..365).random(), ChronoUnit.DAYS)))
|
it.start_date = Instant.now().minus((1L..365).random(), ChronoUnit.DAYS).toString()
|
||||||
it.summary = lorem((0..40).random()).joinToString()
|
it.summary = lorem((0..40).random()).joinToString()
|
||||||
it.publishing_status = if (Random.nextBoolean()) "Finished" else ""
|
|
||||||
it.publishing_type = if (Random.nextBoolean()) "Oneshot" else ""
|
|
||||||
it.artists = randomNames()
|
|
||||||
it.authors = randomNames()
|
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun randomNames(): List<String> = (0..(0..3).random()).map { lorem((3..5).random()).joinToString() }
|
|
||||||
|
|
||||||
private fun lorem(words: Int): Sequence<String> =
|
private fun lorem(words: Int): Sequence<String> =
|
||||||
LoremIpsum(words).values
|
LoremIpsum(words).values
|
||||||
}
|
}
|
||||||
|
@ -60,9 +60,7 @@ fun UpdateScreen(
|
|||||||
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
|
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onOpenChapter: (UpdatesItem) -> Unit,
|
onOpenChapter: (UpdatesItem) -> Unit,
|
||||||
) {
|
) {
|
||||||
BackHandler(enabled = state.selectionMode) {
|
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
|
||||||
onSelectAll(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
|
@ -1,46 +1,12 @@
|
|||||||
package eu.kanade.presentation.util
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.activity.BackEventCompat
|
|
||||||
import androidx.activity.compose.PredictiveBackHandler
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||||
import androidx.compose.animation.ContentTransform
|
import androidx.compose.animation.ContentTransform
|
||||||
import androidx.compose.animation.EnterTransition
|
|
||||||
import androidx.compose.animation.ExitTransition
|
|
||||||
import androidx.compose.animation.SizeTransform
|
|
||||||
import androidx.compose.animation.core.AnimationSpec
|
|
||||||
import androidx.compose.animation.core.SeekableTransitionState
|
|
||||||
import androidx.compose.animation.core.Spring
|
|
||||||
import androidx.compose.animation.core.animate
|
|
||||||
import androidx.compose.animation.core.rememberTransition
|
|
||||||
import androidx.compose.animation.core.spring
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.Saver
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.snapshotFlow
|
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.util.lerp
|
|
||||||
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.ScreenModelStore
|
import cafe.adriel.voyager.core.model.ScreenModelStore
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
@ -49,28 +15,18 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
|||||||
import cafe.adriel.voyager.core.stack.StackEvent
|
import cafe.adriel.voyager.core.stack.StackEvent
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.transitions.ScreenTransitionContent
|
import cafe.adriel.voyager.transitions.ScreenTransitionContent
|
||||||
import eu.kanade.tachiyomi.util.view.getWindowRadius
|
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.CoroutineName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.dropWhile
|
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import soup.compose.material.motion.animation.materialSharedAxisXIn
|
import soup.compose.material.motion.animation.materialSharedAxisX
|
||||||
import soup.compose.material.motion.animation.materialSharedAxisXOut
|
|
||||||
import soup.compose.material.motion.animation.rememberSlideDistance
|
import soup.compose.material.motion.animation.rememberSlideDistance
|
||||||
import tachiyomi.presentation.core.util.PredictiveBack
|
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For invoking back press to the parent activity
|
* For invoking back press to the parent activity
|
||||||
*/
|
*/
|
||||||
@SuppressLint("ComposeCompositionLocalUsage")
|
|
||||||
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
||||||
|
|
||||||
interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
|
interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
|
||||||
@ -103,278 +59,39 @@ interface AssistContentScreen {
|
|||||||
fun onProvideAssistUrl(): String?
|
fun onProvideAssistUrl(): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(InternalVoyagerApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultNavigatorScreenTransition(
|
fun DefaultNavigatorScreenTransition(
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val screenCandidatesToDispose = rememberSaveable(saver = screenCandidatesToDisposeSaver()) {
|
val slideDistance = rememberSlideDistance()
|
||||||
mutableStateOf(emptySet())
|
|
||||||
}
|
|
||||||
val currentScreens = navigator.items
|
|
||||||
DisposableEffect(currentScreens) {
|
|
||||||
onDispose {
|
|
||||||
val newScreenKeys = navigator.items.map { it.key }
|
|
||||||
screenCandidatesToDispose.value += currentScreens.filter { it.key !in newScreenKeys }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val slideDistance = rememberSlideDistance(slideDistance = 30.dp)
|
|
||||||
ScreenTransition(
|
ScreenTransition(
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
|
transition = {
|
||||||
|
materialSharedAxisX(
|
||||||
|
forward = navigator.lastEvent != StackEvent.Pop,
|
||||||
|
slideDistance = slideDistance,
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enterTransition = {
|
|
||||||
if (it == SwipeEdge.Right) {
|
|
||||||
materialSharedAxisXIn(forward = false, slideDistance = slideDistance)
|
|
||||||
} else {
|
|
||||||
materialSharedAxisXIn(forward = true, slideDistance = slideDistance)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
exitTransition = {
|
|
||||||
if (it == SwipeEdge.Right) {
|
|
||||||
materialSharedAxisXOut(forward = false, slideDistance = slideDistance)
|
|
||||||
} else {
|
|
||||||
materialSharedAxisXOut(forward = true, slideDistance = slideDistance)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
popEnterTransition = {
|
|
||||||
if (it == SwipeEdge.Right) {
|
|
||||||
materialSharedAxisXIn(forward = true, slideDistance = slideDistance)
|
|
||||||
} else {
|
|
||||||
materialSharedAxisXIn(forward = false, slideDistance = slideDistance)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
popExitTransition = {
|
|
||||||
if (it == SwipeEdge.Right) {
|
|
||||||
materialSharedAxisXOut(forward = true, slideDistance = slideDistance)
|
|
||||||
} else {
|
|
||||||
materialSharedAxisXOut(forward = false, slideDistance = slideDistance)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
content = { screen ->
|
|
||||||
if (this.transition.targetState == this.transition.currentState) {
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
val newScreens = navigator.items.map { it.key }
|
|
||||||
val screensToDispose = screenCandidatesToDispose.value.filterNot { it.key in newScreens }
|
|
||||||
if (screensToDispose.isNotEmpty()) {
|
|
||||||
screensToDispose.forEach { navigator.dispose(it) }
|
|
||||||
navigator.clearEvent()
|
|
||||||
}
|
|
||||||
screenCandidatesToDispose.value = emptySet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.Content()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class SwipeEdge {
|
|
||||||
Unknown,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum class AnimationType {
|
|
||||||
Pop,
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScreenTransition(
|
fun ScreenTransition(
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
|
transition: AnimatedContentTransitionScope<Screen>.() -> ContentTransform,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enterTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> EnterTransition = { fadeIn() },
|
|
||||||
exitTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> ExitTransition = { fadeOut() },
|
|
||||||
popEnterTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> EnterTransition = enterTransition,
|
|
||||||
popExitTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> ExitTransition = exitTransition,
|
|
||||||
sizeTransform: (AnimatedContentTransitionScope<Screen>.() -> SizeTransform?)? = null,
|
|
||||||
flingAnimationSpec: () -> AnimationSpec<Float> = { spring(stiffness = Spring.StiffnessLow) },
|
|
||||||
content: ScreenTransitionContent = { it.Content() },
|
content: ScreenTransitionContent = { it.Content() },
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
AnimatedContent(
|
||||||
val viewConfig = LocalViewConfiguration.current
|
targetState = navigator.lastItem,
|
||||||
val scope = rememberCoroutineScope()
|
transitionSpec = transition,
|
||||||
val state = remember {
|
|
||||||
ScreenTransitionState(
|
|
||||||
navigator = navigator,
|
|
||||||
scope = scope,
|
|
||||||
flingAnimationSpec = flingAnimationSpec(),
|
|
||||||
windowCornerRadius = view.getWindowRadius().toFloat(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val transitionState = remember { SeekableTransitionState(navigator.lastItem) }
|
|
||||||
val transition = rememberTransition(transitionState = transitionState)
|
|
||||||
|
|
||||||
if (state.isPredictiveBack || state.isAnimating) {
|
|
||||||
LaunchedEffect(state.progress) {
|
|
||||||
if (!state.isPredictiveBack) return@LaunchedEffect
|
|
||||||
val previousEntry = navigator.items.getOrNull(navigator.size - 2)
|
|
||||||
if (previousEntry != null) {
|
|
||||||
transitionState.seekTo(fraction = state.progress, targetState = previousEntry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LaunchedEffect(navigator) {
|
|
||||||
snapshotFlow { navigator.lastItem }
|
|
||||||
.collect {
|
|
||||||
state.cancelCancelAnimation()
|
|
||||||
if (it != transitionState.currentState) {
|
|
||||||
transitionState.animateTo(it)
|
|
||||||
} else {
|
|
||||||
transitionState.snapTo(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PredictiveBackHandler(enabled = navigator.canPop) { backEvent ->
|
|
||||||
state.cancelCancelAnimation()
|
|
||||||
var startOffset: Offset? = null
|
|
||||||
backEvent
|
|
||||||
.dropWhile {
|
|
||||||
if (startOffset == null) startOffset = Offset(it.touchX, it.touchY)
|
|
||||||
if (state.isAnimating) return@dropWhile true
|
|
||||||
// Touch slop check
|
|
||||||
val diff = Offset(it.touchX, it.touchY) - startOffset!!
|
|
||||||
diff.x.absoluteValue < viewConfig.touchSlop && diff.y.absoluteValue < viewConfig.touchSlop
|
|
||||||
}
|
|
||||||
.onCompletion {
|
|
||||||
if (it == null) {
|
|
||||||
state.finish()
|
|
||||||
} else {
|
|
||||||
state.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.collect {
|
|
||||||
state.setPredictiveBackProgress(
|
|
||||||
progress = it.progress,
|
|
||||||
swipeEdge = when (it.swipeEdge) {
|
|
||||||
BackEventCompat.EDGE_LEFT -> SwipeEdge.Left
|
|
||||||
BackEventCompat.EDGE_RIGHT -> SwipeEdge.Right
|
|
||||||
else -> SwipeEdge.Unknown
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.AnimatedContent(
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
transitionSpec = {
|
label = "transition",
|
||||||
val pop = navigator.lastEvent == StackEvent.Pop || state.isPredictiveBack
|
) { screen ->
|
||||||
ContentTransform(
|
navigator.saveableState("transition", screen) {
|
||||||
targetContentEnter = if (pop) {
|
content(screen)
|
||||||
popEnterTransition(state.swipeEdge)
|
|
||||||
} else {
|
|
||||||
enterTransition(state.swipeEdge)
|
|
||||||
},
|
|
||||||
initialContentExit = if (pop) {
|
|
||||||
popExitTransition(state.swipeEdge)
|
|
||||||
} else {
|
|
||||||
exitTransition(state.swipeEdge)
|
|
||||||
},
|
|
||||||
targetContentZIndex = if (pop) 0f else 1f,
|
|
||||||
sizeTransform = sizeTransform?.invoke(this),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
contentKey = { it.key },
|
|
||||||
) {
|
|
||||||
navigator.saveableState("transition", it) {
|
|
||||||
content(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
|
||||||
private class ScreenTransitionState(
|
|
||||||
private val navigator: Navigator,
|
|
||||||
private val scope: CoroutineScope,
|
|
||||||
private val flingAnimationSpec: AnimationSpec<Float>,
|
|
||||||
windowCornerRadius: Float,
|
|
||||||
) {
|
|
||||||
var isPredictiveBack: Boolean by mutableStateOf(false)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var progress: Float by mutableFloatStateOf(0f)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var swipeEdge: SwipeEdge by mutableStateOf(SwipeEdge.Unknown)
|
|
||||||
private set
|
|
||||||
|
|
||||||
private var animationJob: Pair<Job, AnimationType>? by mutableStateOf(null)
|
|
||||||
|
|
||||||
val isAnimating: Boolean
|
|
||||||
get() = animationJob?.first?.isActive == true
|
|
||||||
|
|
||||||
val windowCornerShape = RoundedCornerShape(windowCornerRadius)
|
|
||||||
|
|
||||||
private fun reset() {
|
|
||||||
this.isPredictiveBack = false
|
|
||||||
this.swipeEdge = SwipeEdge.Unknown
|
|
||||||
this.animationJob = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPredictiveBackProgress(progress: Float, swipeEdge: SwipeEdge) {
|
|
||||||
this.progress = lerp(0f, 0.65f, PredictiveBack.transform(progress))
|
|
||||||
this.swipeEdge = swipeEdge
|
|
||||||
this.isPredictiveBack = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun finish() {
|
|
||||||
if (!isPredictiveBack) {
|
|
||||||
navigator.pop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
animationJob = scope.launch {
|
|
||||||
try {
|
|
||||||
animate(
|
|
||||||
initialValue = progress,
|
|
||||||
targetValue = 1f,
|
|
||||||
animationSpec = flingAnimationSpec,
|
|
||||||
block = { i, _ -> progress = i },
|
|
||||||
)
|
|
||||||
navigator.pop()
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
// Cancelled
|
|
||||||
progress = 0f
|
|
||||||
} finally {
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
} to AnimationType.Pop
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel() {
|
|
||||||
if (!isPredictiveBack) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
animationJob = scope.launch {
|
|
||||||
try {
|
|
||||||
animate(
|
|
||||||
initialValue = progress,
|
|
||||||
targetValue = 0f,
|
|
||||||
animationSpec = flingAnimationSpec,
|
|
||||||
block = { i, _ -> progress = i },
|
|
||||||
)
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
// Cancelled
|
|
||||||
progress = 1f
|
|
||||||
} finally {
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
} to AnimationType.Cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancelCancelAnimation() {
|
|
||||||
if (animationJob?.second == AnimationType.Cancel) {
|
|
||||||
animationJob?.first?.cancel()
|
|
||||||
animationJob = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun screenCandidatesToDisposeSaver(): Saver<MutableState<Set<Screen>>, List<Screen>> {
|
|
||||||
return Saver(
|
|
||||||
save = { it.value.toList() },
|
|
||||||
restore = { mutableStateOf(it.toSet()) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -3,6 +3,7 @@ package eu.kanade.presentation.webview
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.webkit.WebResourceRequest
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebResourceResponse
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -26,6 +27,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.kevinnzou.web.AccompanistWebViewClient
|
import com.kevinnzou.web.AccompanistWebViewClient
|
||||||
@ -37,13 +39,19 @@ import eu.kanade.presentation.components.AppBar
|
|||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.getHtml
|
import eu.kanade.tachiyomi.util.system.getHtml
|
||||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.Request
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WebViewScreenContent(
|
fun WebViewScreenContent(
|
||||||
onNavigateUp: () -> Unit,
|
onNavigateUp: () -> Unit,
|
||||||
@ -57,8 +65,11 @@ fun WebViewScreenContent(
|
|||||||
) {
|
) {
|
||||||
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
||||||
val navigator = rememberWebViewNavigator()
|
val navigator = rememberWebViewNavigator()
|
||||||
|
val context = LocalContext.current
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val network = remember { Injekt.get<NetworkHelper>() }
|
||||||
|
val spoofedPackageName = remember { WebViewUtil.spoofedPackageName(context) }
|
||||||
|
|
||||||
var currentUrl by remember { mutableStateOf(url) }
|
var currentUrl by remember { mutableStateOf(url) }
|
||||||
var showCloudflareHelp by remember { mutableStateOf(false) }
|
var showCloudflareHelp by remember { mutableStateOf(false) }
|
||||||
@ -113,6 +124,40 @@ fun WebViewScreenContent(
|
|||||||
}
|
}
|
||||||
return super.shouldOverrideUrlLoading(view, request)
|
return super.shouldOverrideUrlLoading(view, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun shouldInterceptRequest(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?,
|
||||||
|
): WebResourceResponse? {
|
||||||
|
return try {
|
||||||
|
val internalRequest = Request.Builder().apply {
|
||||||
|
url(request!!.url.toString())
|
||||||
|
request.requestHeaders.forEach { (key, value) ->
|
||||||
|
if (key == "X-Requested-With" && value in setOf(context.packageName, spoofedPackageName)) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
addHeader(key, value)
|
||||||
|
}
|
||||||
|
method(request.method, null)
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
val response = network.nonCloudflareClient.newCall(internalRequest).execute()
|
||||||
|
|
||||||
|
val contentType = response.body.contentType()?.let { "${it.type}/${it.subtype}" } ?: "text/html"
|
||||||
|
val contentEncoding = response.body.contentType()?.charset()?.name() ?: "utf-8"
|
||||||
|
|
||||||
|
WebResourceResponse(
|
||||||
|
contentType,
|
||||||
|
contentEncoding,
|
||||||
|
response.code,
|
||||||
|
response.message,
|
||||||
|
response.headers.associate { it.first to it.second },
|
||||||
|
response.body.byteStream(),
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
super.shouldInterceptRequest(view, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +328,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
return super.generateFileName(
|
return super.generateFileName(
|
||||||
logLevel,
|
logLevel,
|
||||||
timestamp,
|
timestamp,
|
||||||
) + "-${BuildConfig.BUILD_TYPE}.txt"
|
) + "-${BuildConfig.BUILD_TYPE}.log"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flattener { timeMillis, level, tag, message ->
|
flattener { timeMillis, level, tag, message ->
|
||||||
|
@ -80,7 +80,7 @@ class BackupNotifier(private val context: Context) {
|
|||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_share_24dp,
|
R.drawable.ic_share_24dp,
|
||||||
context.stringResource(MR.strings.action_share),
|
context.stringResource(MR.strings.action_share),
|
||||||
NotificationReceiver.shareBackupPendingActivity(context, file.uri),
|
NotificationReceiver.shareBackupPendingBroadcast(context, file.uri),
|
||||||
)
|
)
|
||||||
|
|
||||||
show(Notifications.ID_BACKUP_COMPLETE)
|
show(Notifications.ID_BACKUP_COMPLETE)
|
||||||
|
@ -118,15 +118,13 @@ class MangaBackupCreator(
|
|||||||
private fun Manga.toBackupManga(/* SY --> */customMangaInfo: CustomMangaInfo?/* SY <-- */) =
|
private fun Manga.toBackupManga(/* SY --> */customMangaInfo: CustomMangaInfo?/* SY <-- */) =
|
||||||
BackupManga(
|
BackupManga(
|
||||||
url = this.url,
|
url = this.url,
|
||||||
// SY -->
|
title = this.title,
|
||||||
title = this.ogTitle,
|
artist = this.artist,
|
||||||
artist = this.ogArtist,
|
author = this.author,
|
||||||
author = this.ogAuthor,
|
description = this.description,
|
||||||
description = this.ogDescription,
|
genre = this.genre.orEmpty(),
|
||||||
genre = this.ogGenre.orEmpty(),
|
status = this.status.toInt(),
|
||||||
status = this.ogStatus.toInt(),
|
thumbnailUrl = this.thumbnailUrl,
|
||||||
thumbnailUrl = this.ogThumbnailUrl,
|
|
||||||
// SY <--
|
|
||||||
favorite = this.favorite,
|
favorite = this.favorite,
|
||||||
source = this.source,
|
source = this.source,
|
||||||
dateAdded = this.dateAdded,
|
dateAdded = this.dateAdded,
|
||||||
@ -137,7 +135,6 @@ private fun Manga.toBackupManga(/* SY --> */customMangaInfo: CustomMangaInfo?/*
|
|||||||
lastModifiedAt = this.lastModifiedAt,
|
lastModifiedAt = this.lastModifiedAt,
|
||||||
favoriteModifiedAt = this.favoriteModifiedAt,
|
favoriteModifiedAt = this.favoriteModifiedAt,
|
||||||
version = this.version,
|
version = this.version,
|
||||||
notes = this.notes,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
).also { backupManga ->
|
).also { backupManga ->
|
||||||
customMangaInfo?.let {
|
customMangaInfo?.let {
|
||||||
|
@ -38,10 +38,8 @@ data class BackupManga(
|
|||||||
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
||||||
@ProtoNumber(106) var lastModifiedAt: Long = 0,
|
@ProtoNumber(106) var lastModifiedAt: Long = 0,
|
||||||
@ProtoNumber(107) var favoriteModifiedAt: Long? = null,
|
@ProtoNumber(107) var favoriteModifiedAt: Long? = null,
|
||||||
// Mihon values start here
|
|
||||||
@ProtoNumber(108) var excludedScanlators: List<String> = emptyList(),
|
@ProtoNumber(108) var excludedScanlators: List<String> = emptyList(),
|
||||||
@ProtoNumber(109) var version: Long = 0,
|
@ProtoNumber(109) var version: Long = 0,
|
||||||
@ProtoNumber(110) var notes: String = "",
|
|
||||||
|
|
||||||
// SY specific values
|
// SY specific values
|
||||||
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
|
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
|
||||||
@ -79,7 +77,6 @@ data class BackupManga(
|
|||||||
lastModifiedAt = this@BackupManga.lastModifiedAt,
|
lastModifiedAt = this@BackupManga.lastModifiedAt,
|
||||||
favoriteModifiedAt = this@BackupManga.favoriteModifiedAt,
|
favoriteModifiedAt = this@BackupManga.favoriteModifiedAt,
|
||||||
version = this@BackupManga.version,
|
version = this@BackupManga.version,
|
||||||
notes = this@BackupManga.notes,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,15 +139,13 @@ class MangaRestorer(
|
|||||||
mangasQueries.update(
|
mangasQueries.update(
|
||||||
source = manga.source,
|
source = manga.source,
|
||||||
url = manga.url,
|
url = manga.url,
|
||||||
// SY -->
|
artist = manga.artist,
|
||||||
artist = manga.ogArtist,
|
author = manga.author,
|
||||||
author = manga.ogAuthor,
|
description = manga.description,
|
||||||
description = manga.ogDescription,
|
genre = manga.genre?.joinToString(separator = ", "),
|
||||||
genre = manga.ogGenre?.joinToString(separator = ", "),
|
title = manga.title,
|
||||||
title = manga.ogTitle,
|
status = manga.status,
|
||||||
status = manga.ogStatus,
|
thumbnailUrl = manga.thumbnailUrl,
|
||||||
thumbnailUrl = manga.ogThumbnailUrl,
|
|
||||||
// SY <--
|
|
||||||
favorite = manga.favorite,
|
favorite = manga.favorite,
|
||||||
lastUpdate = manga.lastUpdate,
|
lastUpdate = manga.lastUpdate,
|
||||||
nextUpdate = null,
|
nextUpdate = null,
|
||||||
@ -161,7 +159,6 @@ class MangaRestorer(
|
|||||||
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
|
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
|
||||||
version = manga.version,
|
version = manga.version,
|
||||||
isSyncing = 1,
|
isSyncing = 1,
|
||||||
notes = manga.notes,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return manga
|
return manga
|
||||||
@ -277,15 +274,13 @@ class MangaRestorer(
|
|||||||
mangasQueries.insert(
|
mangasQueries.insert(
|
||||||
source = manga.source,
|
source = manga.source,
|
||||||
url = manga.url,
|
url = manga.url,
|
||||||
// SY -->
|
artist = manga.artist,
|
||||||
artist = manga.ogArtist,
|
author = manga.author,
|
||||||
author = manga.ogAuthor,
|
description = manga.description,
|
||||||
description = manga.ogDescription,
|
genre = manga.genre,
|
||||||
genre = manga.ogGenre,
|
title = manga.title,
|
||||||
title = manga.ogTitle,
|
status = manga.status,
|
||||||
status = manga.ogStatus,
|
thumbnailUrl = manga.thumbnailUrl,
|
||||||
thumbnailUrl = manga.ogThumbnailUrl,
|
|
||||||
// SY <--
|
|
||||||
favorite = manga.favorite,
|
favorite = manga.favorite,
|
||||||
lastUpdate = manga.lastUpdate,
|
lastUpdate = manga.lastUpdate,
|
||||||
nextUpdate = 0L,
|
nextUpdate = 0L,
|
||||||
@ -297,7 +292,6 @@ class MangaRestorer(
|
|||||||
dateAdded = manga.dateAdded,
|
dateAdded = manga.dateAdded,
|
||||||
updateStrategy = manga.updateStrategy,
|
updateStrategy = manga.updateStrategy,
|
||||||
version = manga.version,
|
version = manga.version,
|
||||||
notes = manga.notes,
|
|
||||||
)
|
)
|
||||||
mangasQueries.selectLastInsertedRowId()
|
mangasQueries.selectLastInsertedRowId()
|
||||||
}
|
}
|
||||||
@ -462,7 +456,6 @@ class MangaRestorer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore the categories from Json
|
* Restore the categories from Json
|
||||||
*
|
*
|
||||||
|
@ -27,9 +27,6 @@ interface Chapter : SChapter, Serializable {
|
|||||||
var version: Long
|
var version: Long
|
||||||
}
|
}
|
||||||
|
|
||||||
val Chapter.isRecognizedNumber: Boolean
|
|
||||||
get() = chapter_number >= 0f
|
|
||||||
|
|
||||||
fun Chapter.toDomainChapter(): DomainChapter? {
|
fun Chapter.toDomainChapter(): DomainChapter? {
|
||||||
if (id == null || manga_id == null) return null
|
if (id == null || manga_id == null) return null
|
||||||
return DomainChapter(
|
return DomainChapter(
|
||||||
|
@ -307,41 +307,6 @@ class DownloadCache(
|
|||||||
notifyChanges()
|
notifyChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renames a manga in this cache.
|
|
||||||
*
|
|
||||||
* @param manga the manga being renamed.
|
|
||||||
* @param mangaUniFile the manga's new directory.
|
|
||||||
* @param newTitle the manga's new title.
|
|
||||||
*/
|
|
||||||
suspend fun renameManga(manga: Manga, mangaUniFile: UniFile, newTitle: String) {
|
|
||||||
rootDownloadsDirMutex.withLock {
|
|
||||||
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
|
|
||||||
val oldMangaDirName = provider.getMangaDirName(/* SY --> */ manga.ogTitle /* SY <-- */)
|
|
||||||
var oldChapterDirs: MutableSet<String>? = null
|
|
||||||
// Save the old name's cached chapter dirs
|
|
||||||
if (sourceDir.mangaDirs.containsKey(oldMangaDirName)) {
|
|
||||||
oldChapterDirs = sourceDir.mangaDirs[oldMangaDirName]?.chapterDirs
|
|
||||||
sourceDir.mangaDirs -= oldMangaDirName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve/create the cached manga directory for new name
|
|
||||||
val newMangaDirName = provider.getMangaDirName(newTitle)
|
|
||||||
var mangaDir = sourceDir.mangaDirs[newMangaDirName]
|
|
||||||
if (mangaDir == null) {
|
|
||||||
mangaDir = MangaDirectory(mangaUniFile)
|
|
||||||
sourceDir.mangaDirs += newMangaDirName to mangaDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the old chapters to new name's cache
|
|
||||||
if (!oldChapterDirs.isNullOrEmpty()) {
|
|
||||||
mangaDir.chapterDirs += oldChapterDirs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun removeSource(source: Source) {
|
suspend fun removeSource(source: Source) {
|
||||||
rootDownloadsDirMutex.withLock {
|
rootDownloadsDirMutex.withLock {
|
||||||
rootDownloadsDir.sourceDirs -= source.id
|
rootDownloadsDir.sourceDirs -= source.id
|
||||||
|
@ -179,7 +179,7 @@ class DownloadManager(
|
|||||||
|
|
||||||
return files.sortedBy { it.name }
|
return files.sortedBy { it.name }
|
||||||
.mapIndexed { i, file ->
|
.mapIndexed { i, file ->
|
||||||
Page(i, uri = file.uri).apply { status = Page.State.Ready }
|
Page(i, uri = file.uri).apply { status = Page.State.READY }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +291,6 @@ class DownloadManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return the list of all manga folders
|
* return the list of all manga folders
|
||||||
*/
|
*/
|
||||||
@ -317,13 +316,10 @@ class DownloadManager(
|
|||||||
|
|
||||||
if (removeNonFavorite && !manga.favorite) {
|
if (removeNonFavorite && !manga.favorite) {
|
||||||
val mangaFolder = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source)
|
val mangaFolder = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source)
|
||||||
.getOrNull()
|
cleaned += 1 + mangaFolder.listFiles().orEmpty().size
|
||||||
if (mangaFolder != null) {
|
mangaFolder.delete()
|
||||||
cleaned += 1 + mangaFolder.listFiles().orEmpty().size
|
cache.removeManga(manga)
|
||||||
mangaFolder.delete()
|
return cleaned
|
||||||
cache.removeManga(manga)
|
|
||||||
return cleaned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val filesWithNoChapter = provider.findUnmatchedChapterDirs(allChapters, manga, source)
|
val filesWithNoChapter = provider.findUnmatchedChapterDirs(allChapters, manga, source)
|
||||||
@ -340,8 +336,8 @@ class DownloadManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cache.getDownloadCount(manga) == 0) {
|
if (cache.getDownloadCount(manga) == 0) {
|
||||||
val mangaFolder = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source).getOrNull()
|
val mangaFolder = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source)
|
||||||
if (mangaFolder != null && !mangaFolder.listFiles().isNullOrEmpty()) {
|
if (!mangaFolder.listFiles().isNullOrEmpty()) {
|
||||||
mangaFolder.delete()
|
mangaFolder.delete()
|
||||||
cache.removeManga(manga)
|
cache.removeManga(manga)
|
||||||
} else {
|
} else {
|
||||||
@ -399,38 +395,6 @@ class DownloadManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renames manga download folder
|
|
||||||
*
|
|
||||||
* @param manga the manga
|
|
||||||
* @param newTitle the new manga title.
|
|
||||||
*/
|
|
||||||
suspend fun renameManga(manga: Manga, newTitle: String) {
|
|
||||||
val source = sourceManager.getOrStub(manga.source)
|
|
||||||
val oldFolder = provider.findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return
|
|
||||||
val newName = provider.getMangaDirName(newTitle)
|
|
||||||
|
|
||||||
if (oldFolder.name == newName) return
|
|
||||||
|
|
||||||
// just to be safe, don't allow downloads for this manga while renaming it
|
|
||||||
downloader.removeFromQueue(manga)
|
|
||||||
|
|
||||||
val capitalizationChanged = oldFolder.name.equals(newName, ignoreCase = true)
|
|
||||||
if (capitalizationChanged) {
|
|
||||||
val tempName = newName + Downloader.TMP_DIR_SUFFIX
|
|
||||||
if (!oldFolder.renameTo(tempName)) {
|
|
||||||
logcat(LogPriority.ERROR) { "Failed to rename manga download folder: ${oldFolder.name}" }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldFolder.renameTo(newName)) {
|
|
||||||
cache.renameManga(manga, oldFolder, newTitle)
|
|
||||||
} else {
|
|
||||||
logcat(LogPriority.ERROR) { "Failed to rename manga download folder: ${oldFolder.name}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renames an already downloaded chapter
|
* Renames an already downloaded chapter
|
||||||
*
|
*
|
||||||
@ -441,10 +405,7 @@ class DownloadManager(
|
|||||||
*/
|
*/
|
||||||
suspend fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
|
suspend fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
|
||||||
val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator)
|
val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator)
|
||||||
val mangaDir = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source).getOrElse { e ->
|
val mangaDir = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source)
|
||||||
logcat(LogPriority.ERROR, e) { "Manga download folder doesn't exist. Skipping renaming after source sync" }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assume there's only 1 version of the chapter name formats present
|
// Assume there's only 1 version of the chapter name formats present
|
||||||
val oldDownload = oldNames.asSequence()
|
val oldDownload = oldNames.asSequence()
|
||||||
|
@ -14,7 +14,6 @@ import tachiyomi.domain.storage.service.StorageManager
|
|||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to provide the directories where the downloads should be saved.
|
* This class is used to provide the directories where the downloads should be saved.
|
||||||
@ -36,36 +35,20 @@ class DownloadProvider(
|
|||||||
* @param mangaTitle the title of the manga to query.
|
* @param mangaTitle the title of the manga to query.
|
||||||
* @param source the source of the manga.
|
* @param source the source of the manga.
|
||||||
*/
|
*/
|
||||||
internal fun getMangaDir(mangaTitle: String, source: Source): Result<UniFile> {
|
internal fun getMangaDir(mangaTitle: String, source: Source): UniFile {
|
||||||
val downloadsDir = downloadsDir
|
try {
|
||||||
if (downloadsDir == null) {
|
return downloadsDir!!
|
||||||
logcat(LogPriority.ERROR) { "Failed to create download directory" }
|
.createDirectory(getSourceDirName(source))!!
|
||||||
return Result.failure(
|
.createDirectory(getMangaDirName(mangaTitle))!!
|
||||||
IOException(context.stringResource(MR.strings.storage_failed_to_create_download_directory)),
|
} catch (e: Throwable) {
|
||||||
|
logcat(LogPriority.ERROR, e) { "Invalid download directory" }
|
||||||
|
throw Exception(
|
||||||
|
context.stringResource(
|
||||||
|
MR.strings.invalid_location,
|
||||||
|
downloadsDir?.displayablePath ?: "",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourceDirName = getSourceDirName(source)
|
|
||||||
val sourceDir = downloadsDir.createDirectory(sourceDirName)
|
|
||||||
if (sourceDir == null) {
|
|
||||||
val displayablePath = downloadsDir.displayablePath + "/$sourceDirName"
|
|
||||||
logcat(LogPriority.ERROR) { "Failed to create source download directory: $displayablePath" }
|
|
||||||
return Result.failure(
|
|
||||||
IOException(context.stringResource(MR.strings.storage_failed_to_create_directory, displayablePath)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val mangaDirName = getMangaDirName(mangaTitle)
|
|
||||||
val mangaDir = sourceDir.createDirectory(mangaDirName)
|
|
||||||
if (mangaDir == null) {
|
|
||||||
val displayablePath = sourceDir.displayablePath + "/$mangaDirName"
|
|
||||||
logcat(LogPriority.ERROR) { "Failed to create manga download directory: $displayablePath" }
|
|
||||||
return Result.failure(
|
|
||||||
IOException(context.stringResource(MR.strings.storage_failed_to_create_directory, displayablePath)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.success(mangaDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,7 +103,6 @@ class DownloadProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of all files in manga directory
|
* Returns a list of all files in manga directory
|
||||||
*
|
*
|
||||||
|
@ -327,11 +327,7 @@ class Downloader(
|
|||||||
* @param download the chapter to be downloaded.
|
* @param download the chapter to be downloaded.
|
||||||
*/
|
*/
|
||||||
private suspend fun downloadChapter(download: Download) {
|
private suspend fun downloadChapter(download: Download) {
|
||||||
val mangaDir = provider.getMangaDir(/* SY --> */ download.manga.ogTitle /* SY <-- */, download.source).getOrElse { e ->
|
val mangaDir = provider.getMangaDir(/* SY --> */ download.manga.ogTitle /* SY <-- */, download.source)
|
||||||
download.status = Download.State.ERROR
|
|
||||||
notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir)
|
val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir)
|
||||||
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
|
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
|
||||||
@ -383,11 +379,11 @@ class Downloader(
|
|||||||
flow {
|
flow {
|
||||||
// Fetch image URL if necessary
|
// Fetch image URL if necessary
|
||||||
if (page.imageUrl.isNullOrEmpty()) {
|
if (page.imageUrl.isNullOrEmpty()) {
|
||||||
page.status = Page.State.LoadPage
|
page.status = Page.State.LOAD_PAGE
|
||||||
try {
|
try {
|
||||||
page.imageUrl = download.source.getImageUrl(page)
|
page.imageUrl = download.source.getImageUrl(page)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
page.status = Page.State.Error(e)
|
page.status = Page.State.ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,12 +469,12 @@ class Downloader(
|
|||||||
|
|
||||||
page.uri = file.uri
|
page.uri = file.uri
|
||||||
page.progress = 100
|
page.progress = 100
|
||||||
page.status = Page.State.Ready
|
page.status = Page.State.READY
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
// Mark this page as error and allow to download the remaining
|
// Mark this page as error and allow to download the remaining
|
||||||
page.progress = 0
|
page.progress = 0
|
||||||
page.status = Page.State.Error(e)
|
page.status = Page.State.ERROR
|
||||||
notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)
|
notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -498,7 +494,7 @@ class Downloader(
|
|||||||
filename: String,
|
filename: String,
|
||||||
dataSaver: DataSaver,
|
dataSaver: DataSaver,
|
||||||
): UniFile {
|
): UniFile {
|
||||||
page.status = Page.State.DownloadImage
|
page.status = Page.State.DOWNLOAD_IMAGE
|
||||||
page.progress = 0
|
page.progress = 0
|
||||||
return flow {
|
return flow {
|
||||||
val response = source.getImage(page, dataSaver)
|
val response = source.getImage(page, dataSaver)
|
||||||
|
@ -29,7 +29,7 @@ data class Download(
|
|||||||
get() = pages?.sumOf(Page::progress) ?: 0
|
get() = pages?.sumOf(Page::progress) ?: 0
|
||||||
|
|
||||||
val downloadedImages: Int
|
val downloadedImages: Int
|
||||||
get() = pages?.count { it.status == Page.State.Ready } ?: 0
|
get() = pages?.count { it.status == Page.State.READY } ?: 0
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private val _statusFlow = MutableStateFlow(State.NOT_DOWNLOADED)
|
private val _statusFlow = MutableStateFlow(State.NOT_DOWNLOADED)
|
||||||
|
@ -23,7 +23,6 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
|||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.copyFrom
|
import eu.kanade.domain.manga.model.copyFrom
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.domain.sync.SyncPreferences
|
import eu.kanade.domain.sync.SyncPreferences
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
import eu.kanade.domain.track.model.toDomainTrack
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
@ -65,6 +64,7 @@ import tachiyomi.core.common.i18n.stringResource
|
|||||||
import tachiyomi.core.common.preference.getAndSet
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
@ -101,12 +101,9 @@ import java.time.Instant
|
|||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.concurrent.atomics.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.concurrent.atomics.AtomicInt
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
|
||||||
import kotlin.concurrent.atomics.incrementAndFetch
|
|
||||||
|
|
||||||
@OptIn(ExperimentalAtomicApi::class)
|
|
||||||
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
CoroutineWorker(context, workerParams) {
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
@ -346,7 +343,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
*/
|
*/
|
||||||
private suspend fun updateChapterList() {
|
private suspend fun updateChapterList() {
|
||||||
val semaphore = Semaphore(5)
|
val semaphore = Semaphore(5)
|
||||||
val progressCount = AtomicInt(0)
|
val progressCount = AtomicInteger(0)
|
||||||
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
||||||
val newUpdates = CopyOnWriteArrayList<Pair<Manga, Array<Chapter>>>()
|
val newUpdates = CopyOnWriteArrayList<Pair<Manga, Array<Chapter>>>()
|
||||||
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
||||||
@ -411,7 +408,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
if (chaptersToDownload.isNotEmpty()) {
|
if (chaptersToDownload.isNotEmpty()) {
|
||||||
downloadChapters(manga, chaptersToDownload)
|
downloadChapters(manga, chaptersToDownload)
|
||||||
hasDownloads.store(true)
|
hasDownloads.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
libraryPreferences.newUpdatesCount().getAndSet { it + newChapters.size }
|
libraryPreferences.newUpdatesCount().getAndSet { it + newChapters.size }
|
||||||
@ -444,7 +441,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
if (newUpdates.isNotEmpty()) {
|
if (newUpdates.isNotEmpty()) {
|
||||||
notifier.showUpdateNotifications(newUpdates)
|
notifier.showUpdateNotifications(newUpdates)
|
||||||
if (hasDownloads.load()) {
|
if (hasDownloads.get()) {
|
||||||
downloadManager.startDownloads()
|
downloadManager.startDownloads()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -510,7 +507,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
private suspend fun updateCovers() {
|
private suspend fun updateCovers() {
|
||||||
val semaphore = Semaphore(5)
|
val semaphore = Semaphore(5)
|
||||||
val progressCount = AtomicInt(0)
|
val progressCount = AtomicInteger(0)
|
||||||
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
@ -558,12 +555,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* filter all follows from Mangadex and only add reading or rereading manga to library
|
* filter all follows from Mangadex and only add reading or rereading manga to library
|
||||||
*/
|
*/
|
||||||
private suspend fun syncFollows() = coroutineScope {
|
private suspend fun syncFollows() = coroutineScope {
|
||||||
val preferences = Injekt.get<SourcePreferences>()
|
val preferences = Injekt.get<UnsortedPreferences>()
|
||||||
var count = 0
|
var count = 0
|
||||||
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager = sourceManager)
|
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager = sourceManager)
|
||||||
?: return@coroutineScope
|
?: return@coroutineScope
|
||||||
@ -586,7 +582,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
var dbManga = getManga.await(networkManga.url, mangaDex.id)
|
var dbManga = getManga.await(networkManga.url, mangaDex.id)
|
||||||
|
|
||||||
if (dbManga == null) {
|
if (dbManga == null) {
|
||||||
dbManga = networkToLocalManga(
|
dbManga = networkToLocalManga.await(
|
||||||
Manga.create().copy(
|
Manga.create().copy(
|
||||||
url = networkManga.url,
|
url = networkManga.url,
|
||||||
ogTitle = networkManga.title,
|
ogTitle = networkManga.title,
|
||||||
@ -645,7 +641,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
private suspend fun withUpdateNotification(
|
private suspend fun withUpdateNotification(
|
||||||
updatingManga: CopyOnWriteArrayList<Manga>,
|
updatingManga: CopyOnWriteArrayList<Manga>,
|
||||||
completed: AtomicInt,
|
completed: AtomicInteger,
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
block: suspend () -> Unit,
|
block: suspend () -> Unit,
|
||||||
) = coroutineScope {
|
) = coroutineScope {
|
||||||
@ -654,7 +650,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
updatingManga.add(manga)
|
updatingManga.add(manga)
|
||||||
notifier.showProgressNotification(
|
notifier.showProgressNotification(
|
||||||
updatingManga,
|
updatingManga,
|
||||||
completed.load(),
|
completed.get(),
|
||||||
mangaToUpdate.size,
|
mangaToUpdate.size,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -663,10 +659,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
ensureActive()
|
ensureActive()
|
||||||
|
|
||||||
updatingManga.remove(manga)
|
updatingManga.remove(manga)
|
||||||
completed.incrementAndFetch()
|
completed.getAndIncrement()
|
||||||
notifier.showProgressNotification(
|
notifier.showProgressNotification(
|
||||||
updatingManga,
|
updatingManga,
|
||||||
completed.load(),
|
completed.get(),
|
||||||
mangaToUpdate.size,
|
mangaToUpdate.size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -735,7 +731,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
private const val KEY_TARGET = "target"
|
private const val KEY_TARGET = "target"
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key for group to update.
|
* Key for group to update.
|
||||||
*/
|
*/
|
||||||
|
@ -37,11 +37,8 @@ import tachiyomi.domain.source.service.SourceManager
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import kotlin.concurrent.atomics.AtomicInt
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
|
||||||
import kotlin.concurrent.atomics.fetchAndIncrement
|
|
||||||
|
|
||||||
@OptIn(ExperimentalAtomicApi::class)
|
|
||||||
class MetadataUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
class MetadataUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
CoroutineWorker(context, workerParams) {
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
@ -100,7 +97,7 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
|
|||||||
|
|
||||||
private suspend fun updateMetadata() {
|
private suspend fun updateMetadata() {
|
||||||
val semaphore = Semaphore(5)
|
val semaphore = Semaphore(5)
|
||||||
val progressCount = AtomicInt(0)
|
val progressCount = AtomicInteger(0)
|
||||||
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
@ -145,7 +142,7 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
|
|||||||
|
|
||||||
private suspend fun withUpdateNotification(
|
private suspend fun withUpdateNotification(
|
||||||
updatingManga: CopyOnWriteArrayList<Manga>,
|
updatingManga: CopyOnWriteArrayList<Manga>,
|
||||||
completed: AtomicInt,
|
completed: AtomicInteger,
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
block: suspend () -> Unit,
|
block: suspend () -> Unit,
|
||||||
) = coroutineScope {
|
) = coroutineScope {
|
||||||
@ -154,7 +151,7 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
|
|||||||
updatingManga.add(manga)
|
updatingManga.add(manga)
|
||||||
notifier.showProgressNotification(
|
notifier.showProgressNotification(
|
||||||
updatingManga,
|
updatingManga,
|
||||||
completed.load(),
|
completed.get(),
|
||||||
mangaToUpdate.size,
|
mangaToUpdate.size,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -163,10 +160,10 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
|
|||||||
ensureActive()
|
ensureActive()
|
||||||
|
|
||||||
updatingManga.remove(manga)
|
updatingManga.remove(manga)
|
||||||
completed.fetchAndIncrement()
|
completed.getAndIncrement()
|
||||||
notifier.showProgressNotification(
|
notifier.showProgressNotification(
|
||||||
updatingManga,
|
updatingManga,
|
||||||
completed.load(),
|
completed.get(),
|
||||||
mangaToUpdate.size,
|
mangaToUpdate.size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -602,17 +602,18 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns [PendingIntent] that directly launches a share activity for a backup file.
|
* Returns [PendingIntent] that starts a share activity for a backup file.
|
||||||
*
|
*
|
||||||
* @param context context of application
|
* @param context context of application
|
||||||
* @param uri uri of backup file
|
* @param uri uri of backup file
|
||||||
* @return [PendingIntent]
|
* @return [PendingIntent]
|
||||||
*/
|
*/
|
||||||
internal fun shareBackupPendingActivity(context: Context, uri: Uri): PendingIntent {
|
internal fun shareBackupPendingBroadcast(context: Context, uri: Uri): PendingIntent {
|
||||||
val intent = uri.toShareIntent(context, "application/x-protobuf+gzip").apply {
|
val intent = Intent(context, NotificationReceiver::class.java).apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
action = ACTION_SHARE_BACKUP
|
||||||
|
putExtra(EXTRA_URI, uri)
|
||||||
}
|
}
|
||||||
return PendingIntent.getActivity(
|
return PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
intent,
|
intent,
|
||||||
|
@ -144,19 +144,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|Page (perPage: 50) {
|
|Page (perPage: 50) {
|
||||||
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
||||||
|id
|
|id
|
||||||
|staff {
|
|
||||||
|edges {
|
|
||||||
|role
|
|
||||||
|id
|
|
||||||
|node {
|
|
||||||
|name {
|
|
||||||
|full
|
|
||||||
|userPreferred
|
|
||||||
|native
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|title {
|
|title {
|
||||||
|userPreferred
|
|userPreferred
|
||||||
|}
|
|}
|
||||||
@ -237,19 +224,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|month
|
|month
|
||||||
|day
|
|day
|
||||||
|}
|
|}
|
||||||
|staff {
|
|
||||||
|edges {
|
|
||||||
|role
|
|
||||||
|id
|
|
||||||
|node {
|
|
||||||
|name {
|
|
||||||
|full
|
|
||||||
|userPreferred
|
|
||||||
|native
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|}
|
||||||
|}
|
|}
|
||||||
|}
|
|}
|
||||||
|
@ -19,7 +19,6 @@ data class ALManga(
|
|||||||
val startDateFuzzy: Long,
|
val startDateFuzzy: Long,
|
||||||
val totalChapters: Long,
|
val totalChapters: Long,
|
||||||
val averageScore: Int,
|
val averageScore: Int,
|
||||||
val staff: ALStaff,
|
|
||||||
) {
|
) {
|
||||||
fun toTrack() = TrackSearch.create(TrackerManager.ANILIST).apply {
|
fun toTrack() = TrackSearch.create(TrackerManager.ANILIST).apply {
|
||||||
remote_id = remoteId
|
remote_id = remoteId
|
||||||
@ -39,11 +38,6 @@ data class ALManga(
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
staff.edges.forEach {
|
|
||||||
val name = it.node.name() ?: return@forEach
|
|
||||||
if ("Story" in it.role) authors += name
|
|
||||||
if ("Art" in it.role) artists += name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,24 @@ data class ALMangaMetadataData(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class ALMangaMetadataMedia(
|
data class ALMangaMetadataMedia(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val title: ALStaffName,
|
val title: ALItemTitle,
|
||||||
val coverImage: ItemCover,
|
val coverImage: ItemCover,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val staff: ALStaff,
|
val staff: ALStaff,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaff(
|
||||||
|
val edges: List<ALStaffEdge>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaffEdge(
|
||||||
|
val role: String,
|
||||||
|
val node: ALStaffNode,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaffNode(
|
||||||
|
val name: ALItemTitle,
|
||||||
|
)
|
||||||
|
@ -13,7 +13,6 @@ data class ALSearchItem(
|
|||||||
val startDate: ALFuzzyDate,
|
val startDate: ALFuzzyDate,
|
||||||
val chapters: Long?,
|
val chapters: Long?,
|
||||||
val averageScore: Int?,
|
val averageScore: Int?,
|
||||||
val staff: ALStaff,
|
|
||||||
) {
|
) {
|
||||||
fun toALManga(): ALManga = ALManga(
|
fun toALManga(): ALManga = ALManga(
|
||||||
remoteId = id,
|
remoteId = id,
|
||||||
@ -25,7 +24,6 @@ data class ALSearchItem(
|
|||||||
startDateFuzzy = startDate.toEpochMilli(),
|
startDateFuzzy = startDate.toEpochMilli(),
|
||||||
totalChapters = chapters ?: 0,
|
totalChapters = chapters ?: 0,
|
||||||
averageScore = averageScore ?: -1,
|
averageScore = averageScore ?: -1,
|
||||||
staff = staff,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,31 +36,3 @@ data class ALItemTitle(
|
|||||||
data class ItemCover(
|
data class ItemCover(
|
||||||
val large: String,
|
val large: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ALStaff(
|
|
||||||
val edges: List<ALEdge>,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ALEdge(
|
|
||||||
val role: String,
|
|
||||||
val id: Int,
|
|
||||||
val node: ALStaffNode,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ALStaffNode(
|
|
||||||
val name: ALStaffName,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ALStaffName(
|
|
||||||
val userPreferred: String? = null,
|
|
||||||
val native: String? = null,
|
|
||||||
val full: String? = null,
|
|
||||||
) {
|
|
||||||
operator fun invoke(): String? {
|
|
||||||
return userPreferred ?: full ?: native
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -108,7 +108,6 @@ class BangumiApi(
|
|||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs<BGMSearchResult>()
|
.parseAs<BGMSearchResult>()
|
||||||
.data
|
.data
|
||||||
.filter { it.platform == null || it.platform == "漫画" }
|
|
||||||
.map { it.toTrackSearch(trackId) }
|
.map { it.toTrackSearch(trackId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ data class BGMSubject(
|
|||||||
val volumes: Long = 0,
|
val volumes: Long = 0,
|
||||||
val eps: Long = 0,
|
val eps: Long = 0,
|
||||||
val rating: BGMSubjectRating?,
|
val rating: BGMSubjectRating?,
|
||||||
val platform: String?,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val infobox: List<Infobox> = emptyList(),
|
val infobox: List<Infobox> = emptyList(),
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -18,6 +18,8 @@ import tachiyomi.core.common.util.lang.withIOContext
|
|||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class MdList(id: Long) : BaseTracker(id, "MDList") {
|
class MdList(id: Long) : BaseTracker(id, "MDList") {
|
||||||
@ -28,7 +30,7 @@ class MdList(id: Long) : BaseTracker(id, "MDList") {
|
|||||||
.toImmutableList()
|
.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mdex by lazy { MdUtil.getEnabledMangaDex() }
|
private val mdex by lazy { MdUtil.getEnabledMangaDex(Injekt.get()) }
|
||||||
|
|
||||||
val interceptor = MangaDexAuthInterceptor(trackPreferences, this)
|
val interceptor = MangaDexAuthInterceptor(trackPreferences, this)
|
||||||
|
|
||||||
|
@ -34,10 +34,6 @@ class TrackSearch : Track {
|
|||||||
|
|
||||||
override lateinit var tracking_url: String
|
override lateinit var tracking_url: String
|
||||||
|
|
||||||
var authors: List<String> = emptyList()
|
|
||||||
|
|
||||||
var artists: List<String> = emptyList()
|
|
||||||
|
|
||||||
var cover_url: String = ""
|
var cover_url: String = ""
|
||||||
|
|
||||||
var summary: String = ""
|
var summary: String = ""
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.di
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import exh.pref.DelegateSourcePreferences
|
import exh.pref.DelegateSourcePreferences
|
||||||
import exh.source.ExhPreferences
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
|
|
||||||
class SYPreferenceModule(val application: Application) : InjektModule {
|
class SYPreferenceModule(val application: Application) : InjektModule {
|
||||||
@ -15,7 +15,7 @@ class SYPreferenceModule(val application: Application) : InjektModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addSingletonFactory {
|
addSingletonFactory {
|
||||||
ExhPreferences(get())
|
UnsortedPreferences(get())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,18 +12,16 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
|
|||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import kotlin.concurrent.atomics.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base implementation class for extension installer. To be used inside a foreground [Service].
|
* Base implementation class for extension installer. To be used inside a foreground [Service].
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalAtomicApi::class)
|
|
||||||
abstract class Installer(private val service: Service) {
|
abstract class Installer(private val service: Service) {
|
||||||
|
|
||||||
private val extensionManager: ExtensionManager by injectLazy()
|
private val extensionManager: ExtensionManager by injectLazy()
|
||||||
|
|
||||||
private var waitingInstall = AtomicReference<Entry?>(null)
|
private var waitingInstall = AtomicReference<Entry>(null)
|
||||||
private val queue = Collections.synchronizedList(mutableListOf<Entry>())
|
private val queue = Collections.synchronizedList(mutableListOf<Entry>())
|
||||||
|
|
||||||
private val cancelReceiver = object : BroadcastReceiver() {
|
private val cancelReceiver = object : BroadcastReceiver() {
|
||||||
@ -81,7 +79,7 @@ abstract class Installer(private val service: Service) {
|
|||||||
* @see waitingInstall
|
* @see waitingInstall
|
||||||
*/
|
*/
|
||||||
fun continueQueue(resultStep: InstallStep) {
|
fun continueQueue(resultStep: InstallStep) {
|
||||||
val completedEntry = waitingInstall.exchange(null)
|
val completedEntry = waitingInstall.getAndSet(null)
|
||||||
if (completedEntry != null) {
|
if (completedEntry != null) {
|
||||||
extensionManager.updateInstallStep(completedEntry.downloadId, resultStep)
|
extensionManager.updateInstallStep(completedEntry.downloadId, resultStep)
|
||||||
checkQueue()
|
checkQueue()
|
||||||
@ -117,10 +115,10 @@ abstract class Installer(private val service: Service) {
|
|||||||
LocalBroadcastManager.getInstance(service).unregisterReceiver(cancelReceiver)
|
LocalBroadcastManager.getInstance(service).unregisterReceiver(cancelReceiver)
|
||||||
queue.forEach { extensionManager.updateInstallStep(it.downloadId, InstallStep.Error) }
|
queue.forEach { extensionManager.updateInstallStep(it.downloadId, InstallStep.Error) }
|
||||||
queue.clear()
|
queue.clear()
|
||||||
waitingInstall.store(null)
|
waitingInstall.set(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun getActiveEntry(): Entry? = waitingInstall.load()
|
protected fun getActiveEntry(): Entry? = waitingInstall.get()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels queue for the provided download ID if exists.
|
* Cancels queue for the provided download ID if exists.
|
||||||
@ -128,13 +126,13 @@ abstract class Installer(private val service: Service) {
|
|||||||
* @param downloadId Download ID as known by [ExtensionManager]
|
* @param downloadId Download ID as known by [ExtensionManager]
|
||||||
*/
|
*/
|
||||||
private fun cancelQueue(downloadId: Long) {
|
private fun cancelQueue(downloadId: Long) {
|
||||||
val waitingInstall = this.waitingInstall.load()
|
val waitingInstall = this.waitingInstall.get()
|
||||||
val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return
|
val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return
|
||||||
if (cancelEntry(toCancel)) {
|
if (cancelEntry(toCancel)) {
|
||||||
queue.remove(toCancel)
|
queue.remove(toCancel)
|
||||||
if (waitingInstall == toCancel) {
|
if (waitingInstall == toCancel) {
|
||||||
// Currently processing removed entry, continue queue
|
// Currently processing removed entry, continue queue
|
||||||
this.waitingInstall.store(null)
|
this.waitingInstall.set(null)
|
||||||
checkQueue()
|
checkQueue()
|
||||||
}
|
}
|
||||||
extensionManager.updateInstallStep(downloadId, InstallStep.Idle)
|
extensionManager.updateInstallStep(downloadId, InstallStep.Idle)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.source
|
package eu.kanade.tachiyomi.source
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
@ -20,7 +19,6 @@ import exh.source.EH_SOURCE_ID
|
|||||||
import exh.source.EIGHTMUSES_SOURCE_ID
|
import exh.source.EIGHTMUSES_SOURCE_ID
|
||||||
import exh.source.EXH_SOURCE_ID
|
import exh.source.EXH_SOURCE_ID
|
||||||
import exh.source.EnhancedHttpSource
|
import exh.source.EnhancedHttpSource
|
||||||
import exh.source.ExhPreferences
|
|
||||||
import exh.source.HBROWSE_SOURCE_ID
|
import exh.source.HBROWSE_SOURCE_ID
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.source.PURURIN_SOURCE_ID
|
import exh.source.PURURIN_SOURCE_ID
|
||||||
@ -38,6 +36,7 @@ import kotlinx.coroutines.flow.combine
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.source.model.StubSource
|
import tachiyomi.domain.source.model.StubSource
|
||||||
import tachiyomi.domain.source.repository.StubSourceRepository
|
import tachiyomi.domain.source.repository.StubSourceRepository
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
@ -70,15 +69,14 @@ class AndroidSourceManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
private val exhPreferences: ExhPreferences by injectLazy()
|
private val preferences: UnsortedPreferences by injectLazy()
|
||||||
private val sourcePreferences: SourcePreferences by injectLazy()
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
extensionManager.installedExtensionsFlow
|
extensionManager.installedExtensionsFlow
|
||||||
// SY -->
|
// SY -->
|
||||||
.combine(exhPreferences.enableExhentai().changes()) { extensions, enableExhentai ->
|
.combine(preferences.enableExhentai().changes()) { extensions, enableExhentai ->
|
||||||
extensions to enableExhentai
|
extensions to enableExhentai
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -90,7 +88,7 @@ class AndroidSourceManager(
|
|||||||
Injekt.get(),
|
Injekt.get(),
|
||||||
Injekt.get(),
|
Injekt.get(),
|
||||||
// SY -->
|
// SY -->
|
||||||
sourcePreferences.allowLocalSourceHiddenFolders()::get,
|
preferences.allowLocalSourceHiddenFolders()::get,
|
||||||
// SY <--
|
// SY <--
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -44,7 +44,6 @@ import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_WEAK
|
|||||||
import exh.metadata.metadata.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
|
import exh.metadata.metadata.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata.Companion.toGenreString
|
import exh.metadata.metadata.RaisedSearchMetadata.Companion.toGenreString
|
||||||
import exh.metadata.metadata.base.RaisedTag
|
import exh.metadata.metadata.base.RaisedTag
|
||||||
import exh.source.ExhPreferences
|
|
||||||
import exh.ui.login.EhLoginActivity
|
import exh.ui.login.EhLoginActivity
|
||||||
import exh.util.UriFilter
|
import exh.util.UriFilter
|
||||||
import exh.util.UriGroup
|
import exh.util.UriGroup
|
||||||
@ -85,6 +84,7 @@ import org.jsoup.nodes.TextNode
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import tachiyomi.core.common.util.lang.runAsObservable
|
import tachiyomi.core.common.util.lang.runAsObservable
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -117,7 +117,7 @@ class EHentai(
|
|||||||
override val lang = "all"
|
override val lang = "all"
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
private val exhPreferences: ExhPreferences by injectLazy()
|
private val preferences: UnsortedPreferences by injectLazy()
|
||||||
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -476,7 +476,7 @@ class EHentai(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : MangasPage> T.checkValid(): MangasPage =
|
private fun <T : MangasPage> T.checkValid(): MangasPage =
|
||||||
if (exh && mangas.isEmpty() && exhPreferences.igneousVal().get().equals("mystery", true)) {
|
if (exh && mangas.isEmpty() && preferences.igneousVal().get().equals("mystery", true)) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"Invalid igneous cookie, try re-logging or finding a correct one to input in the login menu",
|
"Invalid igneous cookie, try re-logging or finding a correct one to input in the login menu",
|
||||||
)
|
)
|
||||||
@ -879,30 +879,30 @@ class EHentai(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun spPref() = if (exh) {
|
fun spPref() = if (exh) {
|
||||||
exhPreferences.exhSettingsProfile()
|
preferences.exhSettingsProfile()
|
||||||
} else {
|
} else {
|
||||||
exhPreferences.ehSettingsProfile()
|
preferences.ehSettingsProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rawCookies(sp: Int): Map<String, String> {
|
private fun rawCookies(sp: Int): Map<String, String> {
|
||||||
val cookies: MutableMap<String, String> = mutableMapOf()
|
val cookies: MutableMap<String, String> = mutableMapOf()
|
||||||
if (exhPreferences.enableExhentai().get()) {
|
if (preferences.enableExhentai().get()) {
|
||||||
cookies[EhLoginActivity.MEMBER_ID_COOKIE] = exhPreferences.memberIdVal().get()
|
cookies[EhLoginActivity.MEMBER_ID_COOKIE] = preferences.memberIdVal().get()
|
||||||
cookies[EhLoginActivity.PASS_HASH_COOKIE] = exhPreferences.passHashVal().get()
|
cookies[EhLoginActivity.PASS_HASH_COOKIE] = preferences.passHashVal().get()
|
||||||
cookies[EhLoginActivity.IGNEOUS_COOKIE] = exhPreferences.igneousVal().get()
|
cookies[EhLoginActivity.IGNEOUS_COOKIE] = preferences.igneousVal().get()
|
||||||
cookies["sp"] = sp.toString()
|
cookies["sp"] = sp.toString()
|
||||||
|
|
||||||
val sessionKey = exhPreferences.exhSettingsKey().get()
|
val sessionKey = preferences.exhSettingsKey().get()
|
||||||
if (sessionKey.isNotBlank()) {
|
if (sessionKey.isNotBlank()) {
|
||||||
cookies["sk"] = sessionKey
|
cookies["sk"] = sessionKey
|
||||||
}
|
}
|
||||||
|
|
||||||
val sessionCookie = exhPreferences.exhSessionCookie().get()
|
val sessionCookie = preferences.exhSessionCookie().get()
|
||||||
if (sessionCookie.isNotBlank()) {
|
if (sessionCookie.isNotBlank()) {
|
||||||
cookies["s"] = sessionCookie
|
cookies["s"] = sessionCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
val hathPerksCookie = exhPreferences.exhHathPerksCookies().get()
|
val hathPerksCookie = preferences.exhHathPerksCookies().get()
|
||||||
if (hathPerksCookie.isNotBlank()) {
|
if (hathPerksCookie.isNotBlank()) {
|
||||||
cookies["hath_perks"] = hathPerksCookie
|
cookies["hath_perks"] = hathPerksCookie
|
||||||
}
|
}
|
||||||
@ -949,7 +949,7 @@ class EHentai(
|
|||||||
ToplistOptions(),
|
ToplistOptions(),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
AutoCompleteTags(),
|
AutoCompleteTags(),
|
||||||
Watched(isEnabled = exhPreferences.exhWatchedListDefaultState().get()),
|
Watched(isEnabled = preferences.exhWatchedListDefaultState().get()),
|
||||||
GenreGroup(),
|
GenreGroup(),
|
||||||
AdvancedGroup(),
|
AdvancedGroup(),
|
||||||
ReverseFilter(),
|
ReverseFilter(),
|
||||||
@ -1360,7 +1360,7 @@ class EHentai(
|
|||||||
private const val BLANK_PREVIEW_THUMB = "https://$THUMB_DOMAIN/g/$BLANK_THUMB"
|
private const val BLANK_PREVIEW_THUMB = "https://$THUMB_DOMAIN/g/$BLANK_THUMB"
|
||||||
|
|
||||||
private val MATCH_YEAR_REGEX = "^\\d{4}\$".toRegex()
|
private val MATCH_YEAR_REGEX = "^\\d{4}\$".toRegex()
|
||||||
private val MATCH_SEEK_REGEX = "^\\d{2,4}-\\d{1,2}(-\\d{1,2})?".toRegex()
|
private val MATCH_SEEK_REGEX = "^\\d{2,4}-\\d{1,2}".toRegex()
|
||||||
private val MATCH_JUMP_REGEX = "^\\d+(\$|d\$|w\$|m\$|y\$|-\$)".toRegex()
|
private val MATCH_JUMP_REGEX = "^\\d+(\$|d\$|w\$|m\$|y\$|-\$)".toRegex()
|
||||||
|
|
||||||
private const val EH_API_BASE = "https://api.e-hentai.org/api.php"
|
private const val EH_API_BASE = "https://api.e-hentai.org/api.php"
|
||||||
|
@ -170,7 +170,7 @@ class MergedSource : HttpSource() {
|
|||||||
var manga = getManga.await(mangaUrl, mangaSourceId)
|
var manga = getManga.await(mangaUrl, mangaSourceId)
|
||||||
val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId)
|
val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId)
|
||||||
if (manga == null) {
|
if (manga == null) {
|
||||||
val newManga = networkToLocalManga(
|
val newManga = networkToLocalManga.await(
|
||||||
Manga.create().copy(
|
Manga.create().copy(
|
||||||
source = mangaSourceId,
|
source = mangaSourceId,
|
||||||
url = mangaUrl,
|
url = mangaUrl,
|
||||||
|
@ -70,9 +70,7 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) {
|
override suspend fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) {
|
||||||
val body = input.body.string()
|
val json = GALLERY_JSON_REGEX.find(input.body.string())!!.groupValues[1].replace(
|
||||||
val server = MEDIA_SERVER_REGEX.find(body)?.groupValues?.get(1)?.toInt() ?: 1
|
|
||||||
val json = GALLERY_JSON_REGEX.find(body)!!.groupValues[1].replace(
|
|
||||||
UNICODE_ESCAPE_REGEX,
|
UNICODE_ESCAPE_REGEX,
|
||||||
) { it.groupValues[1].toInt(radix = 16).toChar().toString() }
|
) { it.groupValues[1].toInt(radix = 16).toChar().toString() }
|
||||||
val jsonResponse = jsonParser.decodeFromString<JsonResponse>(json)
|
val jsonResponse = jsonParser.decodeFromString<JsonResponse>(json)
|
||||||
@ -86,8 +84,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
|
|
||||||
mediaId = jsonResponse.mediaId
|
mediaId = jsonResponse.mediaId
|
||||||
|
|
||||||
mediaServer = server
|
|
||||||
|
|
||||||
jsonResponse.title?.let { title ->
|
jsonResponse.title?.let { title ->
|
||||||
japaneseTitle = title.japanese
|
japaneseTitle = title.japanese
|
||||||
shortTitle = title.pretty
|
shortTitle = title.pretty
|
||||||
@ -194,24 +190,17 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
return PagePreviewPage(
|
return PagePreviewPage(
|
||||||
page,
|
page,
|
||||||
metadata.pageImageTypes.mapIndexed { index, s ->
|
metadata.pageImageTypes.mapIndexed { index, s ->
|
||||||
PagePreviewInfo(
|
PagePreviewInfo(index + 1, imageUrl = thumbnailUrlFromType(metadata.mediaId!!, index + 1, s)!!)
|
||||||
index + 1,
|
|
||||||
imageUrl = thumbnailUrlFromType(metadata.mediaId!!, metadata.mediaServer ?: 1, index + 1, s)!!,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun thumbnailUrlFromType(
|
private fun thumbnailUrlFromType(mediaId: String, page: Int, t: String) =
|
||||||
mediaId: String,
|
NHentaiSearchMetadata.typeToExtension(t)?.let {
|
||||||
mediaServer: Int,
|
"https://t3.nhentai.net/galleries/$mediaId/${page}t.$it"
|
||||||
page: Int,
|
}
|
||||||
t: String,
|
|
||||||
) = NHentaiSearchMetadata.typeToExtension(t)?.let {
|
|
||||||
"https://t$mediaServer.nhentai.net/galleries/$mediaId/${page}t.$it"
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun fetchPreviewImage(page: PagePreviewInfo, cacheControl: CacheControl?): Response {
|
override suspend fun fetchPreviewImage(page: PagePreviewInfo, cacheControl: CacheControl?): Response {
|
||||||
return client.newCachelessCallWithProgress(
|
return client.newCachelessCallWithProgress(
|
||||||
@ -232,7 +221,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val GALLERY_JSON_REGEX = Regex(".parse\\(\"(.*)\"\\);")
|
private val GALLERY_JSON_REGEX = Regex(".parse\\(\"(.*)\"\\);")
|
||||||
private val MEDIA_SERVER_REGEX = Regex("media_server\\s*:\\s*(\\d+)")
|
|
||||||
private val UNICODE_ESCAPE_REGEX = Regex("\\\\u([0-9a-fA-F]{4})")
|
private val UNICODE_ESCAPE_REGEX = Regex("\\\\u([0-9a-fA-F]{4})")
|
||||||
private const val TITLE_PREF = "Display manga title as:"
|
private const val TITLE_PREF = "Display manga title as:"
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,8 @@ class Pururin(delegate: HttpSource, val context: Context) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val matchingHosts = listOf(
|
override val matchingHosts = listOf(
|
||||||
"pururin.me",
|
"pururin.io",
|
||||||
|
"www.pururin.io",
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun mapUrlToMangaUrl(uri: Uri): String {
|
override suspend fun mapUrlToMangaUrl(uri: Uri): String {
|
||||||
|
@ -7,6 +7,7 @@ import androidx.compose.ui.util.fastAny
|
|||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
|
import eu.kanade.domain.manga.model.toDomainManga
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.presentation.browse.FeedItemUI
|
import eu.kanade.presentation.browse.FeedItemUI
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
@ -30,7 +31,6 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import mihon.domain.manga.model.toDomainManga
|
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
@ -251,7 +251,9 @@ open class FeedScreenModel(
|
|||||||
|
|
||||||
val result = withIOContext {
|
val result = withIOContext {
|
||||||
itemUI.copy(
|
itemUI.copy(
|
||||||
results = networkToLocalManga(page.map { it.toDomainManga(itemUI.source!!.id) }),
|
results = page.map {
|
||||||
|
networkToLocalManga.await(it.toDomainManga(itemUI.source!!.id))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ object MigrationFlags {
|
|||||||
const val CUSTOM_COVER = 0b01000
|
const val CUSTOM_COVER = 0b01000
|
||||||
const val EXTRA = 0b10000
|
const val EXTRA = 0b10000
|
||||||
const val DELETE_CHAPTERS = 0b100000
|
const val DELETE_CHAPTERS = 0b100000
|
||||||
const val NOTES = 0b1000000
|
|
||||||
|
|
||||||
fun hasChapters(value: Int): Boolean {
|
fun hasChapters(value: Int): Boolean {
|
||||||
return value and CHAPTERS != 0
|
return value and CHAPTERS != 0
|
||||||
@ -33,8 +32,4 @@ object MigrationFlags {
|
|||||||
fun hasDeleteChapters(value: Int): Boolean {
|
fun hasDeleteChapters(value: Int): Boolean {
|
||||||
return value and DELETE_CHAPTERS != 0
|
return value and DELETE_CHAPTERS != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasNotes(value: Int): Boolean {
|
|
||||||
return value and NOTES != 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,13 @@ import androidx.compose.runtime.rememberUpdatedState
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.presentation.components.AdaptiveSheet
|
import eu.kanade.presentation.components.AdaptiveSheet
|
||||||
import eu.kanade.tachiyomi.databinding.MigrationBottomSheetBinding
|
import eu.kanade.tachiyomi.databinding.MigrationBottomSheetBinding
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
|
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.util.lang.toLong
|
import tachiyomi.core.common.util.lang.toLong
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ fun MigrationBottomSheetDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MigrationBottomSheetDialogState(private val onStartMigration: State<(extraParam: String?) -> Unit>) {
|
class MigrationBottomSheetDialogState(private val onStartMigration: State<(extraParam: String?) -> Unit>) {
|
||||||
private val preferences: SourcePreferences by injectLazy()
|
private val preferences: UnsortedPreferences by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init general reader preferences.
|
* Init general reader preferences.
|
||||||
@ -59,7 +59,6 @@ class MigrationBottomSheetDialogState(private val onStartMigration: State<(extra
|
|||||||
binding.migCustomCover.isChecked = MigrationFlags.hasCustomCover(flags)
|
binding.migCustomCover.isChecked = MigrationFlags.hasCustomCover(flags)
|
||||||
binding.migExtra.isChecked = MigrationFlags.hasExtra(flags)
|
binding.migExtra.isChecked = MigrationFlags.hasExtra(flags)
|
||||||
binding.migDeleteDownloaded.isChecked = MigrationFlags.hasDeleteChapters(flags)
|
binding.migDeleteDownloaded.isChecked = MigrationFlags.hasDeleteChapters(flags)
|
||||||
binding.migNotes.isChecked = MigrationFlags.hasNotes(flags)
|
|
||||||
|
|
||||||
binding.migChapters.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
binding.migChapters.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
||||||
binding.migCategories.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
binding.migCategories.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
||||||
@ -67,7 +66,6 @@ class MigrationBottomSheetDialogState(private val onStartMigration: State<(extra
|
|||||||
binding.migCustomCover.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
binding.migCustomCover.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
||||||
binding.migExtra.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
binding.migExtra.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
||||||
binding.migDeleteDownloaded.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
binding.migDeleteDownloaded.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
||||||
binding.migNotes.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
|
|
||||||
|
|
||||||
binding.useSmartSearch.bindToPreference(preferences.smartMigration())
|
binding.useSmartSearch.bindToPreference(preferences.smartMigration())
|
||||||
binding.extraSearchParamText.isVisible = false
|
binding.extraSearchParamText.isVisible = false
|
||||||
@ -110,7 +108,6 @@ class MigrationBottomSheetDialogState(private val onStartMigration: State<(extra
|
|||||||
if (binding.migCustomCover.isChecked) flags = flags or MigrationFlags.CUSTOM_COVER
|
if (binding.migCustomCover.isChecked) flags = flags or MigrationFlags.CUSTOM_COVER
|
||||||
if (binding.migExtra.isChecked) flags = flags or MigrationFlags.EXTRA
|
if (binding.migExtra.isChecked) flags = flags or MigrationFlags.EXTRA
|
||||||
if (binding.migDeleteDownloaded.isChecked) flags = flags or MigrationFlags.DELETE_CHAPTERS
|
if (binding.migDeleteDownloaded.isChecked) flags = flags or MigrationFlags.DELETE_CHAPTERS
|
||||||
if (binding.migNotes.isChecked) flags = flags or MigrationFlags.NOTES
|
|
||||||
preferences.migrateFlags().set(flags)
|
preferences.migrateFlags().set(flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,12 +10,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class PreMigrationScreenModel(
|
class PreMigrationScreenModel(
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
|
private val prefs: UnsortedPreferences = Injekt.get(),
|
||||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
) : ScreenModel {
|
) : ScreenModel {
|
||||||
|
|
||||||
@ -51,7 +53,7 @@ class PreMigrationScreenModel(
|
|||||||
*/
|
*/
|
||||||
private fun getEnabledSources(): List<MigrationSourceItem> {
|
private fun getEnabledSources(): List<MigrationSourceItem> {
|
||||||
val languages = sourcePreferences.enabledLanguages().get()
|
val languages = sourcePreferences.enabledLanguages().get()
|
||||||
val sourcesSaved = sourcePreferences.migrationSources().get().split("/")
|
val sourcesSaved = prefs.migrationSources().get().split("/")
|
||||||
.mapNotNull { it.toLongOrNull() }
|
.mapNotNull { it.toLongOrNull() }
|
||||||
val disabledSources = sourcePreferences.disabledSources().get()
|
val disabledSources = sourcePreferences.disabledSources().get()
|
||||||
.mapNotNull { it.toLongOrNull() }
|
.mapNotNull { it.toLongOrNull() }
|
||||||
@ -132,6 +134,6 @@ class PreMigrationScreenModel(
|
|||||||
?.joinToString("/") { it.source.id.toString() }
|
?.joinToString("/") { it.source.id.toString() }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
sourcePreferences.migrationSources().set(listOfSources)
|
prefs.migrationSources().set(listOfSources)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,8 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
|
|||||||
)
|
)
|
||||||
|
|
||||||
val onDismissRequest = { screenModel.dialog.value = null }
|
val onDismissRequest = { screenModel.dialog.value = null }
|
||||||
when (
|
when
|
||||||
|
(
|
||||||
@Suppress("NAME_SHADOWING")
|
@Suppress("NAME_SHADOWING")
|
||||||
val dialog = dialog
|
val dialog = dialog
|
||||||
) {
|
) {
|
||||||
|
@ -8,7 +8,6 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
|||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
@ -39,13 +38,14 @@ import logcat.LogPriority
|
|||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.interactor.SetMangaCategories
|
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.chapter.model.ChapterUpdate
|
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||||
import tachiyomi.domain.history.interactor.GetHistory
|
import tachiyomi.domain.history.interactor.GetHistoryByMangaId
|
||||||
import tachiyomi.domain.history.interactor.UpsertHistory
|
import tachiyomi.domain.history.interactor.UpsertHistory
|
||||||
import tachiyomi.domain.history.model.HistoryUpdate
|
import tachiyomi.domain.history.model.HistoryUpdate
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
@ -64,7 +64,7 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||||||
|
|
||||||
class MigrationListScreenModel(
|
class MigrationListScreenModel(
|
||||||
private val config: MigrationProcedureConfig,
|
private val config: MigrationProcedureConfig,
|
||||||
private val preferences: SourcePreferences = Injekt.get(),
|
private val preferences: UnsortedPreferences = Injekt.get(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
private val coverCache: CoverCache = Injekt.get(),
|
private val coverCache: CoverCache = Injekt.get(),
|
||||||
@ -75,7 +75,7 @@ class MigrationListScreenModel(
|
|||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
|
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
|
||||||
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
|
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
|
||||||
private val getHistoryByMangaId: GetHistory = Injekt.get(),
|
private val getHistoryByMangaId: GetHistoryByMangaId = Injekt.get(),
|
||||||
private val upsertHistory: UpsertHistory = Injekt.get(),
|
private val upsertHistory: UpsertHistory = Injekt.get(),
|
||||||
private val getCategories: GetCategories = Injekt.get(),
|
private val getCategories: GetCategories = Injekt.get(),
|
||||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||||
@ -226,7 +226,7 @@ class MigrationListScreenModel(
|
|||||||
if (searchResult != null &&
|
if (searchResult != null &&
|
||||||
!(searchResult.url == mangaObj.url && source.id == mangaObj.source)
|
!(searchResult.url == mangaObj.url && source.id == mangaObj.source)
|
||||||
) {
|
) {
|
||||||
val localManga = networkToLocalManga(searchResult)
|
val localManga = networkToLocalManga.await(searchResult)
|
||||||
|
|
||||||
val chapters = if (source is EHentai) {
|
val chapters = if (source is EHentai) {
|
||||||
source.getChapterList(localManga.toSManga(), throttleManager::throttle)
|
source.getChapterList(localManga.toSManga(), throttleManager::throttle)
|
||||||
@ -236,7 +236,7 @@ class MigrationListScreenModel(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
syncChaptersWithSource.await(chapters, localManga, source)
|
syncChaptersWithSource.await(chapters, localManga, source)
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
return@async2 null
|
return@async2 null
|
||||||
}
|
}
|
||||||
manga.progress.value =
|
manga.progress.value =
|
||||||
@ -248,7 +248,7 @@ class MigrationListScreenModel(
|
|||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
// Ignore cancellations
|
// Ignore cancellations
|
||||||
throw e
|
throw e
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,7 +264,7 @@ class MigrationListScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (searchResult != null) {
|
if (searchResult != null) {
|
||||||
val localManga = networkToLocalManga(searchResult)
|
val localManga = networkToLocalManga.await(searchResult)
|
||||||
val chapters = try {
|
val chapters = try {
|
||||||
if (source is EHentai) {
|
if (source is EHentai) {
|
||||||
source.getChapterList(localManga.toSManga(), throttleManager::throttle)
|
source.getChapterList(localManga.toSManga(), throttleManager::throttle)
|
||||||
@ -283,7 +283,7 @@ class MigrationListScreenModel(
|
|||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
// Ignore cancellations
|
// Ignore cancellations
|
||||||
throw e
|
throw e
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
manga.progress.value = validSources.size to (index + 1)
|
manga.progress.value = validSources.size to (index + 1)
|
||||||
@ -293,7 +293,7 @@ class MigrationListScreenModel(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}.await()
|
}.await()
|
||||||
} catch (_: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
// Ignore canceled migrations
|
// Ignore canceled migrations
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ class MigrationListScreenModel(
|
|||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
// Ignore cancellations
|
// Ignore cancellations
|
||||||
throw e
|
throw e
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,12 +455,12 @@ class MigrationListScreenModel(
|
|||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
val result = migratingManga.migrationScope.async {
|
val result = migratingManga.migrationScope.async {
|
||||||
val manga = getManga(newMangaId)!!
|
val manga = getManga(newMangaId)!!
|
||||||
val localManga = networkToLocalManga(manga)
|
val localManga = networkToLocalManga.await(manga)
|
||||||
try {
|
try {
|
||||||
val source = sourceManager.get(manga.source)!!
|
val source = sourceManager.get(manga.source)!!
|
||||||
val chapters = source.getChapterList(localManga.toSManga())
|
val chapters = source.getChapterList(localManga.toSManga())
|
||||||
syncChaptersWithSource.await(chapters, localManga, source)
|
syncChaptersWithSource.await(chapters, localManga, source)
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
return@async null
|
return@async null
|
||||||
}
|
}
|
||||||
localManga
|
localManga
|
||||||
@ -473,7 +473,7 @@ class MigrationListScreenModel(
|
|||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
// Ignore cancellations
|
// Ignore cancellations
|
||||||
throw e
|
throw e
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
|
|
||||||
migratingManga.searchResult.value = SearchResult.Result(result.id)
|
migratingManga.searchResult.value = SearchResult.Result(result.id)
|
||||||
|
@ -8,13 +8,13 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
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.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.presentation.browse.MigrateMangaScreen
|
import eu.kanade.presentation.browse.MigrateMangaScreen
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -44,7 +44,7 @@ data class MigrateMangaScreen(
|
|||||||
onClickItem = {
|
onClickItem = {
|
||||||
// SY -->
|
// SY -->
|
||||||
PreMigrationScreen.navigateToMigration(
|
PreMigrationScreen.navigateToMigration(
|
||||||
Injekt.get<SourcePreferences>().skipPreMigration().get(),
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
navigator,
|
navigator,
|
||||||
listOf(it.id),
|
listOf(it.id),
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,6 @@ import cafe.adriel.voyager.core.model.rememberScreenModel
|
|||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
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.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.presentation.browse.MigrateSourceScreen
|
import eu.kanade.presentation.browse.MigrateSourceScreen
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.TabContent
|
import eu.kanade.presentation.components.TabContent
|
||||||
@ -20,6 +19,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
@ -63,7 +63,7 @@ fun Screen.migrateSourceTab(): TabContent {
|
|||||||
manga.asSequence().filter { it.source == source.id }.map { it.id }.toList()
|
manga.asSequence().filter { it.source == source.id }.map { it.id }.toList()
|
||||||
withUIContext {
|
withUIContext {
|
||||||
PreMigrationScreen.navigateToMigration(
|
PreMigrationScreen.navigateToMigration(
|
||||||
Injekt.get<SourcePreferences>().skipPreMigration().get(),
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
navigator,
|
navigator,
|
||||||
sourceMangas,
|
sourceMangas,
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.browse.BrowseTabWrapper
|
||||||
|
import eu.kanade.presentation.util.Screen
|
||||||
|
|
||||||
|
class MigrationSourcesScreen : Screen() {
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
BrowseTabWrapper(migrateSourceTab(), onBackPressed = navigator::pop)
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
@ -36,7 +35,6 @@ import androidx.compose.ui.platform.LocalUriHandler
|
|||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
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.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.presentation.browse.BrowseSourceContent
|
import eu.kanade.presentation.browse.BrowseSourceContent
|
||||||
import eu.kanade.presentation.browse.MissingSourceScreen
|
import eu.kanade.presentation.browse.MissingSourceScreen
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
|
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
|
||||||
@ -65,6 +63,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
|||||||
import mihon.presentation.core.util.collectAsLazyPagingItems
|
import mihon.presentation.core.util.collectAsLazyPagingItems
|
||||||
import tachiyomi.core.common.Constants
|
import tachiyomi.core.common.Constants
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.source.model.StubSource
|
import tachiyomi.domain.source.model.StubSource
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@ -151,11 +150,7 @@ data class BrowseSourceScreen(
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||||
modifier = Modifier
|
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
|
||||||
.pointerInput(Unit) {},
|
|
||||||
) {
|
|
||||||
BrowseSourceToolbar(
|
BrowseSourceToolbar(
|
||||||
searchQuery = state.toolbarQuery,
|
searchQuery = state.toolbarQuery,
|
||||||
onSearchQueryChange = screenModel::setToolbarQuery,
|
onSearchQueryChange = screenModel::setToolbarQuery,
|
||||||
@ -261,11 +256,14 @@ data class BrowseSourceScreen(
|
|||||||
onMangaClick = { navigator.push(MangaScreen(it.id, true, smartSearchConfig)) },
|
onMangaClick = { navigator.push(MangaScreen(it.id, true, smartSearchConfig)) },
|
||||||
onMangaLongClick = { manga ->
|
onMangaLongClick = { manga ->
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
val duplicates = screenModel.getDuplicateLibraryManga(manga)
|
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||||
when {
|
when {
|
||||||
manga.favorite -> screenModel.setDialog(BrowseSourceScreenModel.Dialog.RemoveManga(manga))
|
manga.favorite -> screenModel.setDialog(BrowseSourceScreenModel.Dialog.RemoveManga(manga))
|
||||||
duplicates.isNotEmpty() -> screenModel.setDialog(
|
duplicateManga != null -> screenModel.setDialog(
|
||||||
BrowseSourceScreenModel.Dialog.AddDuplicateManga(manga, duplicates),
|
BrowseSourceScreenModel.Dialog.AddDuplicateManga(
|
||||||
|
manga,
|
||||||
|
duplicateManga,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else -> screenModel.addFavorite(manga)
|
else -> screenModel.addFavorite(manga)
|
||||||
}
|
}
|
||||||
@ -320,16 +318,15 @@ data class BrowseSourceScreen(
|
|||||||
}
|
}
|
||||||
is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> {
|
is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> {
|
||||||
DuplicateMangaDialog(
|
DuplicateMangaDialog(
|
||||||
duplicates = dialog.duplicates,
|
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||||
onOpenManga = { navigator.push(MangaScreen(it.id)) },
|
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
|
||||||
onMigrate = {
|
onMigrate = {
|
||||||
// SY -->
|
// SY -->
|
||||||
PreMigrationScreen.navigateToMigration(
|
PreMigrationScreen.navigateToMigration(
|
||||||
Injekt.get<SourcePreferences>().skipPreMigration().get(),
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
navigator,
|
navigator,
|
||||||
it.id,
|
dialog.duplicate.id,
|
||||||
dialog.manga.id,
|
dialog.manga.id,
|
||||||
)
|
)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -16,6 +16,7 @@ import cafe.adriel.voyager.core.model.screenModelScope
|
|||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
|
import eu.kanade.domain.manga.model.toDomainManga
|
||||||
import eu.kanade.domain.source.interactor.GetExhSavedSearch
|
import eu.kanade.domain.source.interactor.GetExhSavedSearch
|
||||||
import eu.kanade.domain.source.interactor.GetIncognitoState
|
import eu.kanade.domain.source.interactor.GetIncognitoState
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
@ -29,7 +30,6 @@ import eu.kanade.tachiyomi.source.online.MetadataSource
|
|||||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
import eu.kanade.tachiyomi.util.removeCovers
|
import eu.kanade.tachiyomi.util.removeCovers
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
import exh.source.ExhPreferences
|
|
||||||
import exh.source.getMainSource
|
import exh.source.getMainSource
|
||||||
import exh.source.mangaDexSourceIds
|
import exh.source.mangaDexSourceIds
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
@ -49,6 +50,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import tachiyomi.core.common.preference.CheckboxState
|
import tachiyomi.core.common.preference.CheckboxState
|
||||||
@ -56,6 +58,7 @@ import tachiyomi.core.common.preference.mapAsCheckboxState
|
|||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.interactor.SetMangaCategories
|
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
@ -64,15 +67,15 @@ import tachiyomi.domain.library.service.LibraryPreferences
|
|||||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||||
import tachiyomi.domain.manga.interactor.GetFlatMetadataById
|
import tachiyomi.domain.manga.interactor.GetFlatMetadataById
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaWithChapterCount
|
|
||||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||||
import tachiyomi.domain.source.interactor.DeleteSavedSearchById
|
import tachiyomi.domain.source.interactor.DeleteSavedSearchById
|
||||||
import tachiyomi.domain.source.interactor.GetRemoteManga
|
import tachiyomi.domain.source.interactor.GetRemoteManga
|
||||||
import tachiyomi.domain.source.interactor.InsertSavedSearch
|
import tachiyomi.domain.source.interactor.InsertSavedSearch
|
||||||
import tachiyomi.domain.source.model.EXHSavedSearch
|
import tachiyomi.domain.source.model.EXHSavedSearch
|
||||||
import tachiyomi.domain.source.model.SavedSearch
|
import tachiyomi.domain.source.model.SavedSearch
|
||||||
import tachiyomi.domain.source.repository.SourcePagingSource
|
import tachiyomi.domain.source.repository.SourcePagingSourceType
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -98,12 +101,13 @@ open class BrowseSourceScreenModel(
|
|||||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
|
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
private val addTracks: AddTracks = Injekt.get(),
|
private val addTracks: AddTracks = Injekt.get(),
|
||||||
private val getIncognitoState: GetIncognitoState = Injekt.get(),
|
private val getIncognitoState: GetIncognitoState = Injekt.get(),
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
exhPreferences: ExhPreferences = Injekt.get(),
|
unsortedPreferences: UnsortedPreferences = Injekt.get(),
|
||||||
uiPreferences: UiPreferences = Injekt.get(),
|
uiPreferences: UiPreferences = Injekt.get(),
|
||||||
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
|
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
|
||||||
private val deleteSavedSearchById: DeleteSavedSearchById = Injekt.get(),
|
private val deleteSavedSearchById: DeleteSavedSearchById = Injekt.get(),
|
||||||
@ -117,7 +121,7 @@ open class BrowseSourceScreenModel(
|
|||||||
val source = sourceManager.getOrStub(sourceId)
|
val source = sourceManager.getOrStub(sourceId)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val ehentaiBrowseDisplayMode by exhPreferences.enhancedEHentaiView().asState(screenModelScope)
|
val ehentaiBrowseDisplayMode by unsortedPreferences.enhancedEHentaiView().asState(screenModelScope)
|
||||||
|
|
||||||
val startExpanded by uiPreferences.expandFilters().asState(screenModelScope)
|
val startExpanded by uiPreferences.expandFilters().asState(screenModelScope)
|
||||||
|
|
||||||
@ -189,9 +193,10 @@ open class BrowseSourceScreenModel(
|
|||||||
createSourcePagingSource(listing.query ?: "", listing.filters)
|
createSourcePagingSource(listing.query ?: "", listing.filters)
|
||||||
// SY <--
|
// SY <--
|
||||||
}.flow.map { pagingData ->
|
}.flow.map { pagingData ->
|
||||||
pagingData.map { (manga, metadata) ->
|
pagingData.map { (it, metadata) ->
|
||||||
getManga.subscribe(manga.url, manga.source)
|
networkToLocalManga.await(it.toDomainManga(sourceId))
|
||||||
.map { it ?: manga }
|
.let { localManga -> getManga.subscribe(localManga.url, localManga.source) }
|
||||||
|
.filterNotNull()
|
||||||
// SY -->
|
// SY -->
|
||||||
.combineMetadata(metadata)
|
.combineMetadata(metadata)
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -377,8 +382,8 @@ open class BrowseSourceScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
open fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSource {
|
open fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
|
||||||
return getRemoteManga(sourceId, query, filters)
|
return getRemoteManga.subscribe(sourceId, query, filters)
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
@ -394,8 +399,8 @@ open class BrowseSourceScreenModel(
|
|||||||
.orEmpty()
|
.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getDuplicateLibraryManga(manga: Manga): List<MangaWithChapterCount> {
|
suspend fun getDuplicateLibraryManga(manga: Manga): Manga? {
|
||||||
return getDuplicateLibraryManga.invoke(manga)
|
return getDuplicateLibraryManga.await(manga).getOrNull(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
|
private fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
|
||||||
@ -445,7 +450,7 @@ open class BrowseSourceScreenModel(
|
|||||||
sealed interface Dialog {
|
sealed interface Dialog {
|
||||||
data object Filter : Dialog
|
data object Filter : Dialog
|
||||||
data class RemoveManga(val manga: Manga) : Dialog
|
data class RemoveManga(val manga: Manga) : Dialog
|
||||||
data class AddDuplicateManga(val manga: Manga, val duplicates: List<MangaWithChapterCount>) : Dialog
|
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog
|
||||||
data class ChangeMangaCategory(
|
data class ChangeMangaCategory(
|
||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
val initialSelection: ImmutableList<CheckboxState.State<Category>>,
|
val initialSelection: ImmutableList<CheckboxState.State<Category>>,
|
||||||
|
@ -188,24 +188,22 @@ private fun FilterItem(filter: Filter<*>, onUpdate: () -> Unit/* SY --> */, star
|
|||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
filter.values.mapIndexed { index, item ->
|
filter.values.mapIndexed { index, item ->
|
||||||
val sortAscending = filter.state?.ascending
|
|
||||||
?.takeIf { index == filter.state?.index }
|
|
||||||
SortItem(
|
SortItem(
|
||||||
label = item,
|
label = item,
|
||||||
sortDescending = if (sortAscending != null) !sortAscending else null,
|
sortDescending = filter.state?.ascending?.not()
|
||||||
onClick = {
|
?.takeIf { index == filter.state?.index },
|
||||||
val ascending = if (index == filter.state?.index) {
|
) {
|
||||||
!filter.state!!.ascending
|
val ascending = if (index == filter.state?.index) {
|
||||||
} else {
|
!filter.state!!.ascending
|
||||||
filter.state?.ascending ?: true
|
} else {
|
||||||
}
|
filter.state!!.ascending
|
||||||
filter.state = Filter.Sort.Selection(
|
}
|
||||||
index = index,
|
filter.state = Filter.Sort.Selection(
|
||||||
ascending = ascending,
|
index = index,
|
||||||
)
|
ascending = ascending,
|
||||||
onUpdate()
|
)
|
||||||
},
|
onUpdate()
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user