Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c95d7fe30f | |||
| 2b890c2057 | |||
| 456db52653 | |||
| 0a5e9dce24 | |||
| 9b6600d31f | |||
| 5f19859589 | |||
| a189780e7f | |||
| 664fcfd787 | |||
| 64e3e03a02 | |||
| 3139aa5e51 | |||
| 2744a8bd96 | |||
| 0ab7d18ad3 | |||
| 853288f71b | |||
| 4a2a81df80 | |||
| 6827a0899c | |||
| dd1cbb07f7 | |||
| 7f20006622 | |||
| 8d25074a3e | |||
| 73a265f5ad | |||
| 02da349080 | |||
| e218234f91 | |||
| 47b4be7fcf | |||
| fea11eaa06 | |||
| 99ef619603 | |||
| 93a5e70bbe | |||
| a3c1c63332 | |||
| 4348862e46 | |||
| 5a6aaf8dcf | |||
| bc28f7a4e9 | |||
| 27f6ed4338 | |||
| 4ec0a6d148 | |||
| 9e7a3c9e41 | |||
| 8794b7f5de | |||
| 2f02aa07c7 | |||
| 62f9c2b187 | |||
| 900ecfe372 | |||
| 72a19fc349 | |||
| 9e24276b59 | |||
| 46dea6d598 | |||
| d80ad3f145 | |||
| a7a3e5a2db | |||
| a32c7186e4 | |||
| a25aff7fb0 | |||
| 7721d8b733 | |||
| 75db0d09e5 | |||
| fd120c5081 | |||
| 34e9d9f146 | |||
| b7f7187293 | |||
| 4abadea4f9 | |||
| 1b3d76398b | |||
| 688fdecaf8 | |||
| 0bedee1778 | |||
| bb89f9f636 | |||
| f8011981eb | |||
| 7e17e52e07 | |||
| b65990ad29 | |||
| d9560d40de | |||
| 036ab3351d | |||
| 769293355f | |||
| 850d81600e | |||
| ce96b53f10 | |||
| b98dfd65b5 | |||
| 612e0a00bc | |||
| d286cf3267 | |||
| 1a28c7fb35 | |||
| 5909f90003 | |||
| 48f7b701dc | |||
| b17530ccc3 | |||
| f844a48b67 | |||
| 66929e097c | |||
| be30814d35 | |||
| 5d56c1961d | |||
| 4aa52a2576 | |||
| f7a1869066 | |||
| 2f1d76cbac | |||
| 5c5e08b99b | |||
| cc16d53ecc | |||
| 28fa3855c2 | |||
| 5a47a58e1e | |||
| c86714ef59 | |||
| 75fe57b851 | |||
| b9fffc45cc | |||
| de6cd169d0 | |||
| 95e8a02e33 | |||
| c720f0ac5c | |||
| 76af3b59f0 | |||
| 3f8cce8a32 | |||
| 26cfb4811f | |||
| e5a6d1b456 | |||
| f0b621dfe5 | |||
| d88f570f65 | |||
| b430e31da4 | |||
| 271f2d37bb | |||
| c2e36b4c5c | |||
| cb25deb5ac | |||
| a6c6cf77bb | |||
| e3dae57e0b | |||
| 226321f334 | |||
| 2187731d70 | |||
| fd32f2e879 | |||
| 5a094850d1 | |||
| e74053e989 | |||
| 798db44908 | |||
| 7715b5bdd0 | |||
| 084e11f21d | |||
| 01792c0618 | |||
| 0b93ceaa8f | |||
| bfdbe18509 | |||
| e3245d0610 | |||
| c2df6ee54a | |||
| ffc1e2d97b | |||
| d0c8b0c98a | |||
| f206ab8b32 | |||
| a443629234 | |||
| 3de4711e03 | |||
| 106f63a657 | |||
| 3c09343f7b | |||
| 86e1406565 | |||
| b48556aa9f | |||
| f3e905513f | |||
| 633a1892b3 | |||
| 74cf08b47b | |||
| cc7ce80abf | |||
| e06941f82d | |||
| a8a290d03d | |||
| b49ca3ce4c | |||
| c51c364cdd | |||
| 366415d323 | |||
| 14f6fd7908 | |||
| 15f1ee2205 | |||
| 651579b243 | |||
| 8f596069fa | |||
| a28d526102 |
@@ -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.10.5"
|
Example: "1.11.0"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ body:
|
|||||||
label: Android version
|
label: Android version
|
||||||
description: You can find this somewhere in your Android settings.
|
description: You can find this somewhere in your Android settings.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "Android 11"
|
Example: "Android 14"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ body:
|
|||||||
label: Device
|
label: Device
|
||||||
description: List your device and model.
|
description: List your device and model.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "Google Pixel 5"
|
Example: "Google Pixel 8"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -94,9 +94,9 @@ 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 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.10.5](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 updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -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.10.5](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
|
||||||
|
|||||||
@@ -6,20 +6,8 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_wrapper:
|
|
||||||
name: Validate Gradle Wrapper
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
|
||||||
uses: gradle/actions/wrapper-validation@v4
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build app
|
name: Build app
|
||||||
needs: check_wrapper
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -30,7 +18,7 @@ jobs:
|
|||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: temurin
|
||||||
|
|
||||||
- name: Set up gradle
|
- name: Set up gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ jobs:
|
|||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
|
||||||
uses: gradle/actions/wrapper-validation@v4
|
|
||||||
|
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
run: |
|
run: |
|
||||||
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
||||||
@@ -28,12 +25,12 @@ jobs:
|
|||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: temurin
|
||||||
|
|
||||||
- name: Set up gradle
|
- name: Set up gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
# SY <--
|
# SY -->
|
||||||
- name: Write google-services.json
|
- name: Write google-services.json
|
||||||
uses: DamianReeves/write-file-action@v1.3
|
uses: DamianReeves/write-file-action@v1.3
|
||||||
with:
|
with:
|
||||||
@@ -47,10 +44,16 @@ jobs:
|
|||||||
path: app/src/main/assets/client_secrets.json
|
path: app/src/main/assets/client_secrets.json
|
||||||
contents: ${{ secrets.CLIENT_SECRETS_TEXT }}
|
contents: ${{ secrets.CLIENT_SECRETS_TEXT }}
|
||||||
write-mode: overwrite
|
write-mode: overwrite
|
||||||
# SY -->
|
# SY <--
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
- name: Check code format
|
||||||
run: ./gradlew spotlessCheck assembleStandardRelease testStandardReleaseUnitTest --stacktrace
|
run: ./gradlew spotlessCheck
|
||||||
|
|
||||||
|
- name: Build app
|
||||||
|
run: ./gradlew assembleStandardRelease
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: ./gradlew testReleaseUnitTest testStandardReleaseUnitTest
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
@@ -69,19 +72,19 @@ jobs:
|
|||||||
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
|
||||||
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk
|
mv app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk
|
||||||
sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'`
|
||||||
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk
|
mv app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk
|
||||||
sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'`
|
||||||
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk
|
mv app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk
|
||||||
sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'`
|
||||||
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk
|
mv app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk
|
||||||
sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'`
|
||||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
|||||||
+22
-27
@@ -1,7 +1,8 @@
|
|||||||
|
@file:Suppress("ChromeOsAbiSupport")
|
||||||
|
|
||||||
import mihon.buildlogic.getBuildTime
|
import mihon.buildlogic.getBuildTime
|
||||||
import mihon.buildlogic.getCommitCount
|
import mihon.buildlogic.getCommitCount
|
||||||
import mihon.buildlogic.getGitSha
|
import mihon.buildlogic.getGitSha
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("mihon.android.application")
|
id("mihon.android.application")
|
||||||
@@ -30,8 +31,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi.sy"
|
applicationId = "eu.kanade.tachiyomi.sy"
|
||||||
|
|
||||||
versionCode = 69
|
versionCode = 71
|
||||||
versionName = "1.10.5"
|
versionName = "1.11.0"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@@ -98,8 +99,6 @@ android {
|
|||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
create("dev") {
|
create("dev") {
|
||||||
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
|
||||||
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
|
||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,6 +140,24 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
freeCompilerArgs.addAll(
|
||||||
|
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||||
|
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
|
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||||
|
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.i18n)
|
implementation(projects.i18n)
|
||||||
// SY -->
|
// SY -->
|
||||||
@@ -307,28 +324,6 @@ androidComponents {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
|
||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
|
||||||
withType<KotlinCompile> {
|
|
||||||
compilerOptions.freeCompilerArgs.addAll(
|
|
||||||
"-Xcontext-receivers",
|
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
|
||||||
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
|
||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath(kotlinx.gradle)
|
classpath(kotlinx.gradle)
|
||||||
|
|||||||
@@ -415,4 +415,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
<uses-sdk tools:overrideLibrary="rikka.shizuku.api"
|
||||||
|
tools:ignore="ManifestOrder" />
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.core.util
|
package eu.kanade.core.util
|
||||||
|
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
@@ -45,21 +46,6 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list containing only elements matching the given [predicate].
|
|
||||||
*
|
|
||||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
|
||||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
|
||||||
* collections that are created by code we control and are known to support random access.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
|
||||||
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
|
||||||
contract { callsInPlace(predicate) }
|
|
||||||
val destination = ArrayList<T>()
|
|
||||||
fastForEach { if (predicate(it)) destination.add(it) }
|
|
||||||
return destination
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list containing all elements not matching the given [predicate].
|
* Returns a list containing all elements not matching the given [predicate].
|
||||||
*
|
*
|
||||||
@@ -70,27 +56,7 @@ inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
|||||||
@OptIn(ExperimentalContracts::class)
|
@OptIn(ExperimentalContracts::class)
|
||||||
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
||||||
contract { callsInPlace(predicate) }
|
contract { callsInPlace(predicate) }
|
||||||
val destination = ArrayList<T>()
|
return fastFilter { !predicate(it) }
|
||||||
fastForEach { if (!predicate(it)) destination.add(it) }
|
|
||||||
return destination
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list containing only the non-null results of applying the
|
|
||||||
* given [transform] function to each element in the original collection.
|
|
||||||
*
|
|
||||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
|
||||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
|
||||||
* collections that are created by code we control and are known to support random access.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
|
||||||
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
|
|
||||||
contract { callsInPlace(transform) }
|
|
||||||
val destination = ArrayList<R>()
|
|
||||||
fastForEach { element ->
|
|
||||||
transform(element)?.let(destination::add)
|
|
||||||
}
|
|
||||||
return destination
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,26 +97,3 @@ inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
|
|||||||
fastForEach { if (predicate(it)) --count }
|
fastForEach { if (predicate(it)) --count }
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list containing only elements from the given collection
|
|
||||||
* having distinct keys returned by the given [selector] function.
|
|
||||||
*
|
|
||||||
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
|
|
||||||
* The elements in the resulting list are in the same order as they were in the source collection.
|
|
||||||
*
|
|
||||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
|
||||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
|
||||||
* collections that are created by code we control and are known to support random access.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
|
||||||
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
|
|
||||||
contract { callsInPlace(selector) }
|
|
||||||
val set = HashSet<K>()
|
|
||||||
val list = ArrayList<T>()
|
|
||||||
fastForEach {
|
|
||||||
val key = selector(it)
|
|
||||||
if (set.add(key)) list.add(it)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import eu.kanade.domain.manga.interactor.SetExcludedScanlators
|
|||||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
|
import eu.kanade.domain.source.interactor.GetIncognitoState
|
||||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
|
import eu.kanade.domain.source.interactor.ToggleIncognito
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
@@ -190,5 +192,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { DeleteExtensionRepo(get()) }
|
addFactory { DeleteExtensionRepo(get()) }
|
||||||
addFactory { ReplaceExtensionRepo(get()) }
|
addFactory { ReplaceExtensionRepo(get()) }
|
||||||
addFactory { UpdateExtensionRepo(get(), get()) }
|
addFactory { UpdateExtensionRepo(get(), get()) }
|
||||||
|
addFactory { ToggleIncognito(get()) }
|
||||||
|
addFactory { GetIncognitoState(get(), get(), get()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.kanade.domain.base
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -30,4 +31,8 @@ class BasePreferences(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
||||||
|
|
||||||
|
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
|
||||||
|
|
||||||
|
fun alwaysDecodeLongStripWithSSIV() = preferenceStore.getBoolean("pref_always_decode_long_strip_with_ssiv", false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.base.BasePreferences
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
|
class GetIncognitoState(
|
||||||
|
private val basePreferences: BasePreferences,
|
||||||
|
private val sourcePreferences: SourcePreferences,
|
||||||
|
private val extensionManager: ExtensionManager,
|
||||||
|
) {
|
||||||
|
fun await(sourceId: Long?): Boolean {
|
||||||
|
if (basePreferences.incognitoMode().get()) return true
|
||||||
|
if (sourceId == null) return false
|
||||||
|
val extensionPackage = extensionManager.getExtensionPackage(sourceId) ?: return false
|
||||||
|
|
||||||
|
return extensionPackage in sourcePreferences.incognitoExtensions().get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun subscribe(sourceId: Long?): Flow<Boolean> {
|
||||||
|
if (sourceId == null) return basePreferences.incognitoMode().changes()
|
||||||
|
|
||||||
|
return combine(
|
||||||
|
basePreferences.incognitoMode().changes(),
|
||||||
|
sourcePreferences.incognitoExtensions().changes(),
|
||||||
|
extensionManager.getExtensionPackageAsFlow(sourceId),
|
||||||
|
) { incognito, incognitoExtensions, extensionPackage ->
|
||||||
|
incognito || (extensionPackage in incognitoExtensions)
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
|
|
||||||
|
class ToggleIncognito(
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
fun await(extensions: String, enable: Boolean) {
|
||||||
|
preferences.incognitoExtensions().getAndSet {
|
||||||
|
if (enable) it.plus(extensions) else it.minus(extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ class SourcePreferences(
|
|||||||
|
|
||||||
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
|
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
|
||||||
|
|
||||||
|
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet())
|
||||||
|
|
||||||
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
fun lastUsedSource() = preferenceStore.getLong(
|
fun lastUsedSource() = preferenceStore.getLong(
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.domain.track.model
|
||||||
|
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
|
enum class AutoTrackState(val titleRes: StringResource) {
|
||||||
|
ALWAYS(MR.strings.lock_always),
|
||||||
|
ASK(MR.strings.default_category_summary),
|
||||||
|
NEVER(MR.strings.lock_never),
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package eu.kanade.domain.track.service
|
package eu.kanade.domain.track.service
|
||||||
|
|
||||||
|
import eu.kanade.domain.track.model.AutoTrackState
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.common.preference.getEnum
|
||||||
|
|
||||||
class TrackPreferences(
|
class TrackPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
@@ -35,4 +37,9 @@ class TrackPreferences(
|
|||||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||||
|
|
||||||
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||||
|
|
||||||
|
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum(
|
||||||
|
"pref_auto_update_manga_on_mark_read",
|
||||||
|
AutoTrackState.ALWAYS,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseTabWrapper(tab: TabContent) {
|
fun BrowseTabWrapper(tab: TabContent, onBackPressed: (() -> Unit)? = null) {
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
@@ -20,6 +20,7 @@ fun BrowseTabWrapper(tab: TabContent) {
|
|||||||
actions = {
|
actions = {
|
||||||
AppBarActions(tab.actions)
|
AppBarActions(tab.actions)
|
||||||
},
|
},
|
||||||
|
navigateUp = onBackPressed,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ 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.graphics.vector.ImageVector
|
||||||
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.res.vectorResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -48,6 +50,7 @@ import eu.kanade.presentation.components.AppBarActions
|
|||||||
import eu.kanade.presentation.components.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
||||||
@@ -73,6 +76,7 @@ fun ExtensionDetailsScreen(
|
|||||||
onClickClearCookies: () -> Unit,
|
onClickClearCookies: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
|
onClickIncognito: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val url = remember(state.extension) {
|
val url = remember(state.extension) {
|
||||||
@@ -141,9 +145,11 @@ fun ExtensionDetailsScreen(
|
|||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
extension = state.extension,
|
extension = state.extension,
|
||||||
sources = state.sources,
|
sources = state.sources,
|
||||||
|
incognitoMode = state.isIncognito,
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
onClickUninstall = onClickUninstall,
|
onClickUninstall = onClickUninstall,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
|
onClickIncognito = onClickIncognito,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,9 +159,11 @@ private fun ExtensionDetails(
|
|||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
extension: Extension.Installed,
|
extension: Extension.Installed,
|
||||||
sources: ImmutableList<ExtensionSourceItem>,
|
sources: ImmutableList<ExtensionSourceItem>,
|
||||||
|
incognitoMode: Boolean,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
|
onClickIncognito: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var showNsfwWarning by remember { mutableStateOf(false) }
|
var showNsfwWarning by remember { mutableStateOf(false) }
|
||||||
@@ -179,6 +187,7 @@ private fun ExtensionDetails(
|
|||||||
item {
|
item {
|
||||||
DetailsHeader(
|
DetailsHeader(
|
||||||
extension = extension,
|
extension = extension,
|
||||||
|
extIncognitoMode = incognitoMode,
|
||||||
onClickUninstall = onClickUninstall,
|
onClickUninstall = onClickUninstall,
|
||||||
onClickAppInfo = {
|
onClickAppInfo = {
|
||||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
@@ -190,6 +199,7 @@ private fun ExtensionDetails(
|
|||||||
onClickAgeRating = {
|
onClickAgeRating = {
|
||||||
showNsfwWarning = true
|
showNsfwWarning = true
|
||||||
},
|
},
|
||||||
|
onExtIncognitoChange = onClickIncognito,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,9 +227,11 @@ private fun ExtensionDetails(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DetailsHeader(
|
private fun DetailsHeader(
|
||||||
extension: Extension,
|
extension: Extension,
|
||||||
|
extIncognitoMode: Boolean,
|
||||||
onClickAgeRating: () -> Unit,
|
onClickAgeRating: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickAppInfo: (() -> Unit)?,
|
onClickAppInfo: (() -> Unit)?,
|
||||||
|
onExtIncognitoChange: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -227,9 +239,8 @@ private fun DetailsHeader(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium)
|
||||||
.padding(
|
.padding(
|
||||||
start = MaterialTheme.padding.medium,
|
|
||||||
end = MaterialTheme.padding.medium,
|
|
||||||
top = MaterialTheme.padding.medium,
|
top = MaterialTheme.padding.medium,
|
||||||
bottom = MaterialTheme.padding.small,
|
bottom = MaterialTheme.padding.small,
|
||||||
)
|
)
|
||||||
@@ -321,12 +332,9 @@ private fun DetailsHeader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier
|
||||||
start = MaterialTheme.padding.medium,
|
.padding(horizontal = MaterialTheme.padding.medium)
|
||||||
end = MaterialTheme.padding.medium,
|
.padding(top = MaterialTheme.padding.small),
|
||||||
top = MaterialTheme.padding.small,
|
|
||||||
bottom = MaterialTheme.padding.medium,
|
|
||||||
),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
@@ -349,6 +357,24 @@ private fun DetailsHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextPreferenceWidget(
|
||||||
|
modifier = Modifier.padding(horizontal = MaterialTheme.padding.small),
|
||||||
|
title = stringResource(MR.strings.pref_incognito_mode),
|
||||||
|
subtitle = stringResource(MR.strings.pref_incognito_mode_extension_summary),
|
||||||
|
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
|
||||||
|
widget = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Switch(
|
||||||
|
checked = extIncognitoMode,
|
||||||
|
onCheckedChange = onExtIncognitoChange,
|
||||||
|
modifier = Modifier.padding(start = TrailingWidgetBuffer),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryTabRow
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
@@ -14,7 +15,6 @@ import androidx.compose.material3.SnackbarHost
|
|||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -33,20 +33,13 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
fun TabbedScreen(
|
fun TabbedScreen(
|
||||||
titleRes: StringResource,
|
titleRes: StringResource,
|
||||||
tabs: ImmutableList<TabContent>,
|
tabs: ImmutableList<TabContent>,
|
||||||
startIndex: Int? = null,
|
state: PagerState = rememberPagerState { tabs.size },
|
||||||
searchQuery: String? = null,
|
searchQuery: String? = null,
|
||||||
onChangeSearchQuery: (String?) -> Unit = {},
|
onChangeSearchQuery: (String?) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val state = rememberPagerState { tabs.size }
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
LaunchedEffect(startIndex) {
|
|
||||||
if (startIndex != null) {
|
|
||||||
state.scrollToPage(startIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
val tab = tabs[state.currentPage]
|
val tab = tabs[state.currentPage]
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ internal fun LibraryTabs(
|
|||||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||||
onTabItemClick: (Int) -> Unit,
|
onTabItemClick: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
// SY -->
|
|
||||||
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
||||||
// SY <--
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.zIndex(1f),
|
modifier = Modifier.zIndex(1f),
|
||||||
) {
|
) {
|
||||||
|
|||||||
+119
-25
@@ -15,7 +15,6 @@ import androidx.compose.ui.window.DialogProperties
|
|||||||
import exh.favorites.FavoritesSyncStatus
|
import exh.favorites.FavoritesSyncStatus
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
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 kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@@ -23,7 +22,6 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
data class SyncFavoritesProgressProperties(
|
data class SyncFavoritesProgressProperties(
|
||||||
val title: String,
|
val title: String,
|
||||||
val text: String,
|
val text: String,
|
||||||
val canDismiss: Boolean,
|
|
||||||
val positiveButtonText: String? = null,
|
val positiveButtonText: String? = null,
|
||||||
val positiveButton: (() -> Unit)? = null,
|
val positiveButton: (() -> Unit)? = null,
|
||||||
val negativeButtonText: String? = null,
|
val negativeButtonText: String? = null,
|
||||||
@@ -34,18 +32,23 @@ data class SyncFavoritesProgressProperties(
|
|||||||
fun SyncFavoritesProgressDialog(
|
fun SyncFavoritesProgressDialog(
|
||||||
status: FavoritesSyncStatus,
|
status: FavoritesSyncStatus,
|
||||||
setStatusIdle: () -> Unit,
|
setStatusIdle: () -> Unit,
|
||||||
openManga: (Manga) -> Unit,
|
openManga: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
|
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
|
||||||
when (status) {
|
when (status) {
|
||||||
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
|
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
||||||
text = context.stringResource(SYMR.strings.favorites_sync_bad_library_state, status.message),
|
text = context.stringResource(
|
||||||
canDismiss = false,
|
SYMR.strings.favorites_sync_bad_library_state,
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_gallery_in_multiple_categories, status.mangaTitle,
|
||||||
|
status.categories.joinToString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
|
positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
|
||||||
positiveButton = {
|
positiveButton = {
|
||||||
openManga(status.manga)
|
openManga(status.mangaId)
|
||||||
setStatusIdle()
|
setStatusIdle()
|
||||||
},
|
},
|
||||||
negativeButtonText = context.stringResource(MR.strings.action_ok),
|
negativeButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
@@ -53,31 +56,122 @@ fun SyncFavoritesProgressDialog(
|
|||||||
)
|
)
|
||||||
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
|
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
|
title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
|
||||||
text = context.stringResource(SYMR.strings.favorites_sync_done_errors_message, status.message),
|
text = context.stringResource(
|
||||||
canDismiss = false,
|
SYMR.strings.favorites_sync_done_errors_message,
|
||||||
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
status.messages.joinToString(separator = "\n") {
|
||||||
positiveButton = setStatusIdle,
|
when (it) {
|
||||||
)
|
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
|
||||||
is FavoritesSyncStatus.Error -> value = SyncFavoritesProgressProperties(
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
context.stringResource(
|
||||||
text = context.stringResource(SYMR.strings.favorites_sync_error_string, status.message),
|
SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, it.reason,
|
||||||
canDismiss = false,
|
)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, it.url,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, it.title, it.gid)
|
||||||
|
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
positiveButton = setStatusIdle,
|
positiveButton = setStatusIdle,
|
||||||
)
|
)
|
||||||
is FavoritesSyncStatus.Idle -> value = null
|
is FavoritesSyncStatus.Idle -> value = null
|
||||||
is FavoritesSyncStatus.Initializing, is FavoritesSyncStatus.Processing -> {
|
is FavoritesSyncStatus.Initializing -> {
|
||||||
value = SyncFavoritesProgressProperties(
|
value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_syncing),
|
title = context.stringResource(SYMR.strings.favorites_syncing),
|
||||||
text = status.message,
|
text = context.stringResource(SYMR.strings.favorites_sync_initializing),
|
||||||
canDismiss = false,
|
|
||||||
)
|
)
|
||||||
if (status is FavoritesSyncStatus.Processing && status.title != null) {
|
}
|
||||||
|
|
||||||
|
is FavoritesSyncStatus.SyncError -> value = SyncFavoritesProgressProperties(
|
||||||
|
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
||||||
|
text = context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_error_string,
|
||||||
|
when (status) {
|
||||||
|
FavoritesSyncStatus.SyncError.NotLoggedInSyncError -> context.stringResource(SYMR.strings.please_login)
|
||||||
|
FavoritesSyncStatus.SyncError.FailedToFetchFavorites ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_featch)
|
||||||
|
is FavoritesSyncStatus.SyncError.UnknownSyncError ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unknown_error, status.message)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_failed_to_add_to_local_error, status.title, status.reason,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, status.title, status.url,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, status.title, status.gid)
|
||||||
|
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
|
positiveButton = setStatusIdle,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.Processing -> {
|
||||||
|
val properties = SyncFavoritesProgressProperties(
|
||||||
|
title = context.stringResource(SYMR.strings.favorites_syncing),
|
||||||
|
text = when (status) {
|
||||||
|
FavoritesSyncStatus.Processing.VerifyingLibrary ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_verifying_library)
|
||||||
|
FavoritesSyncStatus.Processing.DownloadingFavorites ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_downloading)
|
||||||
|
FavoritesSyncStatus.Processing.CalculatingRemoteChanges ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes)
|
||||||
|
FavoritesSyncStatus.Processing.CalculatingLocalChanges ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes)
|
||||||
|
FavoritesSyncStatus.Processing.SyncingCategoryNames ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names)
|
||||||
|
is FavoritesSyncStatus.Processing.RemovingRemoteGalleries ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, status.galleryCount)
|
||||||
|
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
|
||||||
|
if (status.isThrottling) {
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_processing_throttle,
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total)
|
||||||
|
}
|
||||||
|
is FavoritesSyncStatus.Processing.RemovingGalleryFromLocal ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, status.index, status.total)
|
||||||
|
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
|
||||||
|
if (status.isThrottling) {
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_processing_throttle,
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total)
|
||||||
|
}
|
||||||
|
|
||||||
|
FavoritesSyncStatus.Processing.CleaningUp ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_cleaning_up)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
value = properties
|
||||||
|
if (
|
||||||
|
status is FavoritesSyncStatus.Processing.AddingGalleryToRemote ||
|
||||||
|
status is FavoritesSyncStatus.Processing.AddingGalleryToLocal
|
||||||
|
) {
|
||||||
delay(5.seconds)
|
delay(5.seconds)
|
||||||
value = SyncFavoritesProgressProperties(
|
value = properties.copy(
|
||||||
title = context.stringResource(SYMR.strings.favorites_syncing),
|
text = when (status) {
|
||||||
text = status.delayedMessage ?: status.message,
|
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
|
||||||
canDismiss = false,
|
properties.text + "\n\n" + status.title
|
||||||
|
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
|
||||||
|
properties.text + "\n\n" + status.title
|
||||||
|
else -> properties.text
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,8 +206,8 @@ fun SyncFavoritesProgressDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
properties = DialogProperties(
|
properties = DialogProperties(
|
||||||
dismissOnClickOutside = dialog.canDismiss,
|
dismissOnClickOutside = false,
|
||||||
dismissOnBackPress = dialog.canDismiss,
|
dismissOnBackPress = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,12 +165,12 @@ sealed class Preference {
|
|||||||
|
|
||||||
data class CustomPreference(
|
data class CustomPreference(
|
||||||
override val title: String,
|
override val title: String,
|
||||||
val content: @Composable (PreferenceItem<String>) -> Unit,
|
val content: @Composable () -> Unit,
|
||||||
) : PreferenceItem<String>() {
|
) : PreferenceItem<Unit>() {
|
||||||
override val enabled: Boolean = true
|
override val enabled: Boolean = true
|
||||||
override val subtitle: String? = null
|
override val subtitle: String? = null
|
||||||
override val icon: ImageVector? = null
|
override val icon: ImageVector? = null
|
||||||
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
|
override val onValueChanged: suspend (newValue: Unit) -> Boolean = { true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ internal fun PreferenceItem(
|
|||||||
InfoWidget(text = item.title)
|
InfoWidget(text = item.title)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.CustomPreference -> {
|
is Preference.PreferenceItem.CustomPreference -> {
|
||||||
item.content(item)
|
item.content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
@@ -59,6 +59,7 @@ import eu.kanade.tachiyomi.source.AndroidSourceManager
|
|||||||
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
|
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||||
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||||
@@ -83,6 +84,7 @@ import tachiyomi.core.common.i18n.pluralStringResource
|
|||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
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.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.UnsortedPreferences
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
@@ -369,6 +371,31 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.pref_category_reader),
|
title = stringResource(MR.strings.pref_category_reader),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = basePreferences.hardwareBitmapThreshold(),
|
||||||
|
title = stringResource(MR.strings.pref_hardware_bitmap_threshold),
|
||||||
|
subtitleProvider = { value, options ->
|
||||||
|
stringResource(MR.strings.pref_hardware_bitmap_threshold_summary, options[value].orEmpty())
|
||||||
|
},
|
||||||
|
enabled = !ImageUtil.HARDWARE_BITMAP_UNSUPPORTED &&
|
||||||
|
GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT,
|
||||||
|
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
|
||||||
|
.mapIndexed { index, option ->
|
||||||
|
val display = if (index == 0) {
|
||||||
|
stringResource(MR.strings.pref_hardware_bitmap_threshold_default, option)
|
||||||
|
} else {
|
||||||
|
option.toString()
|
||||||
|
}
|
||||||
|
option to display
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
.toImmutableMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = basePreferences.alwaysDecodeLongStripWithSSIV(),
|
||||||
|
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv),
|
||||||
|
subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary),
|
||||||
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(MR.strings.pref_display_profile),
|
title = stringResource(MR.strings.pref_display_profile),
|
||||||
subtitle = basePreferences.displayProfile().get(),
|
subtitle = basePreferences.displayProfile().get(),
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle),
|
subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!SyncDataJob.isRunning(context)) {
|
if (!SyncDataJob.isRunning(context)) {
|
||||||
SyncDataJob.startNow(context)
|
SyncDataJob.startNow(context, manual = true)
|
||||||
} else {
|
} else {
|
||||||
context.toast(SYMR.strings.sync_in_progress)
|
context.toast(SYMR.strings.sync_in_progress)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -15,7 +15,6 @@ import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
|||||||
import kotlinx.collections.immutable.persistentListOf
|
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.runBlocking
|
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.download.service.DownloadPreferences
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
@@ -35,7 +34,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val getCategories = remember { Injekt.get<GetCategories>() }
|
val getCategories = remember { Injekt.get<GetCategories>() }
|
||||||
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() })
|
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
||||||
|
|
||||||
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
|
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
|
||||||
return listOf(
|
return listOf(
|
||||||
|
|||||||
+1
-4
@@ -25,7 +25,6 @@ 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 kotlinx.coroutines.runBlocking
|
|
||||||
import tachiyomi.domain.UnsortedPreferences
|
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
|
||||||
@@ -57,9 +56,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
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(
|
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
||||||
initial = runBlocking { getCategories.await() },
|
|
||||||
)
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|||||||
+1
-1
@@ -139,7 +139,7 @@ object SettingsMangadexScreen : SearchableSettings {
|
|||||||
title = mdex.name + " Login",
|
title = mdex.name + " Login",
|
||||||
content = {
|
content = {
|
||||||
BasePreferenceWidget(
|
BasePreferenceWidget(
|
||||||
title = it.title,
|
title = mdex.name + " Login",
|
||||||
widget = {
|
widget = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.PeopleAlt,
|
imageVector = Icons.Outlined.PeopleAlt,
|
||||||
|
|||||||
+10
@@ -40,6 +40,7 @@ import androidx.compose.ui.text.input.VisualTransformation
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.domain.track.model.AutoTrackState
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
@@ -53,6 +54,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
|
|||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toPersistentMap
|
||||||
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.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
@@ -85,6 +87,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
|
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
|
||||||
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
||||||
val sourceManager = remember { Injekt.get<SourceManager>() }
|
val sourceManager = remember { Injekt.get<SourceManager>() }
|
||||||
|
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead()
|
||||||
|
|
||||||
var dialog by remember { mutableStateOf<Any?>(null) }
|
var dialog by remember { mutableStateOf<Any?>(null) }
|
||||||
dialog?.run {
|
dialog?.run {
|
||||||
@@ -125,6 +128,13 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
pref = trackPreferences.autoUpdateTrack(),
|
pref = trackPreferences.autoUpdateTrack(),
|
||||||
title = stringResource(MR.strings.pref_auto_update_manga_sync),
|
title = stringResource(MR.strings.pref_auto_update_manga_sync),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = trackPreferences.autoUpdateTrackOnMarkRead(),
|
||||||
|
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
|
||||||
|
entries = AutoTrackState.entries
|
||||||
|
.associateWith { stringResource(it.titleRes) }
|
||||||
|
.toPersistentMap(),
|
||||||
|
),
|
||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.services),
|
title = stringResource(MR.strings.services),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ fun ReaderAppBars(
|
|||||||
enabledPrevious: Boolean,
|
enabledPrevious: Boolean,
|
||||||
currentPage: Int,
|
currentPage: Int,
|
||||||
totalPages: Int,
|
totalPages: Int,
|
||||||
onSliderValueChange: (Int) -> Unit,
|
onPageIndexChange: (Int) -> Unit,
|
||||||
|
|
||||||
readingMode: ReadingMode,
|
readingMode: ReadingMode,
|
||||||
onClickReadingMode: () -> Unit,
|
onClickReadingMode: () -> Unit,
|
||||||
@@ -154,7 +154,7 @@ fun ReaderAppBars(
|
|||||||
enabledPrevious = enabledPrevious,
|
enabledPrevious = enabledPrevious,
|
||||||
currentPage = currentPage,
|
currentPage = currentPage,
|
||||||
totalPages = totalPages,
|
totalPages = totalPages,
|
||||||
onSliderValueChange = onSliderValueChange,
|
onPageIndexChange = onPageIndexChange,
|
||||||
isVerticalSlider = true,
|
isVerticalSlider = true,
|
||||||
currentPageText = currentPageText,
|
currentPageText = currentPageText,
|
||||||
)
|
)
|
||||||
@@ -182,7 +182,7 @@ fun ReaderAppBars(
|
|||||||
enabledPrevious = enabledPrevious,
|
enabledPrevious = enabledPrevious,
|
||||||
currentPage = currentPage,
|
currentPage = currentPage,
|
||||||
totalPages = totalPages,
|
totalPages = totalPages,
|
||||||
onSliderValueChange = onSliderValueChange,
|
onPageIndexChange = onPageIndexChange,
|
||||||
isVerticalSlider = true,
|
isVerticalSlider = true,
|
||||||
currentPageText = currentPageText,
|
currentPageText = currentPageText,
|
||||||
)
|
)
|
||||||
@@ -285,7 +285,7 @@ fun ReaderAppBars(
|
|||||||
enabledPrevious = enabledPrevious,
|
enabledPrevious = enabledPrevious,
|
||||||
currentPage = currentPage,
|
currentPage = currentPage,
|
||||||
totalPages = totalPages,
|
totalPages = totalPages,
|
||||||
onSliderValueChange = onSliderValueChange,
|
onPageIndexChange = onPageIndexChange,
|
||||||
isVerticalSlider = false,
|
isVerticalSlider = false,
|
||||||
currentPageText = currentPageText,
|
currentPageText = currentPageText,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import androidx.compose.material3.FilledIconButton
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButtonDefaults
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Slider
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -45,8 +44,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.presentation.util.isTabletUi
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.Slider
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterNavigator(
|
fun ChapterNavigator(
|
||||||
@@ -61,7 +60,7 @@ fun ChapterNavigator(
|
|||||||
currentPageText: String,
|
currentPageText: String,
|
||||||
// SY <--
|
// SY <--
|
||||||
totalPages: Int,
|
totalPages: Int,
|
||||||
onSliderValueChange: (Int) -> Unit,
|
onPageIndexChange: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
// SY -->
|
// SY -->
|
||||||
if (isVerticalSlider) {
|
if (isVerticalSlider) {
|
||||||
@@ -73,7 +72,7 @@ fun ChapterNavigator(
|
|||||||
currentPage = currentPage,
|
currentPage = currentPage,
|
||||||
currentPageText = currentPageText,
|
currentPageText = currentPageText,
|
||||||
totalPages = totalPages,
|
totalPages = totalPages,
|
||||||
onSliderValueChange = onSliderValueChange,
|
onPageIndexChange = onPageIndexChange,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -138,14 +137,11 @@ fun ChapterNavigator(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(horizontal = 8.dp),
|
.padding(horizontal = 8.dp),
|
||||||
value = currentPage.toFloat(),
|
value = currentPage,
|
||||||
valueRange = 1f..totalPages.toFloat(),
|
valueRange = 1..totalPages,
|
||||||
steps = totalPages - 2,
|
onValueChange = f@{
|
||||||
onValueChange = {
|
if (it == currentPage) return@f
|
||||||
val new = it.roundToInt() - 1
|
onPageIndexChange(it - 1)
|
||||||
if (new != currentPage) {
|
|
||||||
onSliderValueChange(new)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
@@ -184,7 +180,7 @@ fun ChapterNavigatorVert(
|
|||||||
currentPageText: String,
|
currentPageText: String,
|
||||||
// SY <--
|
// SY <--
|
||||||
totalPages: Int,
|
totalPages: Int,
|
||||||
onSliderValueChange: (Int) -> Unit,
|
onPageIndexChange: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
val isTabletUi = isTabletUi()
|
val isTabletUi = isTabletUi()
|
||||||
val verticalPadding = if (isTabletUi) 24.dp else 8.dp
|
val verticalPadding = if (isTabletUi) 24.dp else 8.dp
|
||||||
@@ -259,11 +255,11 @@ fun ChapterNavigatorVert(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
value = currentPage.toFloat(),
|
value = currentPage,
|
||||||
valueRange = 1f..totalPages.toFloat(),
|
valueRange = 1..totalPages,
|
||||||
steps = totalPages,
|
onValueChange = f@{
|
||||||
onValueChange = {
|
if (it == currentPage) return@f
|
||||||
onSliderValueChange(it.roundToInt() - 1)
|
onPageIndexChange(it - 1)
|
||||||
},
|
},
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
@@ -301,7 +297,7 @@ private fun ChapterNavigatorPreview() {
|
|||||||
enabledPrevious = true,
|
enabledPrevious = true,
|
||||||
currentPage = currentPage,
|
currentPage = currentPage,
|
||||||
totalPages = 10,
|
totalPages = 10,
|
||||||
onSliderValueChange = { currentPage = it },
|
onPageIndexChange = { currentPage = (it + 1) },
|
||||||
// SY -->
|
// SY -->
|
||||||
currentPageText = "1",
|
currentPageText = "1",
|
||||||
isVerticalSlider = false,
|
isVerticalSlider = false,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.work.Configuration
|
||||||
|
import androidx.work.WorkManager
|
||||||
import coil3.ImageLoader
|
import coil3.ImageLoader
|
||||||
import coil3.SingletonImageLoader
|
import coil3.SingletonImageLoader
|
||||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||||
@@ -56,6 +58,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||||
@@ -78,6 +81,7 @@ import org.conscrypt.Conscrypt
|
|||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.storage.service.StorageManager
|
import tachiyomi.domain.storage.service.StorageManager
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -173,6 +177,14 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
.onEach(FirebaseConfig::setCrashlyticsEnabled)
|
.onEach(FirebaseConfig::setCrashlyticsEnabled)
|
||||||
.launchIn(scope)
|
.launchIn(scope)
|
||||||
|
|
||||||
|
basePreferences.hardwareBitmapThreshold().let { preference ->
|
||||||
|
if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
basePreferences.hardwareBitmapThreshold().changes()
|
||||||
|
.onEach { ImageUtil.hardwareBitmapThreshold = it }
|
||||||
|
.launchIn(scope)
|
||||||
|
|
||||||
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
|
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
|
||||||
|
|
||||||
// Updates widget update
|
// Updates widget update
|
||||||
@@ -182,6 +194,9 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
if (!WorkManager.isInitialized()) {
|
||||||
|
WorkManager.initialize(this, Configuration.Builder().build())
|
||||||
|
}
|
||||||
val syncPreferences: SyncPreferences = Injekt.get()
|
val syncPreferences: SyncPreferences = Injekt.get()
|
||||||
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
|
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
|
||||||
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
|
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
|
||||||
@@ -283,12 +298,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
|
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
|
||||||
}
|
}
|
||||||
|
|
||||||
val syncPreferences: SyncPreferences = Injekt.get()
|
|
||||||
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
|
|
||||||
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
|
|
||||||
SyncDataJob.startNow(this@App)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXH
|
// EXH
|
||||||
|
|||||||
+2
-1
@@ -202,6 +202,7 @@ class MangaRestorer(
|
|||||||
bookmark = chapter.bookmark || dbChapter.bookmark,
|
bookmark = chapter.bookmark || dbChapter.bookmark,
|
||||||
read = chapter.read,
|
read = chapter.read,
|
||||||
lastPageRead = chapter.lastPageRead,
|
lastPageRead = chapter.lastPageRead,
|
||||||
|
sourceOrder = chapter.sourceOrder,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
chapter.copyFrom(dbChapter).let {
|
chapter.copyFrom(dbChapter).let {
|
||||||
@@ -252,7 +253,7 @@ class MangaRestorer(
|
|||||||
bookmark = chapter.bookmark,
|
bookmark = chapter.bookmark,
|
||||||
lastPageRead = chapter.lastPageRead,
|
lastPageRead = chapter.lastPageRead,
|
||||||
chapterNumber = null,
|
chapterNumber = null,
|
||||||
sourceOrder = null,
|
sourceOrder = if (isSync) chapter.sourceOrder else null,
|
||||||
dateFetch = null,
|
dateFetch = null,
|
||||||
dateUpload = null,
|
dateUpload = null,
|
||||||
chapterId = chapter.id,
|
chapterId = chapter.id,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import coil3.request.bitmapConfig
|
|||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream
|
import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream
|
||||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
|
||||||
import mihon.core.common.archive.archiveReader
|
import mihon.core.common.archive.archiveReader
|
||||||
import okio.BufferedSource
|
import okio.BufferedSource
|
||||||
import tachiyomi.core.common.util.system.ImageUtil
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
@@ -71,7 +70,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
|
|||||||
if (
|
if (
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||||
options.bitmapConfig == Bitmap.Config.HARDWARE &&
|
options.bitmapConfig == Bitmap.Config.HARDWARE &&
|
||||||
maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize
|
ImageUtil.canUseHardwareBitmap(bitmap)
|
||||||
) {
|
) {
|
||||||
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
|
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
|
||||||
if (hwBitmap != null) {
|
if (hwBitmap != null) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
|
@file:Suppress("PropertyName")
|
||||||
|
|
||||||
package eu.kanade.tachiyomi.data.database.models
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
|
@file:Suppress("PropertyName")
|
||||||
|
|
||||||
package eu.kanade.tachiyomi.data.database.models
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
|
@file:Suppress("PropertyName")
|
||||||
|
|
||||||
package eu.kanade.tachiyomi.data.database.models
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
|
@file:Suppress("PropertyName")
|
||||||
|
|
||||||
package eu.kanade.tachiyomi.data.database.models
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.library
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.NetworkRequest
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
@@ -135,10 +137,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
if (tags.contains(WORK_NAME_AUTO)) {
|
if (tags.contains(WORK_NAME_AUTO)) {
|
||||||
val preferences = Injekt.get<LibraryPreferences>()
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
val preferences = Injekt.get<LibraryPreferences>()
|
||||||
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
|
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
||||||
return Result.retry()
|
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
|
||||||
|
return Result.retry()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a running manual worker. If exists, try again later
|
// Find a running manual worker. If exists, try again later
|
||||||
@@ -404,7 +408,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
it.read
|
it.read
|
||||||
}
|
}
|
||||||
val newReadChapters = this.filter { chapter ->
|
val newReadChapters = this.filter { chapter ->
|
||||||
chapter.chapterNumber > 0 &&
|
chapter.chapterNumber >= 0 &&
|
||||||
readChapters.any {
|
readChapters.any {
|
||||||
it.chapterNumber == chapter.chapterNumber
|
it.chapterNumber == chapter.chapterNumber
|
||||||
}
|
}
|
||||||
@@ -768,15 +772,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
|
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
||||||
val constraints = Constraints(
|
val networkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
|
||||||
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
|
NetworkType.UNMETERED
|
||||||
NetworkType.UNMETERED
|
} else {
|
||||||
} else {
|
NetworkType.CONNECTED
|
||||||
NetworkType.CONNECTED
|
}
|
||||||
},
|
val networkRequestBuilder = NetworkRequest.Builder()
|
||||||
requiresCharging = DEVICE_CHARGING in restrictions,
|
if (DEVICE_ONLY_ON_WIFI in restrictions) {
|
||||||
requiresBatteryNotLow = true,
|
networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||||
)
|
}
|
||||||
|
if (DEVICE_NETWORK_NOT_METERED in restrictions) {
|
||||||
|
networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||||
|
}
|
||||||
|
val constraints = Constraints.Builder()
|
||||||
|
// 'networkRequest' only applies to Android 9+, otherwise 'networkType' is used
|
||||||
|
.setRequiredNetworkRequest(networkRequestBuilder.build(), networkType)
|
||||||
|
.setRequiresCharging(DEVICE_CHARGING in restrictions)
|
||||||
|
.setRequiresBatteryNotLow(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
||||||
interval.toLong(),
|
interval.toLong(),
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ object Notifications {
|
|||||||
const val ID_LIBRARY_SIZE_WARNING = -103
|
const val ID_LIBRARY_SIZE_WARNING = -103
|
||||||
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
|
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
|
||||||
const val ID_LIBRARY_ERROR = -102
|
const val ID_LIBRARY_ERROR = -102
|
||||||
|
const val CHANNEL_LIBRARY_EHENTAI = "library_ehentai_channel"
|
||||||
|
const val ID_EHENTAI_PROGRESS = -199
|
||||||
|
const val ID_EHENTAI_ERROR = -198
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification channel and ids used by the downloader.
|
* Notification channel and ids used by the downloader.
|
||||||
@@ -71,6 +74,7 @@ object Notifications {
|
|||||||
const val CHANNEL_APP_UPDATE = "app_apk_update_channel"
|
const val CHANNEL_APP_UPDATE = "app_apk_update_channel"
|
||||||
const val ID_APP_UPDATER = 1
|
const val ID_APP_UPDATER = 1
|
||||||
const val ID_APP_UPDATE_PROMPT = 2
|
const val ID_APP_UPDATE_PROMPT = 2
|
||||||
|
const val ID_APP_UPDATE_ERROR = 3
|
||||||
const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
|
const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
|
||||||
const val ID_UPDATES_TO_EXTS = -401
|
const val ID_UPDATES_TO_EXTS = -401
|
||||||
const val ID_EXTENSION_INSTALLER = -402
|
const val ID_EXTENSION_INSTALLER = -402
|
||||||
@@ -166,6 +170,13 @@ object Notifications {
|
|||||||
setGroup(GROUP_APK_UPDATES)
|
setGroup(GROUP_APK_UPDATES)
|
||||||
setName(context.stringResource(MR.strings.channel_ext_updates))
|
setName(context.stringResource(MR.strings.channel_ext_updates))
|
||||||
},
|
},
|
||||||
|
// SY -->
|
||||||
|
buildNotificationChannel(CHANNEL_LIBRARY_EHENTAI, IMPORTANCE_LOW) {
|
||||||
|
setName("EHentai")
|
||||||
|
setGroup(GROUP_LIBRARY)
|
||||||
|
setShowBadge(false)
|
||||||
|
},
|
||||||
|
// SY <--
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ sealed class Image(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed interface Location {
|
sealed interface Location {
|
||||||
|
@ConsistentCopyVisibility
|
||||||
data class Pictures private constructor(val relativePath: String) : Location {
|
data class Pictures private constructor(val relativePath: String) : Location {
|
||||||
companion object {
|
companion object {
|
||||||
fun create(relativePath: String = ""): Pictures {
|
fun create(relativePath: String = ""): Pictures {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.work.WorkerParameters
|
|||||||
import eu.kanade.domain.sync.SyncPreferences
|
import eu.kanade.domain.sync.SyncPreferences
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||||
|
import eu.kanade.tachiyomi.util.system.isOnline
|
||||||
import eu.kanade.tachiyomi.util.system.isRunning
|
import eu.kanade.tachiyomi.util.system.isRunning
|
||||||
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
@@ -31,6 +32,9 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
|
|||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
if (tags.contains(TAG_AUTO)) {
|
if (tags.contains(TAG_AUTO)) {
|
||||||
|
if (!context.isOnline()) {
|
||||||
|
return Result.retry()
|
||||||
|
}
|
||||||
// Find a running manual worker. If exists, try again later
|
// Find a running manual worker. If exists, try again later
|
||||||
if (context.workManager.isRunning(TAG_MANUAL)) {
|
if (context.workManager.isRunning(TAG_MANUAL)) {
|
||||||
return Result.retry()
|
return Result.retry()
|
||||||
@@ -93,17 +97,18 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startNow(context: Context) {
|
fun startNow(context: Context, manual: Boolean = false) {
|
||||||
val wm = context.workManager
|
val wm = context.workManager
|
||||||
if (wm.isRunning(TAG_JOB)) {
|
if (wm.isRunning(TAG_JOB)) {
|
||||||
// Already running either as a scheduled or manual job
|
// Already running either as a scheduled or manual job
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
val tag = if (manual) TAG_MANUAL else TAG_AUTO
|
||||||
val request = OneTimeWorkRequestBuilder<SyncDataJob>()
|
val request = OneTimeWorkRequestBuilder<SyncDataJob>()
|
||||||
.addTag(TAG_JOB)
|
.addTag(TAG_JOB)
|
||||||
.addTag(TAG_MANUAL)
|
.addTag(tag)
|
||||||
.build()
|
.build()
|
||||||
context.workManager.enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request)
|
context.workManager.enqueueUniqueWork(tag, ExistingWorkPolicy.KEEP, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop(context: Context) {
|
fun stop(context: Context) {
|
||||||
|
|||||||
@@ -233,7 +233,12 @@ abstract class SyncService(
|
|||||||
localChapter != null && remoteChapter != null -> {
|
localChapter != null && remoteChapter != null -> {
|
||||||
// Use version number to decide which chapter to keep
|
// Use version number to decide which chapter to keep
|
||||||
val chosenChapter = if (localChapter.version >= remoteChapter.version) {
|
val chosenChapter = if (localChapter.version >= remoteChapter.version) {
|
||||||
localChapter
|
// If there mare more chapter on remote, local sourceOrder will need to be updated to maintain correct source order.
|
||||||
|
if (localChapters.size < remoteChapters.size) {
|
||||||
|
localChapter.copy(sourceOrder = remoteChapter.sourceOrder)
|
||||||
|
} else {
|
||||||
|
localChapter
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
remoteChapter
|
remoteChapter
|
||||||
}
|
}
|
||||||
@@ -500,6 +505,7 @@ abstract class SyncService(
|
|||||||
logcat(LogPriority.DEBUG, logTag) { "Using remote saved search: ${remoteSearch.name}." }
|
logcat(LogPriority.DEBUG, logTag) { "Using remote saved search: ${remoteSearch.name}." }
|
||||||
remoteSearch
|
remoteSearch
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
logcat(LogPriority.DEBUG, logTag) {
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
"No saved search found for composite key: $compositeKey. Skipping."
|
"No saved search found for composite key: $compositeKey. Skipping."
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import eu.kanade.domain.track.interactor.AddTracks
|
|||||||
import eu.kanade.domain.track.model.toDomainTrack
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -120,6 +121,10 @@ abstract class BaseTracker(
|
|||||||
updateRemote(track)
|
updateRemote(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
throw NotImplementedError("Not implemented.")
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun updateRemote(track: Track): Unit = withIOContext {
|
private suspend fun updateRemote(track: Track): Unit = withIOContext {
|
||||||
try {
|
try {
|
||||||
update(track)
|
update(track)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.annotation.ColorInt
|
|||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -82,4 +83,6 @@ interface Tracker {
|
|||||||
suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
|
suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
|
||||||
|
|
||||||
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
|
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@@ -232,6 +233,10 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
interceptor.setAuth(null)
|
interceptor.setAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
fun saveOAuth(alOAuth: ALOAuth?) {
|
fun saveOAuth(alOAuth: ALOAuth?) {
|
||||||
trackPreferences.trackToken(this).set(json.encodeToString(alOAuth))
|
trackPreferences.trackToken(this).set(json.encodeToString(alOAuth))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,18 @@ import androidx.core.net.toUri
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserListMangaQueryResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserListMangaQueryResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.network.jsonMime
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import eu.kanade.tachiyomi.util.lang.htmlDecode
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonNull
|
import kotlinx.serialization.json.JsonNull
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@@ -288,6 +291,71 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return withIOContext {
|
||||||
|
val query = """
|
||||||
|
|query (${'$'}mangaId: Int!) {
|
||||||
|
|Media (id: ${'$'}mangaId) {
|
||||||
|
|id
|
||||||
|
|title {
|
||||||
|
|userPreferred
|
||||||
|
|}
|
||||||
|
|coverImage {
|
||||||
|
|large
|
||||||
|
|}
|
||||||
|
|description
|
||||||
|
|staff {
|
||||||
|
|edges {
|
||||||
|
|role
|
||||||
|
|node {
|
||||||
|
|name {
|
||||||
|
|userPreferred
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("query", query)
|
||||||
|
putJsonObject("variables") {
|
||||||
|
put("mangaId", track.remoteId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
API_URL,
|
||||||
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<ALMangaMetadata>()
|
||||||
|
.let {
|
||||||
|
val media = it.data.media
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = media.id,
|
||||||
|
title = media.title.userPreferred,
|
||||||
|
thumbnailUrl = media.coverImage.large,
|
||||||
|
description = media.description?.htmlDecode()?.ifEmpty { null },
|
||||||
|
authors = media.staff.edges
|
||||||
|
.filter { it.role == "Story" || it.role == "Story & Art" }
|
||||||
|
.map { it.node.name.userPreferred }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
artists = media.staff.edges
|
||||||
|
.filter { it.role == "Art" || it.role == "Story & Art" }
|
||||||
|
.map { it.node.name.userPreferred }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createDate(dateValue: Long): JsonObject {
|
private fun createDate(dateValue: Long): JsonObject {
|
||||||
if (dateValue == 0L) {
|
if (dateValue == 0L) {
|
||||||
return buildJsonObject {
|
return buildJsonObject {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.anilist.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALMangaMetadata(
|
||||||
|
val data: ALMangaMetadataData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALMangaMetadataData(
|
||||||
|
@SerialName("Media")
|
||||||
|
val media: ALMangaMetadataMedia,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALMangaMetadataMedia(
|
||||||
|
val id: Long,
|
||||||
|
val title: ALItemTitle,
|
||||||
|
val coverImage: ItemCover,
|
||||||
|
val description: String?,
|
||||||
|
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,
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -75,6 +76,10 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
return api.search(query)
|
return api.search(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun refresh(track: Track): Track {
|
override suspend fun refresh(track: Track): Track {
|
||||||
val remoteStatusTrack = api.statusLibManga(track) ?: throw Exception("Could not find manga")
|
val remoteStatusTrack = api.statusLibManga(track) ?: throw Exception("Could not find manga")
|
||||||
track.copyPersonalFrom(remoteStatusTrack)
|
track.copyPersonalFrom(remoteStatusTrack)
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMCollectionResponse
|
|||||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchItem
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchItem
|
||||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchResult
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSubject
|
||||||
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.Infobox
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
@@ -21,6 +24,7 @@ import tachiyomi.core.common.util.lang.withIOContext
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class BangumiApi(
|
class BangumiApi(
|
||||||
private val trackId: Long,
|
private val trackId: Long,
|
||||||
@@ -71,6 +75,8 @@ class BangumiApi(
|
|||||||
val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
|
val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
|
||||||
.toUri()
|
.toUri()
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
|
.appendQueryParameter("type", "1")
|
||||||
|
.appendQueryParameter("responseGroup", "large")
|
||||||
.appendQueryParameter("max_results", "20")
|
.appendQueryParameter("max_results", "20")
|
||||||
.build()
|
.build()
|
||||||
with(json) {
|
with(json) {
|
||||||
@@ -81,7 +87,6 @@ class BangumiApi(
|
|||||||
if (result.code == 404) emptyList<TrackSearch>()
|
if (result.code == 404) emptyList<TrackSearch>()
|
||||||
|
|
||||||
result.list
|
result.list
|
||||||
?.filter { it.type == 1 }
|
|
||||||
?.map { it.toTrackSearch(trackId) }
|
?.map { it.toTrackSearch(trackId) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
}
|
}
|
||||||
@@ -126,6 +131,34 @@ class BangumiApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return withIOContext {
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(GET("${API_URL}/v0/subjects/${track.remoteId}"))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<BGMSubject>()
|
||||||
|
.let {
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = it.id,
|
||||||
|
title = it.nameCn,
|
||||||
|
thumbnailUrl = it.images?.common,
|
||||||
|
description = it.summary,
|
||||||
|
authors = it.infobox
|
||||||
|
.filter { it.key == "作者" }
|
||||||
|
.filterIsInstance<Infobox.SingleValue>()
|
||||||
|
.map { it.value }
|
||||||
|
.joinToString(", "),
|
||||||
|
artists = it.infobox
|
||||||
|
.filter { it.key == "插图" }
|
||||||
|
.filterIsInstance<Infobox.SingleValue>()
|
||||||
|
.map { it.value }
|
||||||
|
.joinToString(", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun accessToken(code: String): BGMOAuth {
|
suspend fun accessToken(code: String): BGMOAuth {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
with(json) {
|
with(json) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ data class BGMSearchItem(
|
|||||||
val nameCn: String,
|
val nameCn: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val type: Int,
|
val type: Int,
|
||||||
|
val summary: String?,
|
||||||
val images: BGMSearchItemCovers?,
|
val images: BGMSearchItemCovers?,
|
||||||
@SerialName("eps_count")
|
@SerialName("eps_count")
|
||||||
val epsCount: Long?,
|
val epsCount: Long?,
|
||||||
@@ -25,9 +26,13 @@ data class BGMSearchItem(
|
|||||||
) {
|
) {
|
||||||
fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply {
|
fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply {
|
||||||
remote_id = this@BGMSearchItem.id
|
remote_id = this@BGMSearchItem.id
|
||||||
title = nameCn
|
title = nameCn.ifBlank { name }
|
||||||
cover_url = images?.common ?: ""
|
cover_url = images?.common.orEmpty()
|
||||||
summary = this@BGMSearchItem.name
|
summary = if (nameCn.isNotBlank()) {
|
||||||
|
"作品原名:$name" + this@BGMSearchItem.summary?.let { "\n$it" }.orEmpty()
|
||||||
|
} else {
|
||||||
|
this@BGMSearchItem.summary.orEmpty()
|
||||||
|
}
|
||||||
score = rating?.score ?: -1.0
|
score = rating?.score ?: -1.0
|
||||||
tracking_url = url
|
tracking_url = url
|
||||||
total_chapters = epsCount ?: 0
|
total_chapters = epsCount ?: 0
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.bangumi.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BGMSubject(
|
||||||
|
val images: BGMSearchItemCovers?,
|
||||||
|
val summary: String,
|
||||||
|
val name: String,
|
||||||
|
@SerialName("name_cn")
|
||||||
|
val nameCn: String,
|
||||||
|
val infobox: List<Infobox>,
|
||||||
|
val id: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
// infobox deserializer and related classes courtesy of
|
||||||
|
// https://github.com/Snd-R/komf/blob/4c260a3dcd326a5e1d74ac9662eec8124ab7e461/komf-core/src/commonMain/kotlin/snd/komf/providers/bangumi/model/BangumiSubject.kt#L53-L89
|
||||||
|
object InfoBoxSerializer : JsonContentPolymorphicSerializer<Infobox>(Infobox::class) {
|
||||||
|
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Infobox> {
|
||||||
|
if (element !is JsonObject) throw SerializationException("Expected JsonObject go ${element::class}")
|
||||||
|
val value = element["value"]
|
||||||
|
|
||||||
|
return when (value) {
|
||||||
|
is JsonArray -> Infobox.MultipleValues.serializer()
|
||||||
|
is JsonPrimitive -> Infobox.SingleValue.serializer()
|
||||||
|
else -> throw SerializationException("Unexpected element type ${element::class}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(with = InfoBoxSerializer::class)
|
||||||
|
sealed interface Infobox {
|
||||||
|
val key: String
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SingleValue(
|
||||||
|
override val key: String,
|
||||||
|
val value: String,
|
||||||
|
) : Infobox
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MultipleValues(
|
||||||
|
override val key: String,
|
||||||
|
val value: List<InfoboxNestedValue>,
|
||||||
|
) : Infobox
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class InfoboxNestedValue(
|
||||||
|
@SerialName("k")
|
||||||
|
val key: String? = null,
|
||||||
|
@SerialName("v")
|
||||||
|
val value: String,
|
||||||
|
)
|
||||||
@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -139,6 +140,10 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
|||||||
interceptor.newAuth(null)
|
interceptor.newAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getUserId(): String {
|
private fun getUserId(): String {
|
||||||
return getPassword()
|
return getPassword()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAddMangaResult
|
|||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAlgoliaSearchResult
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAlgoliaSearchResult
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuCurrentUserResult
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuCurrentUserResult
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuListSearchResult
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuListSearchResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuSearchResult
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuSearchResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.DELETE
|
import eu.kanade.tachiyomi.network.DELETE
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
@@ -15,6 +17,7 @@ import eu.kanade.tachiyomi.network.POST
|
|||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.jsonMime
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import eu.kanade.tachiyomi.util.lang.htmlDecode
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
@@ -240,11 +243,80 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return withIOContext {
|
||||||
|
val query = """
|
||||||
|
|query(${'$'}libraryId: ID!, ${'$'}staffCount: Int) {
|
||||||
|
|findLibraryEntryById(id: ${'$'}libraryId) {
|
||||||
|
|media {
|
||||||
|
|id
|
||||||
|
|titles {
|
||||||
|
|preferred
|
||||||
|
|}
|
||||||
|
|posterImage {
|
||||||
|
|original {
|
||||||
|
|url
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|description
|
||||||
|
|staff(first: ${'$'}staffCount) {
|
||||||
|
|nodes {
|
||||||
|
|role
|
||||||
|
|person {
|
||||||
|
|name
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
""".trimMargin()
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("query", query)
|
||||||
|
putJsonObject("variables") {
|
||||||
|
put("libraryId", track.remoteId)
|
||||||
|
put("staffCount", 25) // 25 based on nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
GRAPHQL_URL,
|
||||||
|
headers = headersOf("Accept-Language", "en"),
|
||||||
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<KitsuMangaMetadata>()
|
||||||
|
.let {
|
||||||
|
val manga = it.data.findLibraryEntryById.media
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = manga.id.toLong(),
|
||||||
|
title = manga.titles.preferred,
|
||||||
|
thumbnailUrl = manga.posterImage.original.url,
|
||||||
|
description = manga.description.en?.htmlDecode()?.ifEmpty { null },
|
||||||
|
authors = manga.staff.nodes
|
||||||
|
.filter { it.role == "Story" || it.role == "Story & Art" }
|
||||||
|
.map { it.person.name }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
artists = manga.staff.nodes
|
||||||
|
.filter { it.role == "Art" || it.role == "Story & Art" }
|
||||||
|
.map { it.person.name }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
|
private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
|
||||||
private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
|
private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
|
||||||
|
|
||||||
private const val BASE_URL = "https://kitsu.app/api/edge/"
|
private const val BASE_URL = "https://kitsu.app/api/edge/"
|
||||||
|
private const val GRAPHQL_URL = "https://kitsu.app/api/graphql"
|
||||||
private const val LOGIN_URL = "https://kitsu.app/api/oauth/token"
|
private const val LOGIN_URL = "https://kitsu.app/api/oauth/token"
|
||||||
private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
|
private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
|
||||||
private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/"
|
private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/"
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.kitsu.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaMetadata(
|
||||||
|
val data: KitsuMangaMetadataData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaMetadataData(
|
||||||
|
val findLibraryEntryById: KitsuMangaMetadataById,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaMetadataById(
|
||||||
|
val media: KitsuMangaMetadataMedia,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaMetadataMedia(
|
||||||
|
val id: String,
|
||||||
|
val titles: KitsuMangaTitle,
|
||||||
|
val posterImage: KitsuMangaCover,
|
||||||
|
val description: KitsuMangaDescription,
|
||||||
|
val staff: KitsuMangaStaff,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaTitle(
|
||||||
|
val preferred: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaCover(
|
||||||
|
val original: KitsuMangaCoverUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaCoverUrl(
|
||||||
|
val url: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaDescription(
|
||||||
|
val en: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaStaff(
|
||||||
|
val nodes: List<KitsuMangaStaffNode>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaStaffNode(
|
||||||
|
val role: String,
|
||||||
|
val person: KitsuMangaStaffPerson,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaStaffPerson(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
@@ -10,7 +10,9 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MUListItem
|
|||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MURating
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MURating
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import eu.kanade.tachiyomi.util.lang.htmlDecode
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -117,6 +119,20 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
|||||||
interceptor.newAuth(authenticated.sessionToken)
|
interceptor.newAuth(authenticated.sessionToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
val series = api.getSeries(track)
|
||||||
|
return series?.let {
|
||||||
|
TrackMangaMetadata(
|
||||||
|
it.seriesId,
|
||||||
|
it.title?.htmlDecode(),
|
||||||
|
it.image?.url?.original,
|
||||||
|
it.description?.htmlDecode(),
|
||||||
|
it.authors?.filter { it.type == "Author" }?.joinToString(separator = ", ") { it.name ?: "" },
|
||||||
|
it.authors?.filter { it.type == "Artist" }?.joinToString(separator = ", ") { it.name ?: "" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun restoreSession(): String? {
|
fun restoreSession(): String? {
|
||||||
return trackPreferences.trackPassword(this).get().ifBlank { null }
|
return trackPreferences.trackPassword(this).get().ifBlank { null }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,6 +190,14 @@ class MangaUpdatesApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getSeries(track: DomainTrack): MURecord {
|
||||||
|
return with(json) {
|
||||||
|
client.newCall(GET("$BASE_URL/v1/series/${track.remoteId}"))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<MURecord>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BASE_URL = "https://api.mangaupdates.com"
|
private const val BASE_URL = "https://api.mangaupdates.com"
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ data class MURecord(
|
|||||||
val ratingVotes: Int? = null,
|
val ratingVotes: Int? = null,
|
||||||
@SerialName("latest_chapter")
|
@SerialName("latest_chapter")
|
||||||
val latestChapter: Int? = null,
|
val latestChapter: Int? = null,
|
||||||
|
val authors: List<MUAuthor>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun MURecord.toTrackSearch(id: Long): TrackSearch {
|
fun MURecord.toTrackSearch(id: Long): TrackSearch {
|
||||||
@@ -36,3 +37,9 @@ fun MURecord.toTrackSearch(id: Long): TrackSearch {
|
|||||||
start_date = this@toTrackSearch.year.toString()
|
start_date = this@toTrackSearch.year.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MUAuthor(
|
||||||
|
val type: String? = null,
|
||||||
|
val name: String? = null,
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package eu.kanade.tachiyomi.data.track.mdlist
|
|||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
@@ -168,6 +170,21 @@ class MdList(id: Long) : BaseTracker(id, "MDList") {
|
|||||||
trackPreferences.trackToken(this).delete()
|
trackPreferences.trackToken(this).delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return withIOContext {
|
||||||
|
val mdex = mdex ?: throw MangaDexNotFoundException()
|
||||||
|
val manga = mdex.getMangaMetadata(track.toDbTrack())
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = 0,
|
||||||
|
title = manga?.title,
|
||||||
|
thumbnailUrl = manga?.thumbnail_url, // Doesn't load the actual cover because of Refer header
|
||||||
|
description = manga?.description,
|
||||||
|
authors = manga?.author,
|
||||||
|
artists = manga?.artist,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val isLoggedIn: Boolean
|
override val isLoggedIn: Boolean
|
||||||
get() = trackPreferences.trackToken(this).get().isNotEmpty()
|
get() = trackPreferences.trackToken(this).get().isNotEmpty()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.model
|
||||||
|
|
||||||
|
data class TrackMangaMetadata(
|
||||||
|
val remoteId: Long? = null,
|
||||||
|
val title: String? = null,
|
||||||
|
val thumbnailUrl: String? = null,
|
||||||
|
val description: String? = null,
|
||||||
|
val authors: String? = null,
|
||||||
|
val artists: String? = null,
|
||||||
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
|
@file:Suppress("PropertyName")
|
||||||
|
|
||||||
package eu.kanade.tachiyomi.data.track.model
|
package eu.kanade.tachiyomi.data.track.model
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -156,6 +157,10 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
|||||||
interceptor.setAuth(null)
|
interceptor.setAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
fun getIfAuthExpired(): Boolean {
|
fun getIfAuthExpired(): Boolean {
|
||||||
return trackPreferences.trackAuthExpired(this).get()
|
return trackPreferences.trackAuthExpired(this).get()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package eu.kanade.tachiyomi.data.track.myanimelist
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItem
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItem
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItemStatus
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItemStatus
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALManga
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALManga
|
||||||
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser
|
||||||
@@ -111,7 +113,7 @@ class MyAnimeListApi(
|
|||||||
summary = it.synopsis
|
summary = it.synopsis
|
||||||
total_chapters = it.numChapters
|
total_chapters = it.numChapters
|
||||||
score = it.mean
|
score = it.mean
|
||||||
cover_url = it.covers.large
|
cover_url = it.covers?.large.orEmpty()
|
||||||
tracking_url = "https://myanimelist.net/manga/$remote_id"
|
tracking_url = "https://myanimelist.net/manga/$remote_id"
|
||||||
publishing_status = it.status.replace("_", " ")
|
publishing_status = it.status.replace("_", " ")
|
||||||
publishing_type = it.mediaType.replace("_", " ")
|
publishing_type = it.mediaType.replace("_", " ")
|
||||||
@@ -193,6 +195,41 @@ class MyAnimeListApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return withIOContext {
|
||||||
|
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||||
|
.appendPath(track.remoteId.toString())
|
||||||
|
.appendQueryParameter(
|
||||||
|
"fields",
|
||||||
|
"id,title,synopsis,main_picture,authors{first_name,last_name}",
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(GET(url.toString()))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<MALMangaMetadata>()
|
||||||
|
.let {
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = it.id,
|
||||||
|
title = it.title,
|
||||||
|
thumbnailUrl = it.covers.large.ifEmpty { null } ?: it.covers.medium,
|
||||||
|
description = it.synopsis,
|
||||||
|
authors = it.authors
|
||||||
|
.filter { it.role == "Story" || it.role == "Story & Art" }
|
||||||
|
.map { "${it.node.firstName} ${it.node.lastName}".trim() }
|
||||||
|
.joinToString(separator = ", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
artists = it.authors
|
||||||
|
.filter { it.role == "Art" || it.role == "Story & Art" }
|
||||||
|
.map { "${it.node.firstName} ${it.node.lastName}".trim() }
|
||||||
|
.joinToString(separator = ", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getListPage(offset: Int): MALUserSearchResult {
|
private suspend fun getListPage(offset: Int): MALUserSearchResult {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
|
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ data class MALManga(
|
|||||||
val numChapters: Long,
|
val numChapters: Long,
|
||||||
val mean: Double = -1.0,
|
val mean: Double = -1.0,
|
||||||
@SerialName("main_picture")
|
@SerialName("main_picture")
|
||||||
val covers: MALMangaCovers,
|
val covers: MALMangaCovers?,
|
||||||
val status: String,
|
val status: String,
|
||||||
@SerialName("media_type")
|
@SerialName("media_type")
|
||||||
val mediaType: String,
|
val mediaType: String,
|
||||||
@@ -23,4 +23,29 @@ data class MALManga(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class MALMangaCovers(
|
data class MALMangaCovers(
|
||||||
val large: String = "",
|
val large: String = "",
|
||||||
|
val medium: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MALMangaMetadata(
|
||||||
|
val id: Long,
|
||||||
|
val title: String,
|
||||||
|
val synopsis: String?,
|
||||||
|
@SerialName("main_picture")
|
||||||
|
val covers: MALMangaCovers,
|
||||||
|
val authors: List<MALAuthor>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MALAuthor(
|
||||||
|
val node: MALAuthorNode,
|
||||||
|
val role: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MALAuthorNode(
|
||||||
|
@SerialName("first_name")
|
||||||
|
val firstName: String,
|
||||||
|
@SerialName("last_name")
|
||||||
|
val lastName: String,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -98,6 +99,10 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
|||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLogo() = R.drawable.ic_tracker_shikimori
|
override fun getLogo() = R.drawable.ic_tracker_shikimori
|
||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package eu.kanade.tachiyomi.data.track.shikimori
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMManga
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMManga
|
||||||
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListEntry
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListEntry
|
||||||
@@ -132,6 +134,65 @@ class ShikimoriApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return withIOContext {
|
||||||
|
val query = """
|
||||||
|
|query(${'$'}ids: String!) {
|
||||||
|
|mangas(ids: ${'$'}ids) {
|
||||||
|
|id
|
||||||
|
|name
|
||||||
|
|description
|
||||||
|
|poster {
|
||||||
|
|originalUrl
|
||||||
|
|}
|
||||||
|
|personRoles {
|
||||||
|
|person {
|
||||||
|
|name
|
||||||
|
|}
|
||||||
|
|rolesEn
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
""".trimMargin()
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("query", query)
|
||||||
|
putJsonObject("variables") {
|
||||||
|
put("ids", "${track.remoteId}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
"https://shikimori.one/api/graphql",
|
||||||
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<SMMetadata>()
|
||||||
|
.let {
|
||||||
|
if (it.data.mangas.isEmpty()) throw Exception("Could not get metadata from Shikimori")
|
||||||
|
val manga = it.data.mangas[0]
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = manga.id.toLong(),
|
||||||
|
title = manga.name,
|
||||||
|
thumbnailUrl = manga.poster.originalUrl,
|
||||||
|
description = manga.description,
|
||||||
|
authors = manga.personRoles
|
||||||
|
.filter { it.rolesEn.contains("Story") || it.rolesEn.contains("Story & Art") }
|
||||||
|
.map { it.person.name }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
artists = manga.personRoles
|
||||||
|
.filter { it.rolesEn.contains("Art") || it.rolesEn.contains("Story & Art") }
|
||||||
|
.map { it.person.name }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun accessToken(code: String): SMOAuth {
|
suspend fun accessToken(code: String): SMOAuth {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
with(json) {
|
with(json) {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.shikimori.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMetadata(
|
||||||
|
val data: SMMetadataData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMetadataData(
|
||||||
|
val mangas: List<SMMetadataResult>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMetadataResult(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val poster: SMMangaPoster,
|
||||||
|
val personRoles: List<SMMangaPersonRoles>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMangaPoster(
|
||||||
|
val originalUrl: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMangaPersonRoles(
|
||||||
|
val person: SMPerson,
|
||||||
|
val rolesEn: List<String>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMPerson(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
@@ -181,9 +181,9 @@ internal class AppUpdateNotifier(private val context: Context) {
|
|||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_close_24dp,
|
R.drawable.ic_close_24dp,
|
||||||
context.stringResource(MR.strings.action_cancel),
|
context.stringResource(MR.strings.action_cancel),
|
||||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATER),
|
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATE_ERROR),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
notificationBuilder.show(Notifications.ID_APP_UPDATER)
|
notificationBuilder.show(Notifications.ID_APP_UPDATE_ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,12 +89,25 @@ class ExtensionManager(
|
|||||||
|
|
||||||
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
|
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
|
||||||
|
|
||||||
fun getAppIconForSource(sourceId: Long): Drawable? {
|
fun getExtensionPackage(sourceId: Long): String? {
|
||||||
val pkgName = installedExtensionMapFlow.value.values
|
return installedExtensionsFlow.value.find { extension ->
|
||||||
.find { ext ->
|
extension.sources.any { it.id == sourceId }
|
||||||
ext.sources.any { it.id == sourceId }
|
}
|
||||||
}
|
|
||||||
?.pkgName
|
?.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getExtensionPackageAsFlow(sourceId: Long): Flow<String?> {
|
||||||
|
return installedExtensionsFlow.map { extensions ->
|
||||||
|
extensions.find { extension ->
|
||||||
|
extension.sources.any { it.id == sourceId }
|
||||||
|
}
|
||||||
|
?.pkgName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAppIconForSource(sourceId: Long): Drawable? {
|
||||||
|
val pkgName = getExtensionPackage(sourceId)
|
||||||
|
|
||||||
if (pkgName != null) {
|
if (pkgName != null) {
|
||||||
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
|
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
|
||||||
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo!!
|
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo!!
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import kotlinx.coroutines.cancel
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import rikka.shizuku.Shizuku
|
import rikka.shizuku.Shizuku
|
||||||
|
import rikka.shizuku.ShizukuRemoteProcess
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
class ShizukuInstaller(private val service: Service) : Installer(service) {
|
class ShizukuInstaller(private val service: Service) : Installer(service) {
|
||||||
|
|
||||||
@@ -93,9 +95,9 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val newProcess: Method
|
||||||
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
|
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
|
||||||
@Suppress("DEPRECATION")
|
val process = newProcess.invoke(null, arrayOf("sh", "-c", command), null, null) as ShizukuRemoteProcess
|
||||||
val process = Shizuku.newProcess(arrayOf("sh", "-c", command), null, null)
|
|
||||||
if (stdin != null) {
|
if (stdin != null) {
|
||||||
process.outputStream.use { stdin.copyTo(it) }
|
process.outputStream.use { stdin.copyTo(it) }
|
||||||
}
|
}
|
||||||
@@ -122,6 +124,9 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
|
|||||||
service.stopSelf()
|
service.stopSelf()
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
newProcess = Shizuku::class.java
|
||||||
|
.getDeclaredMethod("newProcess", Array<out String>::class.java, Array<out String>::class.java, String::class.java)
|
||||||
|
newProcess.isAccessible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class ExtensionInstallActivity : Activity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||||
.setDataAndType(intent.data, intent.type)
|
.setDataAndType(intent.data, intent.type)
|
||||||
.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
||||||
|
|||||||
@@ -449,7 +449,11 @@ class EHentai(
|
|||||||
private fun parseChapterPage(response: Element) = with(response) {
|
private fun parseChapterPage(response: Element) = with(response) {
|
||||||
select(".gdtm a").map {
|
select(".gdtm a").map {
|
||||||
Pair(it.child(0).attr("alt").toInt(), it.attr("href"))
|
Pair(it.child(0).attr("alt").toInt(), it.attr("href"))
|
||||||
}.sortedBy(Pair<Int, String>::first).map { it.second }
|
}.plus(
|
||||||
|
select("#gdt a").map {
|
||||||
|
Pair(it.child(0).attr("title").removePrefix("Page ").substringBefore(":").toInt(), it.attr("href"))
|
||||||
|
},
|
||||||
|
).sortedBy(Pair<Int, String>::first).map { it.second }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterPageCall(np: String): Observable<Response> {
|
private fun chapterPageCall(np: String): Observable<Response> {
|
||||||
@@ -1214,7 +1218,8 @@ class EHentai(
|
|||||||
|
|
||||||
val body = doc.body()
|
val body = doc.body()
|
||||||
val previews = body
|
val previews = body
|
||||||
.select("#gdt div div")
|
.select("#gdt > div > div")
|
||||||
|
.plus(body.select("#gdt > a"))
|
||||||
.map {
|
.map {
|
||||||
val preview = parseNormalPreview(it)
|
val preview = parseNormalPreview(it)
|
||||||
PagePreviewInfo(preview.index, imageUrl = preview.toUrl())
|
PagePreviewInfo(preview.index, imageUrl = preview.toUrl())
|
||||||
@@ -1250,8 +1255,15 @@ class EHentai(
|
|||||||
* Parse normal previews with regular expressions
|
* Parse normal previews with regular expressions
|
||||||
*/
|
*/
|
||||||
private fun parseNormalPreview(element: Element): EHentaiThumbnailPreview {
|
private fun parseNormalPreview(element: Element): EHentaiThumbnailPreview {
|
||||||
val index = element.selectFirst("img")!!.attr("alt").toInt()
|
val imgElement = element.selectFirst("img")
|
||||||
val styles = element.attr("style").split(";").mapNotNull { it.trimOrNull() }
|
val index = imgElement?.attr("alt")?.toInt()
|
||||||
|
?: element.child(0).attr("title").removePrefix("Page ").substringBefore(":").toInt()
|
||||||
|
val styleElement = if (imgElement != null) {
|
||||||
|
element
|
||||||
|
} else {
|
||||||
|
element.child(0)
|
||||||
|
}
|
||||||
|
val styles = styleElement.attr("style").split(";").mapNotNull { it.trimOrNull() }
|
||||||
val width = styles.first { it.startsWith("width:") }
|
val width = styles.first { it.startsWith("width:") }
|
||||||
.removePrefix("width:")
|
.removePrefix("width:")
|
||||||
.removeSuffix("px")
|
.removeSuffix("px")
|
||||||
@@ -1275,7 +1287,7 @@ class EHentai(
|
|||||||
.removeSuffix("px")
|
.removeSuffix("px")
|
||||||
.toInt()
|
.toInt()
|
||||||
|
|
||||||
return EHentaiThumbnailPreview(url, width, height, widthOffset, index).also(::println)
|
return EHentaiThumbnailPreview(url, width, height, widthOffset, index)
|
||||||
}
|
}
|
||||||
data class EHentaiThumbnailPreview(
|
data class EHentaiThumbnailPreview(
|
||||||
val imageUrl: String,
|
val imageUrl: String,
|
||||||
|
|||||||
@@ -313,6 +313,10 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
|||||||
return similarHandler.getRelated(manga)
|
return similarHandler.getRelated(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: Track): SManga? {
|
||||||
|
return mangaHandler.getMangaMetadata(track, id, coverQuality(), tryUsingFirstVolumeCover(), altTitlesInDesc())
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val dataSaverPref = "dataSaverV5"
|
private const val dataSaverPref = "dataSaverV5"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse
|
|||||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -27,14 +28,16 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
|||||||
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
|
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
data class BrowseTab(
|
data object BrowseTab : Tab {
|
||||||
private val toExtensions: Boolean = false,
|
|
||||||
) : Tab {
|
|
||||||
|
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
@Composable
|
@Composable
|
||||||
@@ -52,6 +55,12 @@ data class BrowseTab(
|
|||||||
navigator.push(GlobalSearchScreen())
|
navigator.push(GlobalSearchScreen())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val switchToExtensionTabChannel = Channel<Unit>(1, BufferOverflow.DROP_OLDEST)
|
||||||
|
|
||||||
|
fun showExtension() {
|
||||||
|
switchToExtensionTabChannel.trySend(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -65,35 +74,43 @@ data class BrowseTab(
|
|||||||
val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() }
|
val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() }
|
||||||
val extensionsState by extensionsScreenModel.state.collectAsState()
|
val extensionsState by extensionsScreenModel.state.collectAsState()
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
val tabs = if (hideFeedTab) {
|
||||||
|
persistentListOf(
|
||||||
|
sourcesTab(),
|
||||||
|
extensionsTab(extensionsScreenModel),
|
||||||
|
migrateSourceTab(),
|
||||||
|
)
|
||||||
|
} else if (feedTabInFront) {
|
||||||
|
persistentListOf(
|
||||||
|
feedTab(),
|
||||||
|
sourcesTab(),
|
||||||
|
extensionsTab(extensionsScreenModel),
|
||||||
|
migrateSourceTab(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
persistentListOf(
|
||||||
|
sourcesTab(),
|
||||||
|
feedTab(),
|
||||||
|
extensionsTab(extensionsScreenModel),
|
||||||
|
migrateSourceTab(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
|
val state = rememberPagerState { tabs.size }
|
||||||
|
|
||||||
TabbedScreen(
|
TabbedScreen(
|
||||||
titleRes = MR.strings.browse,
|
titleRes = MR.strings.browse,
|
||||||
// SY -->
|
tabs = tabs,
|
||||||
tabs = if (hideFeedTab) {
|
state = state,
|
||||||
persistentListOf(
|
|
||||||
sourcesTab(),
|
|
||||||
extensionsTab(extensionsScreenModel),
|
|
||||||
migrateSourceTab(),
|
|
||||||
)
|
|
||||||
} else if (feedTabInFront) {
|
|
||||||
persistentListOf(
|
|
||||||
feedTab(),
|
|
||||||
sourcesTab(),
|
|
||||||
extensionsTab(extensionsScreenModel),
|
|
||||||
migrateSourceTab(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
persistentListOf(
|
|
||||||
sourcesTab(),
|
|
||||||
feedTab(),
|
|
||||||
extensionsTab(extensionsScreenModel),
|
|
||||||
migrateSourceTab(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
startIndex = 2.takeIf { toExtensions },
|
|
||||||
// SY <--
|
|
||||||
searchQuery = extensionsState.searchQuery,
|
searchQuery = extensionsState.searchQuery,
|
||||||
onChangeSearchQuery = extensionsScreenModel::search,
|
onChangeSearchQuery = extensionsScreenModel::search,
|
||||||
)
|
)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
switchToExtensionTabChannel.receiveAsFlow()
|
||||||
|
.collectLatest { state.scrollToPage(/* SY --> */2/* SY <-- */) }
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
(context as? MainActivity)?.ready = true
|
(context as? MainActivity)?.ready = true
|
||||||
|
|||||||
+1
@@ -39,6 +39,7 @@ data class ExtensionDetailsScreen(
|
|||||||
onClickClearCookies = screenModel::clearCookies,
|
onClickClearCookies = screenModel::clearCookies,
|
||||||
onClickUninstall = screenModel::uninstallExtension,
|
onClickUninstall = screenModel::uninstallExtension,
|
||||||
onClickSource = screenModel::toggleSource,
|
onClickSource = screenModel::toggleSource,
|
||||||
|
onClickIncognito = screenModel::toggleIncognito,
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|||||||
+21
@@ -6,7 +6,9 @@ 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.extension.interactor.ExtensionSourceItem
|
import eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
|
import eu.kanade.domain.source.interactor.ToggleIncognito
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
@@ -19,6 +21,7 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
@@ -36,6 +39,8 @@ class ExtensionDetailsScreenModel(
|
|||||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||||
private val getExtensionSources: GetExtensionSources = Injekt.get(),
|
private val getExtensionSources: GetExtensionSources = Injekt.get(),
|
||||||
private val toggleSource: ToggleSource = Injekt.get(),
|
private val toggleSource: ToggleSource = Injekt.get(),
|
||||||
|
private val toggleIncognito: ToggleIncognito = Injekt.get(),
|
||||||
|
private val preferences: SourcePreferences = Injekt.get(),
|
||||||
) : StateScreenModel<ExtensionDetailsScreenModel.State>(State()) {
|
) : StateScreenModel<ExtensionDetailsScreenModel.State>(State()) {
|
||||||
|
|
||||||
private val _events: Channel<ExtensionDetailsEvent> = Channel()
|
private val _events: Channel<ExtensionDetailsEvent> = Channel()
|
||||||
@@ -80,6 +85,15 @@ class ExtensionDetailsScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
launch {
|
||||||
|
preferences.incognitoExtensions()
|
||||||
|
.changes()
|
||||||
|
.map { pkgName in it }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collectLatest { isIncognito ->
|
||||||
|
mutableState.update { it.copy(isIncognito = isIncognito) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,9 +132,16 @@ class ExtensionDetailsScreenModel(
|
|||||||
?.let { toggleSource.await(it, enable) }
|
?.let { toggleSource.await(it, enable) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleIncognito(enable: Boolean) {
|
||||||
|
state.value.extension?.pkgName?.let { packageName ->
|
||||||
|
toggleIncognito.await(packageName, enable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val extension: Extension.Installed? = null,
|
val extension: Extension.Installed? = null,
|
||||||
|
val isIncognito: Boolean = false,
|
||||||
private val _sources: ImmutableList<ExtensionSourceItem>? = null,
|
private val _sources: ImmutableList<ExtensionSourceItem>? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
@@ -77,6 +78,7 @@ open class FeedScreenModel(
|
|||||||
getFeedSavedSearchGlobal.subscribe()
|
getFeedSavedSearchGlobal.subscribe()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach {
|
.onEach {
|
||||||
|
sourceManager.isInitialized.first { it }
|
||||||
val items = getSourcesToGetFeed(it).map { (feed, savedSearch) ->
|
val items = getSourcesToGetFeed(it).map { (feed, savedSearch) ->
|
||||||
createCatalogueSearchItem(
|
createCatalogueSearchItem(
|
||||||
feed = feed,
|
feed = feed,
|
||||||
|
|||||||
+1
-4
@@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
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
|
||||||
@@ -29,8 +27,7 @@ import tachiyomi.i18n.sy.SYMR
|
|||||||
|
|
||||||
class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() {
|
class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() {
|
||||||
|
|
||||||
@delegate:Transient
|
var newSelectedItem: Pair<Long, Long>? = null
|
||||||
var newSelectedItem by mutableStateOf<Pair<Long, Long>?>(null)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
|
|||||||
+2
-3
@@ -8,7 +8,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
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
|
||||||
@@ -24,6 +23,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
|||||||
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
||||||
import exh.ui.ifSourcesLoaded
|
import exh.ui.ifSourcesLoaded
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import mihon.presentation.core.util.collectAsLazyPagingItems
|
||||||
import tachiyomi.core.common.Constants
|
import tachiyomi.core.common.Constants
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@@ -71,7 +71,6 @@ data class SourceSearchScreen(
|
|||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
|
|
||||||
val openMigrateDialog: (Manga) -> Unit = {
|
val openMigrateDialog: (Manga) -> Unit = {
|
||||||
// SY -->
|
// SY -->
|
||||||
navigator.items
|
navigator.items
|
||||||
@@ -83,7 +82,7 @@ data class SourceSearchScreen(
|
|||||||
}
|
}
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
mangaList = pagingFlow.collectAsLazyPagingItems(),
|
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
// SY -->
|
// SY -->
|
||||||
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
||||||
|
|||||||
+4
-1
@@ -1,12 +1,15 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
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.browse.BrowseTabWrapper
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
|
|
||||||
class MigrationSourcesScreen : Screen() {
|
class MigrationSourcesScreen : Screen() {
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
BrowseTabWrapper(migrateSourceTab())
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
BrowseTabWrapper(migrateSourceTab(), onBackPressed = navigator::pop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source
|
package eu.kanade.tachiyomi.ui.browse.source
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
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.browse.BrowseTabWrapper
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
@@ -8,7 +10,8 @@ import java.io.Serializable
|
|||||||
class SourcesScreen(private val smartSearchConfig: SmartSearchConfig?) : Screen() {
|
class SourcesScreen(private val smartSearchConfig: SmartSearchConfig?) : Screen() {
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
BrowseTabWrapper(sourcesTab(smartSearchConfig))
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
BrowseTabWrapper(sourcesTab(smartSearchConfig), onBackPressed = navigator::pop)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long? = null) : Serializable
|
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long? = null) : Serializable
|
||||||
|
|||||||
@@ -43,21 +43,25 @@ fun Screen.sourcesTab(
|
|||||||
true -> MR.strings.label_sources
|
true -> MR.strings.label_sources
|
||||||
false -> SYMR.strings.find_in_another_source
|
false -> SYMR.strings.find_in_another_source
|
||||||
},
|
},
|
||||||
actions = if (smartSearchConfig == null) {
|
actions = persistentListOf(
|
||||||
persistentListOf(
|
AppBar.Action(
|
||||||
AppBar.Action(
|
title = stringResource(MR.strings.action_global_search),
|
||||||
title = stringResource(MR.strings.action_global_search),
|
icon = Icons.Outlined.TravelExplore,
|
||||||
icon = Icons.Outlined.TravelExplore,
|
onClick = { navigator.push(GlobalSearchScreen(smartSearchConfig?.origTitle ?: "")) },
|
||||||
onClick = { navigator.push(GlobalSearchScreen()) },
|
),
|
||||||
),
|
).let {
|
||||||
AppBar.Action(
|
when (smartSearchConfig) {
|
||||||
title = stringResource(MR.strings.action_filter),
|
null -> {
|
||||||
icon = Icons.Outlined.FilterList,
|
it.add(
|
||||||
onClick = { navigator.push(SourcesFilterScreen()) },
|
AppBar.Action(
|
||||||
),
|
title = stringResource(MR.strings.action_filter),
|
||||||
)
|
icon = Icons.Outlined.FilterList,
|
||||||
} else {
|
onClick = { navigator.push(SourcesFilterScreen()) },
|
||||||
persistentListOf()
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> it
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// SY <--
|
// SY <--
|
||||||
content = { contentPadding, snackbarHostState ->
|
content = { contentPadding, snackbarHostState ->
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.InputChip
|
import androidx.compose.material3.InputChip
|
||||||
import androidx.compose.material3.InputChipDefaults
|
import androidx.compose.material3.InputChipDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MenuAnchorType
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -154,7 +155,7 @@ fun AutoCompleteTextField(
|
|||||||
null
|
null
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.menuAnchor()
|
.menuAnchor(MenuAnchorType.PrimaryEditable)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.runOnEnterKeyPressed { submit() },
|
.runOnEnterKeyPressed { submit() },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ 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
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
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
|
||||||
@@ -61,6 +60,7 @@ import exh.ui.ifSourcesLoaded
|
|||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
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.UnsortedPreferences
|
||||||
@@ -75,7 +75,7 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
data class BrowseSourceScreen(
|
data class BrowseSourceScreen(
|
||||||
private val sourceId: Long,
|
val sourceId: Long,
|
||||||
private val listingQuery: String?,
|
private val listingQuery: String?,
|
||||||
// SY -->
|
// SY -->
|
||||||
private val filtersJson: String? = null,
|
private val filtersJson: String? = null,
|
||||||
@@ -240,11 +240,9 @@ data class BrowseSourceScreen(
|
|||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
|
|
||||||
|
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
mangaList = pagingFlow.collectAsLazyPagingItems(),
|
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
// SY -->
|
// SY -->
|
||||||
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
||||||
|
|||||||
+3
-3
@@ -15,10 +15,10 @@ import cafe.adriel.voyager.core.model.StateScreenModel
|
|||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
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.base.BasePreferences
|
|
||||||
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.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.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.track.interactor.AddTracks
|
import eu.kanade.domain.track.interactor.AddTracks
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
@@ -93,7 +93,6 @@ open class BrowseSourceScreenModel(
|
|||||||
// SY <--
|
// SY <--
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
sourcePreferences: SourcePreferences = Injekt.get(),
|
sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
basePreferences: BasePreferences = Injekt.get(),
|
|
||||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
private val coverCache: CoverCache = Injekt.get(),
|
private val coverCache: CoverCache = Injekt.get(),
|
||||||
private val getRemoteManga: GetRemoteManga = Injekt.get(),
|
private val getRemoteManga: GetRemoteManga = Injekt.get(),
|
||||||
@@ -105,6 +104,7 @@ open class BrowseSourceScreenModel(
|
|||||||
private val networkToLocalManga: NetworkToLocalManga = 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(),
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
unsortedPreferences: UnsortedPreferences = Injekt.get(),
|
unsortedPreferences: UnsortedPreferences = Injekt.get(),
|
||||||
@@ -149,7 +149,7 @@ open class BrowseSourceScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!basePreferences.incognitoMode().get()) {
|
if (!getIncognitoState.await(source.id)) {
|
||||||
sourcePreferences.lastUsedSource().set(source.id)
|
sourcePreferences.lastUsedSource().set(source.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object HistoryTab : Tab {
|
data object HistoryTab : Tab {
|
||||||
|
|
||||||
private val snackbarHostState = SnackbarHostState()
|
private val snackbarHostState = SnackbarHostState()
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
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 cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.fastFilter
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
@@ -73,11 +73,11 @@ object HomeScreen : Screen() {
|
|||||||
private const val TAB_FADE_DURATION = 200
|
private const val TAB_FADE_DURATION = 200
|
||||||
private const val TAB_NAVIGATOR_KEY = "HomeTabs"
|
private const val TAB_NAVIGATOR_KEY = "HomeTabs"
|
||||||
|
|
||||||
private val tabs = listOf(
|
private val TABS = listOf(
|
||||||
LibraryTab,
|
LibraryTab,
|
||||||
UpdatesTab,
|
UpdatesTab,
|
||||||
HistoryTab,
|
HistoryTab,
|
||||||
BrowseTab(),
|
BrowseTab,
|
||||||
MoreTab,
|
MoreTab,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ object HomeScreen : Screen() {
|
|||||||
startBar = {
|
startBar = {
|
||||||
if (isTabletUi()) {
|
if (isTabletUi()) {
|
||||||
NavigationRail {
|
NavigationRail {
|
||||||
tabs
|
TABS
|
||||||
// SY -->
|
// SY -->
|
||||||
.fastFilter { it.isEnabled() }
|
.fastFilter { it.isEnabled() }
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -123,7 +123,7 @@ object HomeScreen : Screen() {
|
|||||||
exit = shrinkVertically(),
|
exit = shrinkVertically(),
|
||||||
) {
|
) {
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
tabs
|
TABS
|
||||||
// SY -->
|
// SY -->
|
||||||
.fastFilter { it.isEnabled() }
|
.fastFilter { it.isEnabled() }
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -179,7 +179,12 @@ object HomeScreen : Screen() {
|
|||||||
is Tab.Library -> LibraryTab
|
is Tab.Library -> LibraryTab
|
||||||
Tab.Updates -> UpdatesTab
|
Tab.Updates -> UpdatesTab
|
||||||
Tab.History -> HistoryTab
|
Tab.History -> HistoryTab
|
||||||
is Tab.Browse -> BrowseTab(it.toExtensions)
|
is Tab.Browse -> {
|
||||||
|
if (it.toExtensions) {
|
||||||
|
BrowseTab.showExtension()
|
||||||
|
}
|
||||||
|
BrowseTab
|
||||||
|
}
|
||||||
is Tab.More -> MoreTab
|
is Tab.More -> MoreTab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
|
import androidx.compose.ui.util.fastDistinctBy
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
|
import androidx.compose.ui.util.fastMapNotNull
|
||||||
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.core.preference.PreferenceMutableState
|
import eu.kanade.core.preference.PreferenceMutableState
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.fastDistinctBy
|
|
||||||
import eu.kanade.core.util.fastFilter
|
|
||||||
import eu.kanade.core.util.fastFilterNot
|
import eu.kanade.core.util.fastFilterNot
|
||||||
import eu.kanade.core.util.fastMapNotNull
|
|
||||||
import eu.kanade.core.util.fastPartition
|
import eu.kanade.core.util.fastPartition
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ import tachiyomi.source.local.isLocal
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object LibraryTab : Tab {
|
data object LibraryTab : Tab {
|
||||||
|
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
@Composable
|
@Composable
|
||||||
@@ -165,7 +165,7 @@ object LibraryTab : Tab {
|
|||||||
},
|
},
|
||||||
onClickSyncNow = {
|
onClickSyncNow = {
|
||||||
if (!SyncDataJob.isRunning(context)) {
|
if (!SyncDataJob.isRunning(context)) {
|
||||||
SyncDataJob.startNow(context)
|
SyncDataJob.startNow(context, manual = true)
|
||||||
} else {
|
} else {
|
||||||
context.toast(SYMR.strings.sync_in_progress)
|
context.toast(SYMR.strings.sync_in_progress)
|
||||||
}
|
}
|
||||||
@@ -334,8 +334,8 @@ object LibraryTab : Tab {
|
|||||||
// SY -->
|
// SY -->
|
||||||
SyncFavoritesProgressDialog(
|
SyncFavoritesProgressDialog(
|
||||||
status = screenModel.favoritesSync.status.collectAsState().value,
|
status = screenModel.favoritesSync.status.collectAsState().value,
|
||||||
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
|
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle },
|
||||||
openManga = { navigator.push(MangaScreen(it.id)) },
|
openManga = { navigator.push(MangaScreen(it)) },
|
||||||
)
|
)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
|
|||||||
import com.google.firebase.analytics.ktx.analytics
|
import com.google.firebase.analytics.ktx.analytics
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
|
import eu.kanade.domain.source.interactor.GetIncognitoState
|
||||||
import eu.kanade.presentation.components.AppStateBanners
|
import eu.kanade.presentation.components.AppStateBanners
|
||||||
import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor
|
import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor
|
||||||
import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor
|
import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor
|
||||||
@@ -122,6 +123,8 @@ class MainActivity : BaseActivity() {
|
|||||||
private val downloadCache: DownloadCache by injectLazy()
|
private val downloadCache: DownloadCache by injectLazy()
|
||||||
private val chapterCache: ChapterCache by injectLazy()
|
private val chapterCache: ChapterCache by injectLazy()
|
||||||
|
|
||||||
|
private val getIncognitoState: GetIncognitoState by injectLazy()
|
||||||
|
|
||||||
// To be checked by splash screen. If true then splash screen will be removed.
|
// To be checked by splash screen. If true then splash screen will be removed.
|
||||||
var ready = false
|
var ready = false
|
||||||
|
|
||||||
@@ -181,7 +184,7 @@ class MainActivity : BaseActivity() {
|
|||||||
setComposeContent {
|
setComposeContent {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val incognito by preferences.incognitoMode().collectAsState()
|
var incognito by remember { mutableStateOf(getIncognitoState.await(null)) }
|
||||||
val downloadOnly by preferences.downloadedOnly().collectAsState()
|
val downloadOnly by preferences.downloadedOnly().collectAsState()
|
||||||
val indexing by downloadCache.isInitializing.collectAsState()
|
val indexing by downloadCache.isInitializing.collectAsState()
|
||||||
|
|
||||||
@@ -231,6 +234,11 @@ class MainActivity : BaseActivity() {
|
|||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(navigator.lastItem) {
|
||||||
|
(navigator.lastItem as? BrowseSourceScreen)?.sourceId
|
||||||
|
.let(getIncognitoState::subscribe)
|
||||||
|
.collectLatest { incognito = it }
|
||||||
|
}
|
||||||
|
|
||||||
val scaffoldInsets = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
|
val scaffoldInsets = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|||||||
@@ -3,20 +3,26 @@ package eu.kanade.tachiyomi.ui.manga
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
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
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
@@ -26,22 +32,34 @@ import coil3.transform.RoundedCornersTransformation
|
|||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import eu.kanade.presentation.track.components.TrackLogoIcon
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
|
import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
|
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
|
||||||
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
|
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
|
||||||
import exh.util.dropBlank
|
import exh.util.dropBlank
|
||||||
import exh.util.trimOrNull
|
import exh.util.trimOrNull
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
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
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EditMangaDialog(
|
fun EditMangaDialog(
|
||||||
@@ -61,6 +79,10 @@ fun EditMangaDialog(
|
|||||||
var binding by remember {
|
var binding by remember {
|
||||||
mutableStateOf<EditMangaDialogBinding?>(null)
|
mutableStateOf<EditMangaDialogBinding?>(null)
|
||||||
}
|
}
|
||||||
|
val showTrackerSelectionDialogue = remember { mutableStateOf(false) }
|
||||||
|
val getTracks = remember { Injekt.get<GetTracks>() }
|
||||||
|
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
||||||
|
val tracks = remember { mutableStateOf(emptyList<Pair<Track, Tracker>>()) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@@ -109,7 +131,7 @@ fun EditMangaDialog(
|
|||||||
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
|
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
|
||||||
.also { binding = it }
|
.also { binding = it }
|
||||||
.apply {
|
.apply {
|
||||||
onViewCreated(manga, factoryContext, this, scope)
|
onViewCreated(manga, factoryContext, this, scope, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
|
||||||
}
|
}
|
||||||
.root
|
.root
|
||||||
},
|
},
|
||||||
@@ -118,9 +140,61 @@ fun EditMangaDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (showTrackerSelectionDialogue.value) {
|
||||||
|
TrackerSelectDialog(
|
||||||
|
tracks = tracks.value,
|
||||||
|
onDismissRequest = { showTrackerSelectionDialogue.value = false },
|
||||||
|
onTrackerSelect = { tracker, track ->
|
||||||
|
scope.launch {
|
||||||
|
autofillFromTracker(binding!!, track, tracker)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
@Composable
|
||||||
|
private fun TrackerSelectDialog(
|
||||||
|
tracks: List<Pair<Track, Tracker>>,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onTrackerSelect: (
|
||||||
|
tracker: Tracker,
|
||||||
|
track: Track,
|
||||||
|
) -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(stringResource(SYMR.strings.select_tracker))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
tracks.forEach { (track, tracker) ->
|
||||||
|
TrackLogoIcon(
|
||||||
|
tracker,
|
||||||
|
onClick = {
|
||||||
|
onTrackerSelect(tracker, track)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope, getTracks: GetTracks, trackerManager: TrackerManager, tracks: MutableState<List<Pair<Track, Tracker>>>, showTrackerSelectionDialogue: MutableState<Boolean>) {
|
||||||
loadCover(manga, binding)
|
loadCover(manga, binding)
|
||||||
|
|
||||||
val statusAdapter: ArrayAdapter<String> = ArrayAdapter(
|
val statusAdapter: ArrayAdapter<String> = ArrayAdapter(
|
||||||
@@ -203,6 +277,55 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
|
|||||||
|
|
||||||
binding.resetTags.setOnClickListener { resetTags(manga, binding, scope) }
|
binding.resetTags.setOnClickListener { resetTags(manga, binding, scope) }
|
||||||
binding.resetInfo.setOnClickListener { resetInfo(manga, binding, scope) }
|
binding.resetInfo.setOnClickListener { resetInfo(manga, binding, scope) }
|
||||||
|
binding.autofillFromTracker.setOnClickListener {
|
||||||
|
scope.launch {
|
||||||
|
getTrackers(manga, binding, context, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getTrackers(manga: Manga, binding: EditMangaDialogBinding, context: Context, getTracks: GetTracks, trackerManager: TrackerManager, tracks: MutableState<List<Pair<Track, Tracker>>>, showTrackerSelectionDialogue: MutableState<Boolean>) {
|
||||||
|
tracks.value = getTracks.await(manga.id).map { track ->
|
||||||
|
track to trackerManager.get(track.trackerId)!!
|
||||||
|
}
|
||||||
|
.filterNot { (_, tracker) -> tracker is EnhancedTracker }
|
||||||
|
|
||||||
|
if (tracks.value.isEmpty()) {
|
||||||
|
context.toast(context.stringResource(SYMR.strings.entry_not_tracked))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracks.value.size > 1) {
|
||||||
|
showTrackerSelectionDialogue.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
autofillFromTracker(binding, tracks.value.first().first, tracks.value.first().second)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTextIfNotBlank(field: (String) -> Unit, value: String?) {
|
||||||
|
value?.takeIf { it.isNotBlank() }?.let { field(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun autofillFromTracker(binding: EditMangaDialogBinding, track: Track, tracker: Tracker) {
|
||||||
|
try {
|
||||||
|
val trackerMangaMetadata = tracker.getMangaMetadata(track)
|
||||||
|
|
||||||
|
setTextIfNotBlank(binding.title::setText, trackerMangaMetadata?.title)
|
||||||
|
setTextIfNotBlank(binding.mangaAuthor::setText, trackerMangaMetadata?.authors)
|
||||||
|
setTextIfNotBlank(binding.mangaArtist::setText, trackerMangaMetadata?.artists)
|
||||||
|
setTextIfNotBlank(binding.thumbnailUrl::setText, trackerMangaMetadata?.thumbnailUrl)
|
||||||
|
setTextIfNotBlank(binding.mangaDescription::setText, trackerMangaMetadata?.description)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
tracker.logcat(LogPriority.ERROR, e)
|
||||||
|
binding.root.context.toast(
|
||||||
|
binding.root.context.stringResource(
|
||||||
|
MR.strings.track_error,
|
||||||
|
tracker.name,
|
||||||
|
e.message ?: "",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetTags(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
private fun resetTags(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import eu.kanade.presentation.manga.components.SetIntervalDialog
|
|||||||
import eu.kanade.presentation.util.AssistContentScreen
|
import eu.kanade.presentation.util.AssistContentScreen
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.presentation.util.isTabletUi
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.isLocalOrStub
|
import eu.kanade.tachiyomi.source.isLocalOrStub
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
@@ -59,13 +58,10 @@ import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
|||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.md.similar.MangaDexSimilarScreen
|
|
||||||
import exh.pagepreview.PagePreviewScreen
|
import exh.pagepreview.PagePreviewScreen
|
||||||
import exh.pref.DelegateSourcePreferences
|
|
||||||
import exh.recs.RecommendsScreen
|
import exh.recs.RecommendsScreen
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.source.getMainSource
|
import exh.source.getMainSource
|
||||||
import exh.source.isMdBasedSource
|
|
||||||
import exh.ui.ifSourcesLoaded
|
import exh.ui.ifSourcesLoaded
|
||||||
import exh.ui.metadata.MetadataViewScreen
|
import exh.ui.metadata.MetadataViewScreen
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
@@ -214,7 +210,7 @@ class MangaScreen(
|
|||||||
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) },
|
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) },
|
||||||
onEditInfoClicked = screenModel::showEditMangaInfoDialog,
|
onEditInfoClicked = screenModel::showEditMangaInfoDialog,
|
||||||
onRecommendClicked = {
|
onRecommendClicked = {
|
||||||
openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga)
|
openRecommends(navigator, screenModel.source?.getMainSource(), successState.manga)
|
||||||
},
|
},
|
||||||
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
|
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
|
||||||
onMergeClicked = { openSmartSearch(navigator, successState.manga) },
|
onMergeClicked = { openSmartSearch(navigator, successState.manga) },
|
||||||
@@ -550,28 +546,9 @@ class MangaScreen(
|
|||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
// AZ -->
|
// AZ -->
|
||||||
private fun openRecommends(context: Context, navigator: Navigator, source: Source?, manga: Manga) {
|
private fun openRecommends(navigator: Navigator, source: Source?, manga: Manga) {
|
||||||
source ?: return
|
source ?: return
|
||||||
if (source.isMdBasedSource() && Injekt.get<DelegateSourcePreferences>().delegateSources().get()) {
|
navigator.push(RecommendsScreen(manga.id, source.id))
|
||||||
MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(SYMR.strings.az_recommends.getString(context))
|
|
||||||
.setSingleChoiceItems(
|
|
||||||
arrayOf(
|
|
||||||
context.stringResource(SYMR.strings.mangadex_similar),
|
|
||||||
context.stringResource(SYMR.strings.community_recommendations),
|
|
||||||
),
|
|
||||||
-1,
|
|
||||||
) { dialog, index ->
|
|
||||||
dialog.dismiss()
|
|
||||||
when (index) {
|
|
||||||
0 -> navigator.push(MangaDexSimilarScreen(manga.id, source.id))
|
|
||||||
1 -> navigator.push(RecommendsScreen(manga.id, source.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
} else if (source is CatalogueSource) {
|
|
||||||
navigator.push(RecommendsScreen(manga.id, source.id))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// AZ <--
|
// AZ <--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ import eu.kanade.domain.manga.model.toSManga
|
|||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.track.interactor.AddTracks
|
import eu.kanade.domain.track.interactor.AddTracks
|
||||||
import eu.kanade.domain.track.interactor.TrackChapter
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
|
import eu.kanade.domain.track.model.AutoTrackState
|
||||||
import eu.kanade.domain.track.model.toDomainTrack
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.manga.DownloadAction
|
import eu.kanade.presentation.manga.DownloadAction
|
||||||
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
||||||
@@ -48,6 +50,7 @@ import eu.kanade.tachiyomi.source.online.all.MergedSource
|
|||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.util.chapter.getNextUnread
|
import eu.kanade.tachiyomi.util.chapter.getNextUnread
|
||||||
import eu.kanade.tachiyomi.util.removeCovers
|
import eu.kanade.tachiyomi.util.removeCovers
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.debug.DebugToggles
|
import exh.debug.DebugToggles
|
||||||
import exh.eh.EHentaiUpdateHelper
|
import exh.eh.EHentaiUpdateHelper
|
||||||
import exh.log.xLogD
|
import exh.log.xLogD
|
||||||
@@ -145,6 +148,7 @@ class MangaScreenModel(
|
|||||||
private val isFromSource: Boolean,
|
private val isFromSource: Boolean,
|
||||||
val smartSearched: Boolean,
|
val smartSearched: Boolean,
|
||||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
|
private val trackPreferences: TrackPreferences = Injekt.get(),
|
||||||
readerPreferences: ReaderPreferences = Injekt.get(),
|
readerPreferences: ReaderPreferences = Injekt.get(),
|
||||||
uiPreferences: UiPreferences = Injekt.get(),
|
uiPreferences: UiPreferences = Injekt.get(),
|
||||||
private val trackerManager: TrackerManager = Injekt.get(),
|
private val trackerManager: TrackerManager = Injekt.get(),
|
||||||
@@ -208,6 +212,7 @@ class MangaScreenModel(
|
|||||||
|
|
||||||
val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get()
|
val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get()
|
||||||
val chapterSwipeEndAction = libraryPreferences.swipeToStartAction().get()
|
val chapterSwipeEndAction = libraryPreferences.swipeToStartAction().get()
|
||||||
|
var autoTrackState = trackPreferences.autoUpdateTrackOnMarkRead().get()
|
||||||
|
|
||||||
private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope)
|
private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope)
|
||||||
|
|
||||||
@@ -1258,19 +1263,29 @@ class MangaScreenModel(
|
|||||||
*/
|
*/
|
||||||
fun markChaptersRead(chapters: List<Chapter>, read: Boolean) {
|
fun markChaptersRead(chapters: List<Chapter>, read: Boolean) {
|
||||||
toggleAllSelection(false)
|
toggleAllSelection(false)
|
||||||
|
if (chapters.isEmpty()) return
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
setReadStatus.await(
|
setReadStatus.await(
|
||||||
read = read,
|
read = read,
|
||||||
chapters = chapters.toTypedArray(),
|
chapters = chapters.toTypedArray(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!read) return@launchIO
|
if (!read || successState?.hasLoggedInTrackers == false || autoTrackState == AutoTrackState.NEVER) {
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
|
||||||
val tracks = getTracks.await(mangaId)
|
val tracks = getTracks.await(mangaId)
|
||||||
val maxChapterNumber = chapters.maxOf { it.chapterNumber }
|
val maxChapterNumber = chapters.maxOf { it.chapterNumber }
|
||||||
val shouldPromptTrackingUpdate = tracks.any { track -> maxChapterNumber > track.lastChapterRead }
|
val shouldPromptTrackingUpdate = tracks.any { track -> maxChapterNumber > track.lastChapterRead }
|
||||||
|
|
||||||
if (!shouldPromptTrackingUpdate) return@launchIO
|
if (!shouldPromptTrackingUpdate) return@launchIO
|
||||||
|
if (autoTrackState == AutoTrackState.ALWAYS) {
|
||||||
|
trackChapter.await(context, mangaId, maxChapterNumber)
|
||||||
|
withUIContext {
|
||||||
|
context.toast(context.stringResource(MR.strings.trackers_updated_summary, maxChapterNumber.toInt()))
|
||||||
|
}
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
|
||||||
val result = snackbarHostState.showSnackbar(
|
val result = snackbarHostState.showSnackbar(
|
||||||
message = context.stringResource(MR.strings.confirm_tracker_update, maxChapterNumber.toInt()),
|
message = context.stringResource(MR.strings.confirm_tracker_update, maxChapterNumber.toInt()),
|
||||||
|
|||||||
@@ -824,7 +824,11 @@ private data class TrackerRemoveScreen(
|
|||||||
|
|
||||||
fun deleteMangaFromService() {
|
fun deleteMangaFromService() {
|
||||||
screenModelScope.launchNonCancellable {
|
screenModelScope.launchNonCancellable {
|
||||||
(tracker as DeletableTracker).delete(track)
|
try {
|
||||||
|
(tracker as DeletableTracker).delete(track)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e) { "Failed to delete entry from service" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object MoreTab : Tab {
|
data object MoreTab : Tab {
|
||||||
|
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
enabledPrevious = state.viewerChapters?.prevChapter != null,
|
enabledPrevious = state.viewerChapters?.prevChapter != null,
|
||||||
currentPage = state.currentPage,
|
currentPage = state.currentPage,
|
||||||
totalPages = state.totalPages,
|
totalPages = state.totalPages,
|
||||||
onSliderValueChange = {
|
onPageIndexChange = {
|
||||||
isScrollingThroughPages = true
|
isScrollingThroughPages = true
|
||||||
moveToPageIndex(it)
|
moveToPageIndex(it)
|
||||||
},
|
},
|
||||||
@@ -663,8 +663,10 @@ class ReaderActivity : BaseActivity() {
|
|||||||
SurfaceColors.SURFACE_2.getColor(this),
|
SurfaceColors.SURFACE_2.getColor(this),
|
||||||
if (isNightMode()) 230 else 242, // 90% dark 95% light
|
if (isNightMode()) 230 else 242, // 90% dark 95% light
|
||||||
)
|
)
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
window.statusBarColor = toolbarColor
|
window.statusBarColor = toolbarColor
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
window.navigationBarColor = toolbarColor
|
window.navigationBarColor = toolbarColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import eu.kanade.domain.chapter.model.toDbChapter
|
|||||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||||
import eu.kanade.domain.manga.model.readerOrientation
|
import eu.kanade.domain.manga.model.readerOrientation
|
||||||
import eu.kanade.domain.manga.model.readingMode
|
import eu.kanade.domain.manga.model.readingMode
|
||||||
|
import eu.kanade.domain.source.interactor.GetIncognitoState
|
||||||
import eu.kanade.domain.sync.SyncPreferences
|
import eu.kanade.domain.sync.SyncPreferences
|
||||||
import eu.kanade.domain.track.interactor.TrackChapter
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
@@ -115,7 +116,6 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
private val downloadProvider: DownloadProvider = Injekt.get(),
|
private val downloadProvider: DownloadProvider = Injekt.get(),
|
||||||
private val tempFileManager: UniFileTempFileManager = Injekt.get(),
|
private val tempFileManager: UniFileTempFileManager = Injekt.get(),
|
||||||
private val imageSaver: ImageSaver = Injekt.get(),
|
private val imageSaver: ImageSaver = Injekt.get(),
|
||||||
preferences: BasePreferences = Injekt.get(),
|
|
||||||
val readerPreferences: ReaderPreferences = Injekt.get(),
|
val readerPreferences: ReaderPreferences = Injekt.get(),
|
||||||
private val basePreferences: BasePreferences = Injekt.get(),
|
private val basePreferences: BasePreferences = Injekt.get(),
|
||||||
private val downloadPreferences: DownloadPreferences = Injekt.get(),
|
private val downloadPreferences: DownloadPreferences = Injekt.get(),
|
||||||
@@ -127,8 +127,9 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
private val upsertHistory: UpsertHistory = Injekt.get(),
|
private val upsertHistory: UpsertHistory = Injekt.get(),
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||||
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
|
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
|
||||||
private val syncPreferences: SyncPreferences = Injekt.get(),
|
private val getIncognitoState: GetIncognitoState = Injekt.get(),
|
||||||
// SY -->
|
// SY -->
|
||||||
|
private val syncPreferences: SyncPreferences = Injekt.get(),
|
||||||
private val uiPreferences: UiPreferences = Injekt.get(),
|
private val uiPreferences: UiPreferences = Injekt.get(),
|
||||||
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
|
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
|
||||||
private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
|
private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
|
||||||
@@ -264,7 +265,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
.map(::ReaderChapter)
|
.map(::ReaderChapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val incognitoMode = preferences.incognitoMode().get()
|
private val incognitoMode: Boolean by lazy { getIncognitoState.await(manga?.source) }
|
||||||
private val downloadAheadAmount = downloadPreferences.autoDownloadWhileReading().get()
|
private val downloadAheadAmount = downloadPreferences.autoDownloadWhileReading().get()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -700,7 +701,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
// SY <--
|
// SY <--
|
||||||
readerChapter.chapter.read = true
|
readerChapter.chapter.read = true
|
||||||
// SY -->
|
// SY -->
|
||||||
if (readerChapter.chapter.chapter_number > 0 && readerPreferences.markReadDupe().get()) {
|
if (readerChapter.chapter.chapter_number >= 0 && readerPreferences.markReadDupe().get()) {
|
||||||
getChaptersByMangaId.await(manga!!.id).sortedByDescending { it.sourceOrder }
|
getChaptersByMangaId.await(manga!!.id).sortedByDescending { it.sourceOrder }
|
||||||
.filter {
|
.filter {
|
||||||
it.id != readerChapter.chapter.id &&
|
it.id != readerChapter.chapter.id &&
|
||||||
|
|||||||
@@ -33,13 +33,16 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_IN_OUT
|
|||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||||
import com.github.chrisbanes.photoview.PhotoView
|
import com.github.chrisbanes.photoview.PhotoView
|
||||||
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.data.coil.cropBorders
|
import eu.kanade.tachiyomi.data.coil.cropBorders
|
||||||
import eu.kanade.tachiyomi.data.coil.customDecoder
|
import eu.kanade.tachiyomi.data.coil.customDecoder
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
|
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
|
||||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||||
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
|
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
|
||||||
import okio.BufferedSource
|
import okio.BufferedSource
|
||||||
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper view for showing page image.
|
* A wrapper view for showing page image.
|
||||||
@@ -57,6 +60,10 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||||||
private val isWebtoon: Boolean = false,
|
private val isWebtoon: Boolean = false,
|
||||||
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
|
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
|
||||||
|
|
||||||
|
private val alwaysDecodeLongStripWithSSIV by lazy {
|
||||||
|
Injekt.get<BasePreferences>().alwaysDecodeLongStripWithSSIV().get()
|
||||||
|
}
|
||||||
|
|
||||||
private var pageView: View? = null
|
private var pageView: View? = null
|
||||||
|
|
||||||
private var config: Config? = null
|
private var config: Config? = null
|
||||||
@@ -116,21 +123,22 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) {
|
private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) {
|
||||||
|
val config = config
|
||||||
if (config != null &&
|
if (config != null &&
|
||||||
config!!.landscapeZoom &&
|
config.landscapeZoom &&
|
||||||
config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
|
config.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
|
||||||
sWidth > sHeight &&
|
sWidth > sHeight &&
|
||||||
scale == minScale
|
scale == minScale
|
||||||
) {
|
) {
|
||||||
handler?.postDelayed(500) {
|
handler?.postDelayed(500) {
|
||||||
val point = when (config!!.zoomStartPosition) {
|
val point = when (config.zoomStartPosition) {
|
||||||
ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F)
|
ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F)
|
||||||
ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F)
|
ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F)
|
||||||
ZoomStartPosition.CENTER -> center
|
ZoomStartPosition.CENTER -> center
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetScale = height.toFloat() / sHeight.toFloat()
|
val targetScale = height.toFloat() / sHeight.toFloat()
|
||||||
animateScaleAndCenter(targetScale, point)!!
|
(animateScaleAndCenter(targetScale, point) ?: return@postDelayed)
|
||||||
.withDuration(500)
|
.withDuration(500)
|
||||||
.withEasing(EASE_IN_OUT_QUAD)
|
.withEasing(EASE_IN_OUT_QUAD)
|
||||||
.withInterruptible(true)
|
.withInterruptible(true)
|
||||||
@@ -232,7 +240,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||||||
} else {
|
} else {
|
||||||
SubsamplingScaleImageView(context)
|
SubsamplingScaleImageView(context)
|
||||||
}.apply {
|
}.apply {
|
||||||
setMaxTileSize(GLUtil.maxTextureSize)
|
setMaxTileSize(ImageUtil.hardwareBitmapThreshold)
|
||||||
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
|
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
|
||||||
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
||||||
setMinimumTileDpi(180)
|
setMinimumTileDpi(180)
|
||||||
@@ -287,35 +295,44 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isWebtoon) {
|
when (data) {
|
||||||
val request = ImageRequest.Builder(context)
|
is BitmapDrawable -> {
|
||||||
.data(data)
|
setImage(ImageSource.bitmap(data.bitmap))
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
isVisible = true
|
||||||
.diskCachePolicy(CachePolicy.DISABLED)
|
}
|
||||||
.target(
|
is BufferedSource -> {
|
||||||
onSuccess = { result ->
|
if (!isWebtoon || alwaysDecodeLongStripWithSSIV) {
|
||||||
val image = result as BitmapImage
|
setHardwareConfig(ImageUtil.canUseHardwareBitmap(data))
|
||||||
setImage(ImageSource.bitmap(image.bitmap))
|
setImage(ImageSource.inputStream(data.inputStream()))
|
||||||
isVisible = true
|
isVisible = true
|
||||||
},
|
return@apply
|
||||||
onError = {
|
}
|
||||||
this@ReaderPageImageView.onImageLoadError()
|
|
||||||
},
|
ImageRequest.Builder(context)
|
||||||
)
|
.data(data)
|
||||||
.size(ViewSizeResolver(this@ReaderPageImageView))
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.precision(Precision.INEXACT)
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
.cropBorders(config.cropBorders)
|
.target(
|
||||||
.customDecoder(true)
|
onSuccess = { result ->
|
||||||
.crossfade(false)
|
val image = result as BitmapImage
|
||||||
.build()
|
setImage(ImageSource.bitmap(image.bitmap))
|
||||||
context.imageLoader.enqueue(request)
|
isVisible = true
|
||||||
} else {
|
},
|
||||||
when (data) {
|
onError = {
|
||||||
is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap))
|
onImageLoadError()
|
||||||
is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream()))
|
},
|
||||||
else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
|
)
|
||||||
|
.size(ViewSizeResolver(this@ReaderPageImageView))
|
||||||
|
.precision(Precision.INEXACT)
|
||||||
|
.cropBorders(config.cropBorders)
|
||||||
|
.customDecoder(true)
|
||||||
|
.crossfade(false)
|
||||||
|
.build()
|
||||||
|
.let(context.imageLoader::enqueue)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
|
||||||
}
|
}
|
||||||
isVisible = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -353,8 +353,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
|||||||
else -> oldCurrent?.first ?: return
|
else -> oldCurrent?.first ?: return
|
||||||
}
|
}
|
||||||
|
|
||||||
val index = when (newPage) {
|
val index = when {
|
||||||
is ChapterTransition -> {
|
newPage is ChapterTransition && joinedItems.none { it.first == newPage || it.second == newPage } -> {
|
||||||
val filteredPages = joinedItems.filter {
|
val filteredPages = joinedItems.filter {
|
||||||
it.first is ReaderPage &&
|
it.first is ReaderPage &&
|
||||||
(it.first as ReaderPage).chapter == newPage.to
|
(it.first as ReaderPage).chapter == newPage.to
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.ui.stats
|
package eu.kanade.tachiyomi.ui.stats
|
||||||
|
|
||||||
|
import androidx.compose.ui.util.fastDistinctBy
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
|
import androidx.compose.ui.util.fastMapNotNull
|
||||||
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.core.util.fastCountNot
|
import eu.kanade.core.util.fastCountNot
|
||||||
import eu.kanade.core.util.fastDistinctBy
|
|
||||||
import eu.kanade.core.util.fastFilter
|
|
||||||
import eu.kanade.core.util.fastFilterNot
|
import eu.kanade.core.util.fastFilterNot
|
||||||
import eu.kanade.core.util.fastMapNotNull
|
|
||||||
import eu.kanade.presentation.more.stats.StatsScreenState
|
import eu.kanade.presentation.more.stats.StatsScreenState
|
||||||
import eu.kanade.presentation.more.stats.data.StatsData
|
import eu.kanade.presentation.more.stats.data.StatsData
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object UpdatesTab : Tab {
|
data object UpdatesTab : Tab {
|
||||||
|
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.core.content.getSystemService
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
||||||
@@ -107,9 +108,13 @@ fun Context.createFileInCacheDir(name: String): File {
|
|||||||
fun Context.createReaderThemeContext(): Context {
|
fun Context.createReaderThemeContext(): Context {
|
||||||
val preferences = Injekt.get<UiPreferences>()
|
val preferences = Injekt.get<UiPreferences>()
|
||||||
val readerPreferences = Injekt.get<ReaderPreferences>()
|
val readerPreferences = Injekt.get<ReaderPreferences>()
|
||||||
|
val themeMode = preferences.themeMode().get()
|
||||||
val isDarkBackground = when (readerPreferences.readerTheme().get()) {
|
val isDarkBackground = when (readerPreferences.readerTheme().get()) {
|
||||||
1, 2 -> true // Black, Gray
|
1, 2 -> true // Black, Gray
|
||||||
3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default
|
3 -> when (themeMode) { // Automatic bg uses activity background by default
|
||||||
|
ThemeMode.SYSTEM -> applicationContext.isNightMode()
|
||||||
|
else -> themeMode == ThemeMode.DARK
|
||||||
|
}
|
||||||
else -> false // White
|
else -> false // White
|
||||||
}
|
}
|
||||||
val expected = if (isDarkBackground) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
|
val expected = if (isDarkBackground) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user