Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6db1637770 | |||
| 5742d2e3fe | |||
| c2d0308ac0 | |||
| 84c7da5a7d | |||
| 274350c118 | |||
| 6bd978eef1 | |||
| e0f40fad8c | |||
| 5647665782 | |||
| df99e7ee49 | |||
| dbd4437474 | |||
| 9c198d0c33 | |||
| d62a8a138c | |||
| f8a57ec98c | |||
| aa6339df06 | |||
| 3fbbfbd9cb | |||
| 31d6bf1967 | |||
| 226b3f2ff4 | |||
| ac8dab75fe | |||
| aad2bf4645 | |||
| 7f71296e1c | |||
| 9137170fb8 | |||
| 0af667c9aa | |||
| 8dc6a95ce6 | |||
| 1eb64d117e | |||
| 8f48a80bc4 | |||
| e76dd7fab0 | |||
| b53a9ce5ae | |||
| 952f26929c | |||
| 9ff048e683 | |||
| a64fe8121b | |||
| 4db7a32075 | |||
| 20ee5ea3e1 | |||
| d9200ef006 | |||
| dfde271f7f | |||
| 5346eac037 | |||
| 95e151be4b | |||
| 98af745e08 | |||
| 56433a624e | |||
| c59cb620dd | |||
| f60cb9bb64 | |||
| 949a2a95ad | |||
| 0bd700699b | |||
| 1d10925829 | |||
| 0e2866260f | |||
| 02ace23c38 | |||
| 3e16adf961 | |||
| fb1a3da0ea | |||
| 5f2e979bb5 | |||
| 5d4d15aa9c | |||
| fb71d0cd68 | |||
| a189a7eaec | |||
| 59a6bd700b | |||
| 278224676b | |||
| 66f2877a3f | |||
| a97deb0036 | |||
| ab976d8b07 | |||
| cb2cfa7e94 | |||
| 2c2f84bb29 | |||
| 7156b0dcce | |||
| f62671742c | |||
| 58be872bef | |||
| ce6b847c8b | |||
| 9c22e7fcb7 | |||
| 452f36939a | |||
| f0b821e2df | |||
| cda87a5c07 | |||
| 10844339b8 | |||
| 042785e188 | |||
| 07740ae83c | |||
| 9d08fe05c1 | |||
| 516114011f | |||
| bb08522a32 | |||
| 25949c3296 | |||
| 5720774bbf | |||
| 74c8b20a85 | |||
| 744b714c25 | |||
| 73d57239f7 | |||
| 325a706840 | |||
| c179b1812c | |||
| 1fa05703fa | |||
| b34f807d33 | |||
| fda27e6eba | |||
| 217503eab0 | |||
| 8d062cecfd | |||
| 614839c023 | |||
| 254980695b | |||
| 28cca49635 | |||
| 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 |
+27
-3
@@ -1,12 +1,32 @@
|
|||||||
[*.{kt,kts}]
|
root = true
|
||||||
max_line_length = 120
|
|
||||||
indent_size = 4
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.xml]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# noinspection EditorConfigKeyCorrectness
|
||||||
|
[*.{kt,kts}]
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 120
|
||||||
|
|
||||||
ij_kotlin_allow_trailing_comma = true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||||
|
|
||||||
|
ktlint_code_style = intellij_idea
|
||||||
|
ktlint_function_naming_ignore_when_annotated_with = Composable
|
||||||
|
ktlint_standard_class-signature = disabled
|
||||||
|
ktlint_standard_discouraged-comment-location = disabled
|
||||||
|
ktlint_standard_function-expression-body = disabled
|
||||||
|
ktlint_standard_function-signature = disabled
|
||||||
|
# SY
|
||||||
ktlint_standard_filename = disabled
|
ktlint_standard_filename = disabled
|
||||||
ktlint_standard_argument-list-wrapping = disabled
|
ktlint_standard_argument-list-wrapping = disabled
|
||||||
ktlint_standard_function-naming = disabled
|
ktlint_standard_function-naming = disabled
|
||||||
@@ -14,3 +34,7 @@ ktlint_standard_property-naming = disabled
|
|||||||
ktlint_standard_multiline-expression-wrapping = disabled
|
ktlint_standard_multiline-expression-wrapping = disabled
|
||||||
ktlint_standard_string-template-indent = disabled
|
ktlint_standard_string-template-indent = disabled
|
||||||
ktlint_standard_comment-wrapping = disabled
|
ktlint_standard_comment-wrapping = disabled
|
||||||
|
ktlint_standard_max-line-length = disabled
|
||||||
|
ktlint_standard_type-argument-comment = disabled
|
||||||
|
ktlint_standard_value-argument-comment = disabled
|
||||||
|
ktlint_standard_value-parameter-comment = disabled
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: ❌ Help with Extensions
|
||||||
|
url: https://mihon.app/docs/faq/browse/extensions
|
||||||
|
about: For extension-related questions/issues
|
||||||
- name: 🖥️ Mihon website
|
- name: 🖥️ Mihon website
|
||||||
url: https://mihon.app/
|
url: https://mihon.app/
|
||||||
about: Guides, troubleshooting, and answers to common questions
|
about: Guides, troubleshooting, and answers to common questions
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Crash logs
|
label: Crash logs
|
||||||
description: |
|
description: |
|
||||||
If you're experiencing crashes, share the crash logs from **More → Settings → Advanced** then press **Dump crash logs**.
|
If you're experiencing crashes, if possible, go to the app's **More → Settings → Advanced** page, press **Dump crash logs** and share the crash logs here.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
You can paste the crash logs in plain text or upload it as an attachment.
|
You can upload the crash log file as an attachment, or paste the crash logs in plain text if needed.
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: tachiyomisy-version
|
id: tachiyomisy-version
|
||||||
@@ -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.12.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,11 +94,11 @@ 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.12.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I understand that **Mihon does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions.
|
||||||
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.12.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
|
||||||
@@ -60,6 +63,8 @@ jobs:
|
|||||||
alias: ${{ secrets.ALIAS }}
|
alias: ${{ secrets.ALIAS }}
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
env:
|
||||||
|
BUILD_TOOLS_VERSION: '35.0.1'
|
||||||
|
|
||||||
- name: Clean up build artifacts
|
- name: Clean up build artifacts
|
||||||
run: |
|
run: |
|
||||||
@@ -69,19 +74,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
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
name: Remote Dispatch Action Initiator
|
name: Remote Dispatch Action Initiator
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'preview'
|
- 'preview'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
trigger_preview_build:
|
trigger_preview_build:
|
||||||
name: Trigger preview build
|
name: Trigger preview build
|
||||||
@@ -14,8 +14,14 @@ jobs:
|
|||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Set up JDK
|
||||||
uses: gradle/actions/wrapper-validation@v4
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
distribution: temurin
|
||||||
|
|
||||||
|
- name: Set up gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
- name: Create Tag
|
- name: Create Tag
|
||||||
run: |
|
run: |
|
||||||
@@ -28,3 +34,6 @@ jobs:
|
|||||||
-H 'Accept: application/vnd.github.everest-preview+json' \
|
-H 'Accept: application/vnd.github.everest-preview+json' \
|
||||||
-u ${{ secrets.ACCESS_TOKEN }} \
|
-u ${{ secrets.ACCESS_TOKEN }} \
|
||||||
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: ./gradlew testDebugUnitTest testDevDebugUnitTest
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
name: Lock threads
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Daily
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
# Manual trigger
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lock:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: dessant/lock-threads@v5
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
issue-inactive-days: '2'
|
|
||||||
pr-inactive-days: '2'
|
|
||||||
+26
-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 = 73
|
||||||
versionName = "1.10.5"
|
versionName = "1.12.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 -->
|
||||||
@@ -245,6 +262,7 @@ dependencies {
|
|||||||
implementation(libs.swipe)
|
implementation(libs.swipe)
|
||||||
implementation(libs.compose.webview)
|
implementation(libs.compose.webview)
|
||||||
implementation(libs.compose.grid)
|
implementation(libs.compose.grid)
|
||||||
|
implementation(libs.reorderable)
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
@@ -289,6 +307,9 @@ dependencies {
|
|||||||
// Koin
|
// Koin
|
||||||
implementation(sylibs.koin.core)
|
implementation(sylibs.koin.core)
|
||||||
implementation(sylibs.koin.android)
|
implementation(sylibs.koin.android)
|
||||||
|
|
||||||
|
// ZXing Android Embedded
|
||||||
|
implementation(sylibs.zxing.android.embedded)
|
||||||
}
|
}
|
||||||
|
|
||||||
androidComponents {
|
androidComponents {
|
||||||
@@ -307,28 +328,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)
|
||||||
|
|||||||
@@ -359,7 +359,7 @@
|
|||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:scheme="http" />
|
<data android:scheme="http" />
|
||||||
|
|
||||||
<data android:host="pururin.io" />
|
<data android:host="pururin.me" />
|
||||||
|
|
||||||
<data android:pathPattern="/gallery/..*" />
|
<data android:pathPattern="/gallery/..*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -413,6 +413,13 @@
|
|||||||
android:scheme="tachiyomisy" />
|
android:scheme="tachiyomisy" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
|
tools:remove="screenOrientation" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
<uses-sdk tools:overrideLibrary="rikka.shizuku.api"
|
||||||
|
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
|
||||||
@@ -108,7 +110,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { RenameCategory(get()) }
|
addFactory { RenameCategory(get()) }
|
||||||
addFactory { ReorderCategory(get()) }
|
addFactory { ReorderCategory(get()) }
|
||||||
addFactory { UpdateCategory(get()) }
|
addFactory { UpdateCategory(get()) }
|
||||||
addFactory { DeleteCategory(get()) }
|
addFactory { DeleteCategory(get(), get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
||||||
addFactory { GetDuplicateLibraryManga(get()) }
|
addFactory { GetDuplicateLibraryManga(get()) }
|
||||||
@@ -150,7 +152,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { UpdateChapter(get()) }
|
addFactory { UpdateChapter(get()) }
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
|
||||||
addFactory { ShouldUpdateDbChapter() }
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
addFactory { GetAvailableScanlators(get()) }
|
addFactory { GetAvailableScanlators(get()) }
|
||||||
addFactory { FilterChaptersForDownload(get(), get(), get(), get()) }
|
addFactory { FilterChaptersForDownload(get(), get(), get(), get()) }
|
||||||
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import tachiyomi.domain.chapter.model.NoChaptersException
|
|||||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
import tachiyomi.domain.chapter.service.ChapterRecognition
|
import tachiyomi.domain.chapter.service.ChapterRecognition
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
@@ -35,6 +36,7 @@ class SyncChaptersWithSource(
|
|||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
private val getExcludedScanlators: GetExcludedScanlators,
|
private val getExcludedScanlators: GetExcludedScanlators,
|
||||||
|
private val libraryPreferences: LibraryPreferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,12 +152,18 @@ class SyncChaptersWithSource(
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val reAdded = mutableListOf<Chapter>()
|
val changedOrDuplicateReadUrls = mutableSetOf<String>()
|
||||||
|
|
||||||
val deletedChapterNumbers = TreeSet<Double>()
|
val deletedChapterNumbers = TreeSet<Double>()
|
||||||
val deletedReadChapterNumbers = TreeSet<Double>()
|
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||||
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||||
|
|
||||||
|
val readChapterNumbers = dbChapters
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.read && it.isRecognizedNumber }
|
||||||
|
.map { it.chapterNumber }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
removedChapters.forEach { chapter ->
|
removedChapters.forEach { chapter ->
|
||||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||||
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
||||||
@@ -165,12 +173,20 @@ class SyncChaptersWithSource(
|
|||||||
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
||||||
.associate { it.chapterNumber to it.dateFetch }
|
.associate { it.chapterNumber to it.dateFetch }
|
||||||
|
|
||||||
|
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
|
||||||
|
.contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_NEW)
|
||||||
|
|
||||||
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
var itemCount = newChapters.size
|
var itemCount = newChapters.size
|
||||||
var updatedToAdd = newChapters.map { toAddItem ->
|
var updatedToAdd = newChapters.map { toAddItem ->
|
||||||
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||||
|
|
||||||
|
if (chapter.chapterNumber in readChapterNumbers && markDuplicateAsRead) {
|
||||||
|
changedOrDuplicateReadUrls.add(chapter.url)
|
||||||
|
chapter = chapter.copy(read = true)
|
||||||
|
}
|
||||||
|
|
||||||
if (!chapter.isRecognizedNumber || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
if (!chapter.isRecognizedNumber || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||||
|
|
||||||
chapter = chapter.copy(
|
chapter = chapter.copy(
|
||||||
@@ -183,19 +199,19 @@ class SyncChaptersWithSource(
|
|||||||
chapter = chapter.copy(dateFetch = it)
|
chapter = chapter.copy(dateFetch = it)
|
||||||
}
|
}
|
||||||
|
|
||||||
reAdded.add(chapter)
|
changedOrDuplicateReadUrls.add(chapter.url)
|
||||||
|
|
||||||
chapter
|
chapter
|
||||||
}
|
}
|
||||||
|
|
||||||
// --> EXH (carry over reading progress)
|
// --> EXH (carry over reading progress)
|
||||||
if (manga.isEhBasedManga()) {
|
if (manga.isEhBasedManga()) {
|
||||||
val finalAdded = updatedToAdd.subtract(reAdded)
|
val finalAdded = updatedToAdd.filterNot { it.url in changedOrDuplicateReadUrls }
|
||||||
if (finalAdded.isNotEmpty()) {
|
if (finalAdded.isNotEmpty()) {
|
||||||
val max = dbChapters.maxOfOrNull { it.lastPageRead }
|
val max = dbChapters.maxOfOrNull { it.lastPageRead }
|
||||||
if (max != null && max > 0) {
|
if (max != null && max > 0) {
|
||||||
updatedToAdd = updatedToAdd.map {
|
updatedToAdd = updatedToAdd.map {
|
||||||
if (it !in reAdded) {
|
if (it.url !in changedOrDuplicateReadUrls) {
|
||||||
it.copy(lastPageRead = max)
|
it.copy(lastPageRead = max)
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
@@ -225,12 +241,8 @@ class SyncChaptersWithSource(
|
|||||||
// Note that last_update actually represents last time the chapter list changed at all
|
// Note that last_update actually represents last time the chapter list changed at all
|
||||||
updateManga.awaitUpdateLastUpdate(manga.id)
|
updateManga.awaitUpdateLastUpdate(manga.id)
|
||||||
|
|
||||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
|
||||||
|
|
||||||
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
||||||
|
|
||||||
return updatedToAdd.filterNot {
|
return updatedToAdd.filterNot { it.url in changedOrDuplicateReadUrls || it.scanlator in excludedScanlators }
|
||||||
it.url in reAddedUrls || it.scanlator in excludedScanlators
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ val Manga.readerOrientation: Long
|
|||||||
|
|
||||||
val Manga.downloadedFilter: TriState
|
val Manga.downloadedFilter: TriState
|
||||||
get() {
|
get() {
|
||||||
if (forceDownloaded()) return TriState.ENABLED_IS
|
if (Injekt.get<BasePreferences>().downloadedOnly().get()) return TriState.ENABLED_IS
|
||||||
return when (downloadedFilterRaw) {
|
return when (downloadedFilterRaw) {
|
||||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||||
@@ -35,9 +35,6 @@ fun Manga.chaptersFiltered(): Boolean {
|
|||||||
downloadedFilter != TriState.DISABLED ||
|
downloadedFilter != TriState.DISABLED ||
|
||||||
bookmarkedFilter != TriState.DISABLED
|
bookmarkedFilter != TriState.DISABLED
|
||||||
}
|
}
|
||||||
fun Manga.forceDownloaded(): Boolean {
|
|
||||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Manga.toSManga(): SManga = SManga.create().also {
|
fun Manga.toSManga(): SManga = SManga.create().also {
|
||||||
it.url = url
|
it.url = url
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ fun Track.copyPersonalFrom(other: Track): Track {
|
|||||||
status = other.status,
|
status = other.status,
|
||||||
startDate = other.startDate,
|
startDate = other.startDate,
|
||||||
finishDate = other.finishDate,
|
finishDate = other.finishDate,
|
||||||
|
private = other.private,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also {
|
|||||||
it.tracking_url = remoteUrl
|
it.tracking_url = remoteUrl
|
||||||
it.started_reading_date = startDate
|
it.started_reading_date = startDate
|
||||||
it.finished_reading_date = finishDate
|
it.finished_reading_date = finishDate
|
||||||
|
it.private = private
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
||||||
@@ -44,5 +46,6 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
|||||||
remoteUrl = tracking_url,
|
remoteUrl = tracking_url,
|
||||||
startDate = started_reading_date,
|
startDate = started_reading_date,
|
||||||
finishDate = finished_reading_date,
|
finishDate = finished_reading_date,
|
||||||
|
private = private,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,16 @@ 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,
|
||||||
|
)
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
fun resolveUsingSourceMetadata() = preferenceStore.getBoolean(
|
||||||
|
"pref_resolve_using_source_metadata_key",
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ enum class AppTheme(val titleRes: StringResource?) {
|
|||||||
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
||||||
YINYANG(MR.strings.theme_yinyang),
|
YINYANG(MR.strings.theme_yinyang),
|
||||||
YOTSUBA(MR.strings.theme_yotsuba),
|
YOTSUBA(MR.strings.theme_yotsuba),
|
||||||
|
MONOCHROME(MR.strings.theme_monochrome),
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
DARK_BLUE(null),
|
DARK_BLUE(null),
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -19,6 +19,7 @@ import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
|
import exh.metadata.metadata.RankedSearchMetadata
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@@ -119,6 +120,14 @@ private fun BrowseSourceComfortableGridItem(
|
|||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (metadata is RankedSearchMetadata) {
|
||||||
|
metadata.rank?.let {
|
||||||
|
Badge(
|
||||||
|
text = "+$it",
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import eu.kanade.presentation.library.components.MangaCompactGridItem
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
|
import exh.metadata.metadata.RankedSearchMetadata
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@@ -119,6 +120,14 @@ private fun BrowseSourceCompactGridItem(
|
|||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (metadata is RankedSearchMetadata) {
|
||||||
|
metadata.rank?.let {
|
||||||
|
Badge(
|
||||||
|
text = "+$it",
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import eu.kanade.presentation.library.components.MangaListItem
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
|
import exh.metadata.metadata.RankedSearchMetadata
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@@ -110,6 +111,14 @@ private fun BrowseSourceListItem(
|
|||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (metadata is RankedSearchMetadata) {
|
||||||
|
metadata.rank?.let {
|
||||||
|
Badge(
|
||||||
|
text = "+$it",
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,23 +2,25 @@ package eu.kanade.presentation.category
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.SortByAlpha
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
||||||
import eu.kanade.presentation.category.components.CategoryListItem
|
import eu.kanade.presentation.category.components.CategoryListItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreenState
|
import eu.kanade.tachiyomi.ui.category.CategoryScreenState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@@ -32,11 +34,9 @@ import tachiyomi.presentation.core.util.plus
|
|||||||
fun CategoryScreen(
|
fun CategoryScreen(
|
||||||
state: CategoryScreenState.Success,
|
state: CategoryScreenState.Success,
|
||||||
onClickCreate: () -> Unit,
|
onClickCreate: () -> Unit,
|
||||||
onClickSortAlphabetically: () -> Unit,
|
|
||||||
onClickRename: (Category) -> Unit,
|
onClickRename: (Category) -> Unit,
|
||||||
onClickDelete: (Category) -> Unit,
|
onClickDelete: (Category) -> Unit,
|
||||||
onClickMoveUp: (Category) -> Unit,
|
onChangeOrder: (Category, Int) -> Unit,
|
||||||
onClickMoveDown: (Category) -> Unit,
|
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
@@ -45,17 +45,6 @@ fun CategoryScreen(
|
|||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(MR.strings.action_edit_categories),
|
title = stringResource(MR.strings.action_edit_categories),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
actions = {
|
|
||||||
AppBarActions(
|
|
||||||
persistentListOf(
|
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(MR.strings.action_sort),
|
|
||||||
icon = Icons.Outlined.SortByAlpha,
|
|
||||||
onClick = onClickSortAlphabetically,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -77,12 +66,10 @@ fun CategoryScreen(
|
|||||||
CategoryContent(
|
CategoryContent(
|
||||||
categories = state.categories,
|
categories = state.categories,
|
||||||
lazyListState = lazyListState,
|
lazyListState = lazyListState,
|
||||||
paddingValues = paddingValues + topSmallPaddingValues +
|
paddingValues = paddingValues,
|
||||||
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
|
||||||
onClickRename = onClickRename,
|
onClickRename = onClickRename,
|
||||||
onClickDelete = onClickDelete,
|
onClickDelete = onClickDelete,
|
||||||
onMoveUp = onClickMoveUp,
|
onChangeOrder = onChangeOrder,
|
||||||
onMoveDown = onClickMoveDown,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,28 +81,44 @@ private fun CategoryContent(
|
|||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
onClickRename: (Category) -> Unit,
|
onClickRename: (Category) -> Unit,
|
||||||
onClickDelete: (Category) -> Unit,
|
onClickDelete: (Category) -> Unit,
|
||||||
onMoveUp: (Category) -> Unit,
|
onChangeOrder: (Category, Int) -> Unit,
|
||||||
onMoveDown: (Category) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
|
val categoriesState = remember { categories.toMutableStateList() }
|
||||||
|
val reorderableState = rememberReorderableLazyListState(lazyListState, paddingValues) { from, to ->
|
||||||
|
val item = categoriesState.removeAt(from.index)
|
||||||
|
categoriesState.add(to.index, item)
|
||||||
|
onChangeOrder(item, to.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(categories) {
|
||||||
|
if (!reorderableState.isAnyItemDragging) {
|
||||||
|
categoriesState.clear()
|
||||||
|
categoriesState.addAll(categories)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues +
|
||||||
|
topSmallPaddingValues +
|
||||||
|
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
itemsIndexed(
|
items(
|
||||||
items = categories,
|
items = categoriesState,
|
||||||
key = { _, category -> "category-${category.id}" },
|
key = { category -> category.key },
|
||||||
) { index, category ->
|
) { category ->
|
||||||
CategoryListItem(
|
ReorderableItem(reorderableState, category.key) {
|
||||||
modifier = Modifier.animateItem(),
|
CategoryListItem(
|
||||||
category = category,
|
modifier = Modifier.animateItem(),
|
||||||
canMoveUp = index != 0,
|
category = category,
|
||||||
canMoveDown = index != categories.lastIndex,
|
onRename = { onClickRename(category) },
|
||||||
onMoveUp = onMoveUp,
|
onDelete = { onClickDelete(category) },
|
||||||
onMoveDown = onMoveDown,
|
)
|
||||||
onRename = { onClickRename(category) },
|
}
|
||||||
onDelete = { onClickDelete(category) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val Category.key inline get() = "category-$id"
|
||||||
|
|||||||
@@ -219,35 +219,6 @@ fun CategoryDeleteDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun CategorySortAlphabeticallyDialog(
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onSort: () -> Unit,
|
|
||||||
) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
onSort()
|
|
||||||
onDismissRequest()
|
|
||||||
}) {
|
|
||||||
Text(text = stringResource(MR.strings.action_ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(MR.strings.action_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title = {
|
|
||||||
Text(text = stringResource(MR.strings.action_sort_category))
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(text = stringResource(MR.strings.sort_category_confirmation))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChangeCategoryDialog(
|
fun ChangeCategoryDialog(
|
||||||
initialSelection: ImmutableList<CheckboxState<Category>>,
|
initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ package eu.kanade.presentation.category.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
|
||||||
import androidx.compose.material.icons.outlined.ArrowDropDown
|
|
||||||
import androidx.compose.material.icons.outlined.ArrowDropUp
|
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
import androidx.compose.material.icons.outlined.DragHandle
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -19,57 +16,42 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import sh.calvin.reorderable.ReorderableCollectionItemScope
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryListItem(
|
fun ReorderableCollectionItemScope.CategoryListItem(
|
||||||
category: Category,
|
category: Category,
|
||||||
canMoveUp: Boolean,
|
|
||||||
canMoveDown: Boolean,
|
|
||||||
onMoveUp: (Category) -> Unit,
|
|
||||||
onMoveDown: (Category) -> Unit,
|
|
||||||
onRename: () -> Unit,
|
onRename: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(modifier = modifier) {
|
||||||
modifier = modifier,
|
|
||||||
) {
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onRename() }
|
.clickable(onClick = onRename)
|
||||||
|
.padding(vertical = MaterialTheme.padding.small)
|
||||||
.padding(
|
.padding(
|
||||||
start = MaterialTheme.padding.medium,
|
start = MaterialTheme.padding.small,
|
||||||
top = MaterialTheme.padding.medium,
|
|
||||||
end = MaterialTheme.padding.medium,
|
end = MaterialTheme.padding.medium,
|
||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.DragHandle,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(MaterialTheme.padding.medium)
|
||||||
|
.draggableHandle(),
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = category.name,
|
text = category.name,
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.padding(start = MaterialTheme.padding.medium),
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
Row {
|
|
||||||
IconButton(
|
|
||||||
onClick = { onMoveUp(category) },
|
|
||||||
enabled = canMoveUp,
|
|
||||||
) {
|
|
||||||
Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = null)
|
|
||||||
}
|
|
||||||
IconButton(
|
|
||||||
onClick = { onMoveDown(category) },
|
|
||||||
enabled = canMoveDown,
|
|
||||||
) {
|
|
||||||
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = null)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
IconButton(onClick = onRename) {
|
IconButton(onClick = onRename) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Edit,
|
imageVector = Icons.Outlined.Edit,
|
||||||
@@ -77,7 +59,10 @@ fun CategoryListItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onDelete) {
|
IconButton(onClick = onDelete) {
|
||||||
Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(MR.strings.action_delete))
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Delete,
|
||||||
|
contentDescription = stringResource(MR.strings.action_delete),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ fun HistoryScreen(
|
|||||||
onSearchQueryChange: (String?) -> Unit,
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
onClickCover: (mangaId: Long) -> Unit,
|
onClickCover: (mangaId: Long) -> Unit,
|
||||||
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
||||||
|
onClickFavorite: (mangaId: Long) -> Unit,
|
||||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -85,6 +86,7 @@ fun HistoryScreen(
|
|||||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
||||||
|
onClickFavorite = { history -> onClickFavorite(history.mangaId) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,6 +100,7 @@ private fun HistoryScreenContent(
|
|||||||
onClickCover: (HistoryWithRelations) -> Unit,
|
onClickCover: (HistoryWithRelations) -> Unit,
|
||||||
onClickResume: (HistoryWithRelations) -> Unit,
|
onClickResume: (HistoryWithRelations) -> Unit,
|
||||||
onClickDelete: (HistoryWithRelations) -> Unit,
|
onClickDelete: (HistoryWithRelations) -> Unit,
|
||||||
|
onClickFavorite: (HistoryWithRelations) -> Unit,
|
||||||
) {
|
) {
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
@@ -127,6 +130,7 @@ private fun HistoryScreenContent(
|
|||||||
onClickCover = { onClickCover(value) },
|
onClickCover = { onClickCover(value) },
|
||||||
onClickResume = { onClickResume(value) },
|
onClickResume = { onClickResume(value) },
|
||||||
onClickDelete = { onClickDelete(value) },
|
onClickDelete = { onClickDelete(value) },
|
||||||
|
onClickFavorite = { onClickFavorite(value) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,6 +157,7 @@ internal fun HistoryScreenPreviews(
|
|||||||
onClickCover = {},
|
onClickCover = {},
|
||||||
onClickResume = { _, _ -> run {} },
|
onClickResume = { _, _ -> run {} },
|
||||||
onDialogChange = {},
|
onDialogChange = {},
|
||||||
|
onClickFavorite = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
import androidx.compose.material.icons.outlined.FavoriteBorder
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -39,6 +40,7 @@ fun HistoryItem(
|
|||||||
onClickCover: () -> Unit,
|
onClickCover: () -> Unit,
|
||||||
onClickResume: () -> Unit,
|
onClickResume: () -> Unit,
|
||||||
onClickDelete: () -> Unit,
|
onClickDelete: () -> Unit,
|
||||||
|
onClickFavorite: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -82,6 +84,16 @@ fun HistoryItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!history.coverData.isMangaFavorite) {
|
||||||
|
IconButton(onClick = onClickFavorite) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.FavoriteBorder,
|
||||||
|
contentDescription = stringResource(MR.strings.add_to_library),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(onClick = onClickDelete) {
|
IconButton(onClick = onClickDelete) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Delete,
|
imageVector = Icons.Outlined.Delete,
|
||||||
@@ -105,6 +117,7 @@ private fun HistoryItemPreviews(
|
|||||||
onClickCover = {},
|
onClickCover = {},
|
||||||
onClickResume = {},
|
onClickResume = {},
|
||||||
onClickDelete = {},
|
onClickDelete = {},
|
||||||
|
onClickFavorite = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -309,15 +310,16 @@ private fun ColumnScope.DisplayPage(
|
|||||||
|
|
||||||
val columns by columnPreference.collectAsState()
|
val columns by columnPreference.collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(MR.strings.pref_library_columns),
|
|
||||||
max = 10,
|
|
||||||
value = columns,
|
value = columns,
|
||||||
|
valueRange = 0..10,
|
||||||
|
label = stringResource(MR.strings.pref_library_columns),
|
||||||
valueText = if (columns > 0) {
|
valueText = if (columns > 0) {
|
||||||
stringResource(MR.strings.pref_library_columns_per_row, columns)
|
columns.toString()
|
||||||
} else {
|
} else {
|
||||||
stringResource(MR.strings.label_default)
|
stringResource(MR.strings.label_auto)
|
||||||
},
|
},
|
||||||
onChange = columnPreference::set,
|
onChange = columnPreference::set,
|
||||||
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,6 +328,10 @@ private fun ColumnScope.DisplayPage(
|
|||||||
label = stringResource(MR.strings.action_display_download_badge),
|
label = stringResource(MR.strings.action_display_download_badge),
|
||||||
pref = screenModel.libraryPreferences.downloadBadge(),
|
pref = screenModel.libraryPreferences.downloadBadge(),
|
||||||
)
|
)
|
||||||
|
CheckboxItem(
|
||||||
|
label = stringResource(MR.strings.action_display_unread_badge),
|
||||||
|
pref = screenModel.libraryPreferences.unreadBadge(),
|
||||||
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_local_badge),
|
label = stringResource(MR.strings.action_display_local_badge),
|
||||||
pref = screenModel.libraryPreferences.localBadge(),
|
pref = screenModel.libraryPreferences.localBadge(),
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
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.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.manga.model.downloadedFilter
|
import eu.kanade.domain.manga.model.downloadedFilter
|
||||||
import eu.kanade.domain.manga.model.forceDownloaded
|
|
||||||
import eu.kanade.presentation.components.TabbedDialog
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@@ -40,6 +41,8 @@ import tachiyomi.presentation.core.components.SortItem
|
|||||||
import tachiyomi.presentation.core.components.TriStateItem
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.theme.active
|
import tachiyomi.presentation.core.theme.active
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterSettingsDialog(
|
fun ChapterSettingsDialog(
|
||||||
@@ -63,6 +66,8 @@ fun ChapterSettingsDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly().get() }
|
||||||
|
|
||||||
TabbedDialog(
|
TabbedDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
tabTitles = persistentListOf(
|
tabTitles = persistentListOf(
|
||||||
@@ -97,7 +102,7 @@ fun ChapterSettingsDialog(
|
|||||||
FilterPage(
|
FilterPage(
|
||||||
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
||||||
onDownloadFilterChanged = onDownloadFilterChanged
|
onDownloadFilterChanged = onDownloadFilterChanged
|
||||||
.takeUnless { manga?.forceDownloaded() == true },
|
.takeUnless { downloadedOnly },
|
||||||
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
||||||
onUnreadFilterChanged = onUnreadFilterChanged,
|
onUnreadFilterChanged = onUnreadFilterChanged,
|
||||||
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ fun MangaScreen(
|
|||||||
isTabletUi: Boolean,
|
isTabletUi: Boolean,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
@@ -182,7 +182,7 @@ fun MangaScreen(
|
|||||||
nextUpdate = nextUpdate,
|
nextUpdate = nextUpdate,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
onBackClicked = onBackClicked,
|
navigateUp = navigateUp,
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
onDownloadChapter = onDownloadChapter,
|
onDownloadChapter = onDownloadChapter,
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
@@ -228,7 +228,7 @@ fun MangaScreen(
|
|||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
nextUpdate = nextUpdate,
|
nextUpdate = nextUpdate,
|
||||||
onBackClicked = onBackClicked,
|
navigateUp = navigateUp,
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
onDownloadChapter = onDownloadChapter,
|
onDownloadChapter = onDownloadChapter,
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
@@ -277,7 +277,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
nextUpdate: Instant?,
|
nextUpdate: Instant?,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
@@ -345,14 +345,13 @@ private fun MangaScreenSmallImpl(
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
BackHandler(onBack = {
|
||||||
if (isAnySelected) {
|
if (isAnySelected) {
|
||||||
onAllChapterSelected(false)
|
onAllChapterSelected(false)
|
||||||
} else {
|
} else {
|
||||||
onBackClicked()
|
navigateUp()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
BackHandler(onBack = internalOnBackPressed)
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -365,20 +364,18 @@ private fun MangaScreenSmallImpl(
|
|||||||
val isFirstItemScrolled by remember {
|
val isFirstItemScrolled by remember {
|
||||||
derivedStateOf { chapterListState.firstVisibleItemScrollOffset > 0 }
|
derivedStateOf { chapterListState.firstVisibleItemScrollOffset > 0 }
|
||||||
}
|
}
|
||||||
val animatedTitleAlpha by animateFloatAsState(
|
val titleAlpha by animateFloatAsState(
|
||||||
if (!isFirstItemVisible) 1f else 0f,
|
if (!isFirstItemVisible) 1f else 0f,
|
||||||
label = "Top Bar Title",
|
label = "Top Bar Title",
|
||||||
)
|
)
|
||||||
val animatedBgAlpha by animateFloatAsState(
|
val backgroundAlpha by animateFloatAsState(
|
||||||
if (!isFirstItemVisible || isFirstItemScrolled) 1f else 0f,
|
if (!isFirstItemVisible || isFirstItemScrolled) 1f else 0f,
|
||||||
label = "Top Bar Background",
|
label = "Top Bar Background",
|
||||||
)
|
)
|
||||||
MangaToolbar(
|
MangaToolbar(
|
||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
titleAlphaProvider = { animatedTitleAlpha },
|
|
||||||
backgroundAlphaProvider = { animatedBgAlpha },
|
|
||||||
hasFilters = state.filterActive,
|
hasFilters = state.filterActive,
|
||||||
onBackClicked = internalOnBackPressed,
|
navigateUp = navigateUp,
|
||||||
onClickFilter = onFilterClicked,
|
onClickFilter = onFilterClicked,
|
||||||
onClickShare = onShareClicked,
|
onClickShare = onShareClicked,
|
||||||
onClickDownload = onDownloadActionClicked,
|
onClickDownload = onDownloadActionClicked,
|
||||||
@@ -392,8 +389,11 @@ private fun MangaScreenSmallImpl(
|
|||||||
onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow },
|
onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow },
|
||||||
// SY <--
|
// SY <--
|
||||||
actionModeCounter = selectedChapterCount,
|
actionModeCounter = selectedChapterCount,
|
||||||
|
onCancelActionMode = { onAllChapterSelected(false) },
|
||||||
onSelectAll = { onAllChapterSelected(true) },
|
onSelectAll = { onAllChapterSelected(true) },
|
||||||
onInvertSelection = { onInvertSelection() },
|
onInvertSelection = { onInvertSelection() },
|
||||||
|
titleAlphaProvider = { titleAlpha },
|
||||||
|
backgroundAlphaProvider = { backgroundAlpha },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
@@ -600,7 +600,7 @@ fun MangaScreenLargeImpl(
|
|||||||
nextUpdate: Instant?,
|
nextUpdate: Instant?,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
@@ -672,14 +672,13 @@ fun MangaScreenLargeImpl(
|
|||||||
|
|
||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
BackHandler(onBack = {
|
||||||
if (isAnySelected) {
|
if (isAnySelected) {
|
||||||
onAllChapterSelected(false)
|
onAllChapterSelected(false)
|
||||||
} else {
|
} else {
|
||||||
onBackClicked()
|
navigateUp()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
BackHandler(onBack = internalOnBackPressed)
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -689,10 +688,8 @@ fun MangaScreenLargeImpl(
|
|||||||
MangaToolbar(
|
MangaToolbar(
|
||||||
modifier = Modifier.onSizeChanged { topBarHeight = it.height },
|
modifier = Modifier.onSizeChanged { topBarHeight = it.height },
|
||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
|
|
||||||
backgroundAlphaProvider = { 1f },
|
|
||||||
hasFilters = state.filterActive,
|
hasFilters = state.filterActive,
|
||||||
onBackClicked = internalOnBackPressed,
|
navigateUp = navigateUp,
|
||||||
onClickFilter = onFilterButtonClicked,
|
onClickFilter = onFilterButtonClicked,
|
||||||
onClickShare = onShareClicked,
|
onClickShare = onShareClicked,
|
||||||
onClickDownload = onDownloadActionClicked,
|
onClickDownload = onDownloadActionClicked,
|
||||||
@@ -705,9 +702,12 @@ fun MangaScreenLargeImpl(
|
|||||||
onClickMergedSettings = onMergedSettingsClicked.takeIf { state.manga.source == MERGED_SOURCE_ID },
|
onClickMergedSettings = onMergedSettingsClicked.takeIf { state.manga.source == MERGED_SOURCE_ID },
|
||||||
onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow },
|
onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow },
|
||||||
// SY <--
|
// SY <--
|
||||||
|
onCancelActionMode = { onAllChapterSelected(false) },
|
||||||
actionModeCounter = selectedChapterCount,
|
actionModeCounter = selectedChapterCount,
|
||||||
onSelectAll = { onAllChapterSelected(true) },
|
onSelectAll = { onAllChapterSelected(true) },
|
||||||
onInvertSelection = { onInvertSelection() },
|
onInvertSelection = { onInvertSelection() },
|
||||||
|
titleAlphaProvider = { 1f },
|
||||||
|
backgroundAlphaProvider = { 1f },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import androidx.compose.material.icons.outlined.BookmarkRemove
|
|||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.DoneAll
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
import androidx.compose.material.icons.outlined.Download
|
import androidx.compose.material.icons.outlined.Download
|
||||||
import androidx.compose.material.icons.outlined.Label
|
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
import androidx.compose.material.icons.outlined.RemoveDone
|
import androidx.compose.material.icons.outlined.RemoveDone
|
||||||
import androidx.compose.material.icons.outlined.SwapCalls
|
import androidx.compose.material.icons.outlined.SwapCalls
|
||||||
@@ -237,6 +236,7 @@ fun LibraryBottomActionMenu(
|
|||||||
// SY -->
|
// SY -->
|
||||||
onClickCleanTitles: (() -> Unit)?,
|
onClickCleanTitles: (() -> Unit)?,
|
||||||
onClickMigrate: (() -> Unit)?,
|
onClickMigrate: (() -> Unit)?,
|
||||||
|
onClickCollectRecommendations: (() -> Unit)?,
|
||||||
onClickAddToMangaDex: (() -> Unit)?,
|
onClickAddToMangaDex: (() -> Unit)?,
|
||||||
onClickResetInfo: (() -> Unit)?,
|
onClickResetInfo: (() -> Unit)?,
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -267,7 +267,10 @@ fun LibraryBottomActionMenu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SY -->
|
// SY -->
|
||||||
val showOverflow = onClickCleanTitles != null || onClickAddToMangaDex != null || onClickResetInfo != null
|
val showOverflow = onClickCleanTitles != null ||
|
||||||
|
onClickAddToMangaDex != null ||
|
||||||
|
onClickResetInfo != null ||
|
||||||
|
onClickCollectRecommendations != null
|
||||||
val configuration = LocalConfiguration.current
|
val configuration = LocalConfiguration.current
|
||||||
val moveMarkPrev = remember { !configuration.isTabletUi() }
|
val moveMarkPrev = remember { !configuration.isTabletUi() }
|
||||||
var overFlowOpen by remember { mutableStateOf(false) }
|
var overFlowOpen by remember { mutableStateOf(false) }
|
||||||
@@ -358,6 +361,12 @@ fun LibraryBottomActionMenu(
|
|||||||
onClick = onClickMigrate,
|
onClick = onClickMigrate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (onClickCollectRecommendations != null) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(SYMR.strings.rec_search_short)) },
|
||||||
|
onClick = onClickCollectRecommendations,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (onClickAddToMangaDex != null) {
|
if (onClickAddToMangaDex != null) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(SYMR.strings.mangadex_add_to_follows)) },
|
text = { Text(stringResource(SYMR.strings.mangadex_add_to_follows)) },
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Close
|
|
||||||
import androidx.compose.material.icons.outlined.Download
|
import androidx.compose.material.icons.outlined.Download
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
import androidx.compose.material.icons.outlined.FlipToBack
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
import androidx.compose.material.icons.outlined.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -20,12 +14,12 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.components.AppBarTitle
|
||||||
import eu.kanade.presentation.components.DownloadDropdownMenu
|
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||||
import eu.kanade.presentation.components.UpIcon
|
|
||||||
import eu.kanade.presentation.manga.DownloadAction
|
import eu.kanade.presentation.manga.DownloadAction
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -36,9 +30,8 @@ import tachiyomi.presentation.core.theme.active
|
|||||||
@Composable
|
@Composable
|
||||||
fun MangaToolbar(
|
fun MangaToolbar(
|
||||||
title: String,
|
title: String,
|
||||||
titleAlphaProvider: () -> Float,
|
|
||||||
hasFilters: Boolean,
|
hasFilters: Boolean,
|
||||||
onBackClicked: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onClickFilter: () -> Unit,
|
onClickFilter: () -> Unit,
|
||||||
onClickShare: (() -> Unit)?,
|
onClickShare: (() -> Unit)?,
|
||||||
onClickDownload: ((DownloadAction) -> Unit)?,
|
onClickDownload: ((DownloadAction) -> Unit)?,
|
||||||
@@ -54,152 +47,145 @@ fun MangaToolbar(
|
|||||||
|
|
||||||
// For action mode
|
// For action mode
|
||||||
actionModeCounter: Int,
|
actionModeCounter: Int,
|
||||||
|
onCancelActionMode: () -> Unit,
|
||||||
onSelectAll: () -> Unit,
|
onSelectAll: () -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
|
|
||||||
|
titleAlphaProvider: () -> Float,
|
||||||
|
backgroundAlphaProvider: () -> Float,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
backgroundAlphaProvider: () -> Float = titleAlphaProvider,
|
|
||||||
) {
|
) {
|
||||||
Column(
|
val isActionMode = actionModeCounter > 0
|
||||||
|
AppBar(
|
||||||
|
titleContent = {
|
||||||
|
if (isActionMode) {
|
||||||
|
AppBarTitle(actionModeCounter.toString())
|
||||||
|
} else {
|
||||||
|
AppBarTitle(title, modifier = Modifier.alpha(titleAlphaProvider()))
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
backgroundColor = MaterialTheme.colorScheme
|
||||||
val isActionMode = actionModeCounter > 0
|
.surfaceColorAtElevation(3.dp)
|
||||||
TopAppBar(
|
.copy(alpha = if (isActionMode) 1f else backgroundAlphaProvider()),
|
||||||
title = {
|
navigateUp = navigateUp,
|
||||||
Text(
|
actions = {
|
||||||
text = if (isActionMode) actionModeCounter.toString() else title,
|
var downloadExpanded by remember { mutableStateOf(false) }
|
||||||
maxLines = 1,
|
if (onClickDownload != null) {
|
||||||
overflow = TextOverflow.Ellipsis,
|
val onDismissRequest = { downloadExpanded = false }
|
||||||
color = LocalContentColor.current.copy(alpha = if (isActionMode) 1f else titleAlphaProvider()),
|
DownloadDropdownMenu(
|
||||||
|
expanded = downloadExpanded,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onDownloadClicked = onClickDownload,
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onBackClicked) {
|
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||||
UpIcon(navigationIcon = Icons.Outlined.Close.takeIf { isActionMode })
|
AppBarActions(
|
||||||
}
|
actions = persistentListOf<AppBar.AppBarAction>().builder().apply {
|
||||||
},
|
if (isActionMode) {
|
||||||
actions = {
|
add(
|
||||||
if (isActionMode) {
|
|
||||||
AppBarActions(
|
|
||||||
persistentListOf(
|
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.action_select_all),
|
title = stringResource(MR.strings.action_select_all),
|
||||||
icon = Icons.Outlined.SelectAll,
|
icon = Icons.Outlined.SelectAll,
|
||||||
onClick = onSelectAll,
|
onClick = onSelectAll,
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
add(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.action_select_inverse),
|
title = stringResource(MR.strings.action_select_inverse),
|
||||||
icon = Icons.Outlined.FlipToBack,
|
icon = Icons.Outlined.FlipToBack,
|
||||||
onClick = onInvertSelection,
|
onClick = onInvertSelection,
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
return@apply
|
||||||
} else {
|
}
|
||||||
var downloadExpanded by remember { mutableStateOf(false) }
|
|
||||||
if (onClickDownload != null) {
|
if (onClickDownload != null) {
|
||||||
val onDismissRequest = { downloadExpanded = false }
|
add(
|
||||||
DownloadDropdownMenu(
|
AppBar.Action(
|
||||||
expanded = downloadExpanded,
|
title = stringResource(MR.strings.manga_download),
|
||||||
onDismissRequest = onDismissRequest,
|
icon = Icons.Outlined.Download,
|
||||||
onDownloadClicked = onClickDownload,
|
onClick = { downloadExpanded = !downloadExpanded },
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
add(
|
||||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
AppBar.Action(
|
||||||
AppBarActions(
|
title = stringResource(MR.strings.action_filter),
|
||||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
icon = Icons.Outlined.FilterList,
|
||||||
.apply {
|
iconTint = filterTint,
|
||||||
if (onClickDownload != null) {
|
onClick = onClickFilter,
|
||||||
add(
|
),
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(MR.strings.manga_download),
|
|
||||||
icon = Icons.Outlined.Download,
|
|
||||||
onClick = { downloadExpanded = !downloadExpanded },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
add(
|
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(MR.strings.action_filter),
|
|
||||||
icon = Icons.Outlined.FilterList,
|
|
||||||
iconTint = filterTint,
|
|
||||||
onClick = onClickFilter,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_webview_refresh),
|
|
||||||
onClick = onClickRefresh,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (onClickEditCategory != null) {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_edit_categories),
|
|
||||||
onClick = onClickEditCategory,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (onClickMigrate != null) {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_migrate),
|
|
||||||
onClick = onClickMigrate,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (onClickShare != null) {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_share),
|
|
||||||
onClick = onClickShare,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// SY -->
|
|
||||||
if (onClickMerge != null) {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(SYMR.strings.merge),
|
|
||||||
onClick = onClickMerge,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (onClickEditInfo != null) {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(SYMR.strings.action_edit_info),
|
|
||||||
onClick = onClickEditInfo,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (onClickRecommend != null) {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(SYMR.strings.az_recommends),
|
|
||||||
onClick = onClickRecommend,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (onClickMergedSettings != null) {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(SYMR.strings.merge_settings),
|
|
||||||
onClick = onClickMergedSettings,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
}
|
|
||||||
.build(),
|
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_webview_refresh),
|
||||||
|
onClick = onClickRefresh,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (onClickEditCategory != null) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_edit_categories),
|
||||||
|
onClick = onClickEditCategory,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (onClickMigrate != null) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_migrate),
|
||||||
|
onClick = onClickMigrate,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (onClickShare != null) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_share),
|
||||||
|
onClick = onClickShare,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// SY -->
|
||||||
|
if (onClickMerge != null) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(SYMR.strings.merge),
|
||||||
|
onClick = onClickMerge,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (onClickEditInfo != null) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(SYMR.strings.action_edit_info),
|
||||||
|
onClick = onClickEditInfo,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (onClickRecommend != null) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(SYMR.strings.az_recommends),
|
||||||
|
onClick = onClickRecommend,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (onClickMergedSettings != null) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(SYMR.strings.merge_settings),
|
||||||
|
onClick = onClickMergedSettings,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
},
|
.build(),
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
)
|
||||||
containerColor = MaterialTheme.colorScheme
|
},
|
||||||
.surfaceColorAtElevation(3.dp)
|
isActionMode = isActionMode,
|
||||||
.copy(alpha = if (isActionMode) 1f else backgroundAlphaProvider()),
|
onCancelActionMode = onCancelActionMode,
|
||||||
),
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
package eu.kanade.presentation.more
|
package eu.kanade.presentation.more
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
import androidx.compose.foundation.layout.only
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||||
@@ -29,7 +22,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import eu.kanade.presentation.components.WarningBanner
|
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@@ -49,7 +41,6 @@ fun MoreScreen(
|
|||||||
onDownloadedOnlyChange: (Boolean) -> Unit,
|
onDownloadedOnlyChange: (Boolean) -> Unit,
|
||||||
incognitoMode: Boolean,
|
incognitoMode: Boolean,
|
||||||
onIncognitoModeChange: (Boolean) -> Unit,
|
onIncognitoModeChange: (Boolean) -> Unit,
|
||||||
isFDroid: Boolean,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
showNavUpdates: Boolean,
|
showNavUpdates: Boolean,
|
||||||
showNavHistory: Boolean,
|
showNavHistory: Boolean,
|
||||||
@@ -66,26 +57,7 @@ fun MoreScreen(
|
|||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
Scaffold(
|
Scaffold { contentPadding ->
|
||||||
topBar = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.windowInsetsPadding(
|
|
||||||
WindowInsets.systemBars.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
if (isFDroid) {
|
|
||||||
WarningBanner(
|
|
||||||
textRes = MR.strings.fdroid_warning,
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
uriHandler.openUri(
|
|
||||||
"https://mihon.app/docs/faq/general#how-do-i-update-from-the-f-droid-builds",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) { contentPadding ->
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.Manifest
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
@@ -32,6 +31,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
@@ -111,7 +111,7 @@ internal class PermissionStep : OnboardingStep {
|
|||||||
onButtonClick = {
|
onButtonClick = {
|
||||||
@SuppressLint("BatteryLife")
|
@SuppressLint("BatteryLife")
|
||||||
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
||||||
data = Uri.parse("package:${context.packageName}")
|
data = "package:${context.packageName}".toUri()
|
||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.more.settings
|
package eu.kanade.presentation.more.settings
|
||||||
|
|
||||||
|
import androidx.annotation.IntRange
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@@ -20,7 +21,7 @@ sealed class Preference {
|
|||||||
|
|
||||||
// SY <--
|
// SY <--
|
||||||
abstract val icon: ImageVector?
|
abstract val icon: ImageVector?
|
||||||
abstract val onValueChanged: suspend (newValue: T) -> Boolean
|
abstract val onValueChanged: suspend (value: T) -> Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic [PreferenceItem] that only displays texts.
|
* A basic [PreferenceItem] that only displays texts.
|
||||||
@@ -28,57 +29,58 @@ sealed class Preference {
|
|||||||
data class TextPreference(
|
data class TextPreference(
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: CharSequence? = null,
|
override val subtitle: CharSequence? = null,
|
||||||
override val icon: ImageVector? = null,
|
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
|
|
||||||
|
|
||||||
val onClick: (() -> Unit)? = null,
|
val onClick: (() -> Unit)? = null,
|
||||||
) : PreferenceItem<String>()
|
) : PreferenceItem<String>() {
|
||||||
|
override val icon: ImageVector? = null
|
||||||
|
override val onValueChanged: suspend (value: String) -> Boolean = { true }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] that provides a two-state toggleable option.
|
* A [PreferenceItem] that provides a two-state toggleable option.
|
||||||
*/
|
*/
|
||||||
data class SwitchPreference(
|
data class SwitchPreference(
|
||||||
val pref: PreferenceData<Boolean>,
|
val preference: PreferenceData<Boolean>,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: CharSequence? = null,
|
override val subtitle: CharSequence? = null,
|
||||||
override val icon: ImageVector? = null,
|
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: Boolean) -> Boolean = { true },
|
override val onValueChanged: suspend (value: Boolean) -> Boolean = { true },
|
||||||
) : PreferenceItem<Boolean>()
|
) : PreferenceItem<Boolean>() {
|
||||||
|
override val icon: ImageVector? = null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] that provides a slider to select an integer number.
|
* A [PreferenceItem] that provides a slider to select an integer number.
|
||||||
*/
|
*/
|
||||||
data class SliderPreference(
|
data class SliderPreference(
|
||||||
val value: Int,
|
val value: Int,
|
||||||
val min: Int = 0,
|
override val title: String,
|
||||||
val max: Int,
|
val valueRange: IntProgression = 0..1,
|
||||||
override val title: String = "",
|
@IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 },
|
||||||
override val subtitle: String? = null,
|
override val subtitle: String? = null,
|
||||||
override val icon: ImageVector? = null,
|
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: Int) -> Boolean = { true },
|
override val onValueChanged: suspend (value: Int) -> Boolean = { true },
|
||||||
) : PreferenceItem<Int>()
|
) : PreferenceItem<Int>() {
|
||||||
|
override val icon: ImageVector? = null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] that displays a list of entries as a dialog.
|
* A [PreferenceItem] that displays a list of entries as a dialog.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
data class ListPreference<T>(
|
data class ListPreference<T>(
|
||||||
val pref: PreferenceData<T>,
|
val preference: PreferenceData<T>,
|
||||||
|
val entries: ImmutableMap<T, String>,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: String? = "%s",
|
override val subtitle: String? = "%s",
|
||||||
val subtitleProvider: @Composable (value: T, entries: ImmutableMap<T, String>) -> String? =
|
val subtitleProvider: @Composable (value: T, entries: ImmutableMap<T, String>) -> String? =
|
||||||
{ v, e -> subtitle?.format(e[v]) },
|
{ v, e -> subtitle?.format(e[v]) },
|
||||||
override val icon: ImageVector? = null,
|
override val icon: ImageVector? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: T) -> Boolean = { true },
|
override val onValueChanged: suspend (value: T) -> Boolean = { true },
|
||||||
|
|
||||||
val entries: ImmutableMap<T, String>,
|
|
||||||
) : PreferenceItem<T>() {
|
) : PreferenceItem<T>() {
|
||||||
internal fun internalSet(newValue: Any) = pref.set(newValue as T)
|
internal fun internalSet(value: Any) = preference.set(value as T)
|
||||||
internal suspend fun internalOnValueChanged(newValue: Any) = onValueChanged(newValue as T)
|
internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun internalSubtitleProvider(value: Any?, entries: ImmutableMap<out Any?, String>) =
|
internal fun internalSubtitleProvider(value: Any?, entries: ImmutableMap<out Any?, String>) =
|
||||||
@@ -90,15 +92,14 @@ sealed class Preference {
|
|||||||
*/
|
*/
|
||||||
data class BasicListPreference(
|
data class BasicListPreference(
|
||||||
val value: String,
|
val value: String,
|
||||||
|
val entries: ImmutableMap<String, String>,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: String? = "%s",
|
override val subtitle: String? = "%s",
|
||||||
val subtitleProvider: @Composable (value: String, entries: ImmutableMap<String, String>) -> String? =
|
val subtitleProvider: @Composable (value: String, entries: ImmutableMap<String, String>) -> String? =
|
||||||
{ v, e -> subtitle?.format(e[v]) },
|
{ v, e -> subtitle?.format(e[v]) },
|
||||||
override val icon: ImageVector? = null,
|
override val icon: ImageVector? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
|
override val onValueChanged: suspend (value: String) -> Boolean = { true },
|
||||||
|
|
||||||
val entries: ImmutableMap<String, String>,
|
|
||||||
) : PreferenceItem<String>()
|
) : PreferenceItem<String>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,52 +107,51 @@ sealed class Preference {
|
|||||||
* Multiple entries can be selected at the same time.
|
* Multiple entries can be selected at the same time.
|
||||||
*/
|
*/
|
||||||
data class MultiSelectListPreference(
|
data class MultiSelectListPreference(
|
||||||
val pref: PreferenceData<Set<String>>,
|
val preference: PreferenceData<Set<String>>,
|
||||||
|
val entries: ImmutableMap<String, String>,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: String? = "%s",
|
override val subtitle: String? = "%s",
|
||||||
val subtitleProvider: @Composable (
|
val subtitleProvider: @Composable (value: Set<String>, entries: ImmutableMap<String, String>) -> String? =
|
||||||
value: Set<String>,
|
{ v, e ->
|
||||||
entries: ImmutableMap<String, String>,
|
val combined = remember(v, e) {
|
||||||
) -> String? = { v, e ->
|
v.mapNotNull { e[it] }
|
||||||
val combined = remember(v) {
|
.joinToString()
|
||||||
v.map { e[it] }
|
.takeUnless { it.isBlank() }
|
||||||
.takeIf { it.isNotEmpty() }
|
}
|
||||||
?.joinToString()
|
?: stringResource(MR.strings.none)
|
||||||
} ?: stringResource(MR.strings.none)
|
subtitle?.format(combined)
|
||||||
subtitle?.format(combined)
|
},
|
||||||
},
|
|
||||||
override val icon: ImageVector? = null,
|
override val icon: ImageVector? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: Set<String>) -> Boolean = { true },
|
override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true },
|
||||||
|
|
||||||
val entries: ImmutableMap<String, String>,
|
|
||||||
) : PreferenceItem<Set<String>>()
|
) : PreferenceItem<Set<String>>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] that shows a EditText in the dialog.
|
* A [PreferenceItem] that shows a EditText in the dialog.
|
||||||
*/
|
*/
|
||||||
data class EditTextPreference(
|
data class EditTextPreference(
|
||||||
val pref: PreferenceData<String>,
|
val preference: PreferenceData<String>,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: String? = "%s",
|
override val subtitle: String? = "%s",
|
||||||
override val icon: ImageVector? = null,
|
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
|
override val onValueChanged: suspend (value: String) -> Boolean = { true },
|
||||||
) : PreferenceItem<String>()
|
) : PreferenceItem<String>() {
|
||||||
|
override val icon: ImageVector? = null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] for individual tracker.
|
* A [PreferenceItem] for individual tracker.
|
||||||
*/
|
*/
|
||||||
data class TrackerPreference(
|
data class TrackerPreference(
|
||||||
val tracker: Tracker,
|
val tracker: Tracker,
|
||||||
override val title: String,
|
|
||||||
val login: () -> Unit,
|
val login: () -> Unit,
|
||||||
val logout: () -> Unit,
|
val logout: () -> Unit,
|
||||||
) : PreferenceItem<String>() {
|
) : PreferenceItem<String>() {
|
||||||
|
override val title: String = ""
|
||||||
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 (value: String) -> Boolean = { true }
|
||||||
}
|
}
|
||||||
|
|
||||||
data class InfoPreference(
|
data class InfoPreference(
|
||||||
@@ -160,17 +160,17 @@ sealed class Preference {
|
|||||||
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 (value: String) -> Boolean = { true }
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (value: Unit) -> Boolean = { true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import androidx.compose.animation.expandVertically
|
|||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -12,16 +14,20 @@ import androidx.compose.runtime.compositionLocalOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.structuralEqualityPolicy
|
import androidx.compose.runtime.structuralEqualityPolicy
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.MultiSelectListPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.MultiSelectListPreferenceWidget
|
||||||
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
||||||
|
import eu.kanade.presentation.more.settings.widget.PrefsVerticalPadding
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TitleFontSize
|
||||||
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.BaseSliderItem
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
|
||||||
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
|
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
|
||||||
@@ -60,7 +66,7 @@ internal fun PreferenceItem(
|
|||||||
) {
|
) {
|
||||||
when (item) {
|
when (item) {
|
||||||
is Preference.PreferenceItem.SwitchPreference -> {
|
is Preference.PreferenceItem.SwitchPreference -> {
|
||||||
val value by item.pref.collectAsState()
|
val value by item.preference.collectAsState()
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
title = item.title,
|
title = item.title,
|
||||||
subtitle = item.subtitle,
|
subtitle = item.subtitle,
|
||||||
@@ -69,29 +75,33 @@ internal fun PreferenceItem(
|
|||||||
onCheckedChanged = { newValue ->
|
onCheckedChanged = { newValue ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (item.onValueChanged(newValue)) {
|
if (item.onValueChanged(newValue)) {
|
||||||
item.pref.set(newValue)
|
item.preference.set(newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.SliderPreference -> {
|
is Preference.PreferenceItem.SliderPreference -> {
|
||||||
// TODO: use different composable?
|
BaseSliderItem(
|
||||||
SliderItem(
|
|
||||||
label = item.title,
|
label = item.title,
|
||||||
min = item.min,
|
|
||||||
max = item.max,
|
|
||||||
value = item.value,
|
value = item.value,
|
||||||
|
valueRange = item.valueRange,
|
||||||
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
||||||
|
steps = item.steps,
|
||||||
|
labelStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
|
||||||
onChange = {
|
onChange = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
item.onValueChanged(it)
|
item.onValueChanged(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = PrefsHorizontalPadding,
|
||||||
|
vertical = PrefsVerticalPadding,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.ListPreference<*> -> {
|
is Preference.PreferenceItem.ListPreference<*> -> {
|
||||||
val value by item.pref.collectAsState()
|
val value by item.preference.collectAsState()
|
||||||
ListPreferenceWidget(
|
ListPreferenceWidget(
|
||||||
value = value,
|
value = value,
|
||||||
title = item.title,
|
title = item.title,
|
||||||
@@ -118,14 +128,14 @@ internal fun PreferenceItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.MultiSelectListPreference -> {
|
is Preference.PreferenceItem.MultiSelectListPreference -> {
|
||||||
val values by item.pref.collectAsState()
|
val values by item.preference.collectAsState()
|
||||||
MultiSelectListPreferenceWidget(
|
MultiSelectListPreferenceWidget(
|
||||||
preference = item,
|
preference = item,
|
||||||
values = values,
|
values = values,
|
||||||
onValuesChange = { newValues ->
|
onValuesChange = { newValues ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (item.onValueChanged(newValues)) {
|
if (item.onValueChanged(newValues)) {
|
||||||
item.pref.set(newValues.toMutableSet())
|
item.preference.set(newValues.toMutableSet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -140,7 +150,7 @@ internal fun PreferenceItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.EditTextPreference -> {
|
is Preference.PreferenceItem.EditTextPreference -> {
|
||||||
val values by item.pref.collectAsState()
|
val values by item.preference.collectAsState()
|
||||||
EditTextPreferenceWidget(
|
EditTextPreferenceWidget(
|
||||||
title = item.title,
|
title = item.title,
|
||||||
subtitle = item.subtitle,
|
subtitle = item.subtitle,
|
||||||
@@ -148,7 +158,7 @@ internal fun PreferenceItem(
|
|||||||
value = values,
|
value = values,
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
val accepted = item.onValueChanged(it)
|
val accepted = item.onValueChanged(it)
|
||||||
if (accepted) item.pref.set(it)
|
if (accepted) item.preference.set(it)
|
||||||
accepted
|
accepted
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -167,7 +177,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-19
@@ -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
|
||||||
@@ -124,7 +126,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
/* SY --> Preference.PreferenceItem.SwitchPreference(
|
/* SY --> Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = networkPreferences.verboseLogging(),
|
preference = networkPreferences.verboseLogging(),
|
||||||
title = stringResource(MR.strings.pref_verbose_logging),
|
title = stringResource(MR.strings.pref_verbose_logging),
|
||||||
subtitle = stringResource(MR.strings.pref_verbose_logging_summary),
|
subtitle = stringResource(MR.strings.pref_verbose_logging_summary),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
@@ -270,8 +272,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = networkPreferences.dohProvider(),
|
preference = networkPreferences.dohProvider(),
|
||||||
title = stringResource(MR.strings.pref_dns_over_https),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
-1 to stringResource(MR.strings.disabled),
|
-1 to stringResource(MR.strings.disabled),
|
||||||
PREF_DOH_CLOUDFLARE to "Cloudflare",
|
PREF_DOH_CLOUDFLARE to "Cloudflare",
|
||||||
@@ -287,13 +288,14 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
PREF_DOH_NJALLA to "Njalla",
|
PREF_DOH_NJALLA to "Njalla",
|
||||||
PREF_DOH_SHECAN to "Shecan",
|
PREF_DOH_SHECAN to "Shecan",
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_dns_over_https),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
context.toast(MR.strings.requires_app_restart)
|
context.toast(MR.strings.requires_app_restart)
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
pref = userAgentPref,
|
preference = userAgentPref,
|
||||||
title = stringResource(MR.strings.pref_user_agent_string),
|
title = stringResource(MR.strings.pref_user_agent_string),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
try {
|
try {
|
||||||
@@ -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(
|
||||||
|
preference = basePreferences.hardwareBitmapThreshold(),
|
||||||
|
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(),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = basePreferences.alwaysDecodeLongStripWithSSIV(),
|
||||||
|
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_2),
|
||||||
|
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(),
|
||||||
@@ -417,8 +444,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.label_extensions),
|
title = stringResource(MR.strings.label_extensions),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = extensionInstallerPref,
|
preference = extensionInstallerPref,
|
||||||
title = stringResource(MR.strings.ext_installer_pref),
|
|
||||||
entries = extensionInstallerPref.entries
|
entries = extensionInstallerPref.entries
|
||||||
.filter {
|
.filter {
|
||||||
// TODO: allow private option in stable versions once URL handling is more fleshed out
|
// TODO: allow private option in stable versions once URL handling is more fleshed out
|
||||||
@@ -430,6 +456,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
.associateWith { stringResource(it.titleRes) }
|
.associateWith { stringResource(it.titleRes) }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.ext_installer_pref),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
if (it == BasePreferences.ExtensionInstaller.SHIZUKU &&
|
if (it == BasePreferences.ExtensionInstaller.SHIZUKU &&
|
||||||
!context.isShizukuInstalled
|
!context.isShizukuInstalled
|
||||||
@@ -591,7 +618,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
title = stringResource(SYMR.strings.data_saver),
|
title = stringResource(SYMR.strings.data_saver),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = sourcePreferences.dataSaver(),
|
preference = sourcePreferences.dataSaver(),
|
||||||
title = stringResource(SYMR.strings.data_saver),
|
title = stringResource(SYMR.strings.data_saver),
|
||||||
subtitle = stringResource(SYMR.strings.data_saver_summary),
|
subtitle = stringResource(SYMR.strings.data_saver_summary),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
@@ -601,28 +628,28 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
pref = sourcePreferences.dataSaverServer(),
|
preference = sourcePreferences.dataSaverServer(),
|
||||||
title = stringResource(SYMR.strings.bandwidth_data_saver_server),
|
title = stringResource(SYMR.strings.bandwidth_data_saver_server),
|
||||||
subtitle = stringResource(SYMR.strings.data_saver_server_summary),
|
subtitle = stringResource(SYMR.strings.data_saver_server_summary),
|
||||||
enabled = dataSaver == DataSaver.BANDWIDTH_HERO,
|
enabled = dataSaver == DataSaver.BANDWIDTH_HERO,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.dataSaverDownloader(),
|
preference = sourcePreferences.dataSaverDownloader(),
|
||||||
title = stringResource(SYMR.strings.data_saver_downloader),
|
title = stringResource(SYMR.strings.data_saver_downloader),
|
||||||
enabled = dataSaver != DataSaver.NONE,
|
enabled = dataSaver != DataSaver.NONE,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.dataSaverIgnoreJpeg(),
|
preference = sourcePreferences.dataSaverIgnoreJpeg(),
|
||||||
title = stringResource(SYMR.strings.data_saver_ignore_jpeg),
|
title = stringResource(SYMR.strings.data_saver_ignore_jpeg),
|
||||||
enabled = dataSaver != DataSaver.NONE,
|
enabled = dataSaver != DataSaver.NONE,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.dataSaverIgnoreGif(),
|
preference = sourcePreferences.dataSaverIgnoreGif(),
|
||||||
title = stringResource(SYMR.strings.data_saver_ignore_gif),
|
title = stringResource(SYMR.strings.data_saver_ignore_gif),
|
||||||
enabled = dataSaver != DataSaver.NONE,
|
enabled = dataSaver != DataSaver.NONE,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = sourcePreferences.dataSaverImageQuality(),
|
preference = sourcePreferences.dataSaverImageQuality(),
|
||||||
title = stringResource(SYMR.strings.data_saver_image_quality),
|
title = stringResource(SYMR.strings.data_saver_image_quality),
|
||||||
subtitle = stringResource(SYMR.strings.data_saver_image_quality_summary),
|
subtitle = stringResource(SYMR.strings.data_saver_image_quality_summary),
|
||||||
entries = listOf(
|
entries = listOf(
|
||||||
@@ -641,7 +668,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg()
|
val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg()
|
||||||
.collectAsState()
|
.collectAsState()
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.dataSaverImageFormatJpeg(),
|
preference = sourcePreferences.dataSaverImageFormatJpeg(),
|
||||||
title = stringResource(SYMR.strings.data_saver_image_format),
|
title = stringResource(SYMR.strings.data_saver_image_format),
|
||||||
subtitle = if (dataSaverImageFormatJpeg) {
|
subtitle = if (dataSaverImageFormatJpeg) {
|
||||||
stringResource(SYMR.strings.data_saver_image_format_summary_on)
|
stringResource(SYMR.strings.data_saver_image_format_summary_on)
|
||||||
@@ -652,7 +679,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.dataSaverColorBW(),
|
preference = sourcePreferences.dataSaverColorBW(),
|
||||||
title = stringResource(SYMR.strings.data_saver_color_bw),
|
title = stringResource(SYMR.strings.data_saver_color_bw),
|
||||||
enabled = dataSaver == DataSaver.BANDWIDTH_HERO,
|
enabled = dataSaver == DataSaver.BANDWIDTH_HERO,
|
||||||
),
|
),
|
||||||
@@ -672,7 +699,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
title = stringResource(SYMR.strings.developer_tools),
|
title = stringResource(SYMR.strings.developer_tools),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.isHentaiEnabled(),
|
preference = unsortedPreferences.isHentaiEnabled(),
|
||||||
title = stringResource(SYMR.strings.toggle_hentai_features),
|
title = stringResource(SYMR.strings.toggle_hentai_features),
|
||||||
subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary),
|
subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
@@ -687,7 +714,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = delegateSourcePreferences.delegateSources(),
|
preference = delegateSourcePreferences.delegateSources(),
|
||||||
title = stringResource(SYMR.strings.toggle_delegated_sources),
|
title = stringResource(SYMR.strings.toggle_delegated_sources),
|
||||||
subtitle = stringResource(
|
subtitle = stringResource(
|
||||||
SYMR.strings.toggle_delegated_sources_summary,
|
SYMR.strings.toggle_delegated_sources_summary,
|
||||||
@@ -697,7 +724,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = unsortedPreferences.logLevel(),
|
preference = unsortedPreferences.logLevel(),
|
||||||
title = stringResource(SYMR.strings.log_level),
|
title = stringResource(SYMR.strings.log_level),
|
||||||
subtitle = stringResource(SYMR.strings.log_level_summary),
|
subtitle = stringResource(SYMR.strings.log_level_summary),
|
||||||
entries = EHLogLevel.entries.mapIndexed { index, ehLogLevel ->
|
entries = EHLogLevel.entries.mapIndexed { index, ehLogLevel ->
|
||||||
@@ -707,7 +734,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
}.toMap().toImmutableMap(),
|
}.toMap().toImmutableMap(),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.enableSourceBlacklist(),
|
preference = sourcePreferences.enableSourceBlacklist(),
|
||||||
title = stringResource(SYMR.strings.enable_source_blacklist),
|
title = stringResource(SYMR.strings.enable_source_blacklist),
|
||||||
subtitle = stringResource(
|
subtitle = stringResource(
|
||||||
SYMR.strings.enable_source_blacklist_summary,
|
SYMR.strings.enable_source_blacklist_summary,
|
||||||
@@ -751,7 +778,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
title = stringResource(SYMR.strings.encrypt_database),
|
title = stringResource(SYMR.strings.encrypt_database),
|
||||||
pref = securityPreferences.encryptDatabase(),
|
preference = securityPreferences.encryptDatabase(),
|
||||||
subtitle = stringResource(SYMR.strings.encrypt_database_subtitle),
|
subtitle = stringResource(SYMR.strings.encrypt_database_subtitle),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
if (it) {
|
if (it) {
|
||||||
|
|||||||
+13
-14
@@ -88,7 +88,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = amoledPref,
|
preference = amoledPref,
|
||||||
title = stringResource(MR.strings.pref_dark_theme_pure_black),
|
title = stringResource(MR.strings.pref_dark_theme_pure_black),
|
||||||
enabled = themeMode != ThemeMode.LIGHT,
|
enabled = themeMode != ThemeMode.LIGHT,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
@@ -122,28 +122,28 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
onClick = { navigator.push(AppLanguageScreen()) },
|
onClick = { navigator.push(AppLanguageScreen()) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = uiPreferences.tabletUiMode(),
|
preference = uiPreferences.tabletUiMode(),
|
||||||
title = stringResource(MR.strings.pref_tablet_ui_mode),
|
|
||||||
entries = TabletUiMode.entries
|
entries = TabletUiMode.entries
|
||||||
.associateWith { stringResource(it.titleRes) }
|
.associateWith { stringResource(it.titleRes) }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_tablet_ui_mode),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
context.toast(MR.strings.requires_app_restart)
|
context.toast(MR.strings.requires_app_restart)
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = uiPreferences.dateFormat(),
|
preference = uiPreferences.dateFormat(),
|
||||||
title = stringResource(MR.strings.pref_date_format),
|
|
||||||
entries = DateFormats
|
entries = DateFormats
|
||||||
.associateWith {
|
.associateWith {
|
||||||
val formattedDate = UiPreferences.dateFormat(it).format(now)
|
val formattedDate = UiPreferences.dateFormat(it).format(now)
|
||||||
"${it.ifEmpty { stringResource(MR.strings.label_default) }} ($formattedDate)"
|
"${it.ifEmpty { stringResource(MR.strings.label_default) }} ($formattedDate)"
|
||||||
}
|
}
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_date_format),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.relativeTime(),
|
preference = uiPreferences.relativeTime(),
|
||||||
title = stringResource(MR.strings.pref_relative_format),
|
title = stringResource(MR.strings.pref_relative_format),
|
||||||
subtitle = stringResource(
|
subtitle = stringResource(
|
||||||
MR.strings.pref_relative_format_summary,
|
MR.strings.pref_relative_format_summary,
|
||||||
@@ -164,16 +164,16 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
stringResource(SYMR.strings.pref_category_fork),
|
stringResource(SYMR.strings.pref_category_fork),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.expandFilters(),
|
preference = uiPreferences.expandFilters(),
|
||||||
title = stringResource(SYMR.strings.toggle_expand_search_filters),
|
title = stringResource(SYMR.strings.toggle_expand_search_filters),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.recommendsInOverflow(),
|
preference = uiPreferences.recommendsInOverflow(),
|
||||||
title = stringResource(SYMR.strings.put_recommends_in_overflow),
|
title = stringResource(SYMR.strings.put_recommends_in_overflow),
|
||||||
subtitle = stringResource(SYMR.strings.put_recommends_in_overflow_summary),
|
subtitle = stringResource(SYMR.strings.put_recommends_in_overflow_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.mergeInOverflow(),
|
preference = uiPreferences.mergeInOverflow(),
|
||||||
title = stringResource(SYMR.strings.put_merge_in_overflow),
|
title = stringResource(SYMR.strings.put_merge_in_overflow),
|
||||||
subtitle = stringResource(SYMR.strings.put_merge_in_overflow_summary),
|
subtitle = stringResource(SYMR.strings.put_merge_in_overflow_summary),
|
||||||
),
|
),
|
||||||
@@ -189,8 +189,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
} else {
|
} else {
|
||||||
stringResource(MR.strings.disabled)
|
stringResource(MR.strings.disabled)
|
||||||
},
|
},
|
||||||
min = 0,
|
valueRange = 0..10,
|
||||||
max = 10,
|
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
uiPreferences.previewsRowCount().set(it)
|
uiPreferences.previewsRowCount().set(it)
|
||||||
true
|
true
|
||||||
@@ -206,15 +205,15 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
stringResource(SYMR.strings.pref_category_navbar),
|
stringResource(SYMR.strings.pref_category_navbar),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.showNavUpdates(),
|
preference = uiPreferences.showNavUpdates(),
|
||||||
title = stringResource(SYMR.strings.pref_hide_updates_button),
|
title = stringResource(SYMR.strings.pref_hide_updates_button),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.showNavHistory(),
|
preference = uiPreferences.showNavHistory(),
|
||||||
title = stringResource(SYMR.strings.pref_hide_history_button),
|
title = stringResource(SYMR.strings.pref_hide_history_button),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.bottomBarLabels(),
|
preference = uiPreferences.bottomBarLabels(),
|
||||||
title = stringResource(SYMR.strings.pref_show_bottom_bar_labels),
|
title = stringResource(SYMR.strings.pref_show_bottom_bar_labels),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+7
-7
@@ -67,17 +67,17 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.sourcesTabCategoriesFilter(),
|
preference = sourcePreferences.sourcesTabCategoriesFilter(),
|
||||||
title = stringResource(SYMR.strings.pref_source_source_filtering),
|
title = stringResource(SYMR.strings.pref_source_source_filtering),
|
||||||
subtitle = stringResource(SYMR.strings.pref_source_source_filtering_summery),
|
subtitle = stringResource(SYMR.strings.pref_source_source_filtering_summery),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.useNewSourceNavigation(),
|
preference = uiPreferences.useNewSourceNavigation(),
|
||||||
title = stringResource(SYMR.strings.pref_source_navigation),
|
title = stringResource(SYMR.strings.pref_source_navigation),
|
||||||
subtitle = stringResource(SYMR.strings.pref_source_navigation_summery),
|
subtitle = stringResource(SYMR.strings.pref_source_navigation_summery),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.allowLocalSourceHiddenFolders(),
|
preference = unsortedPreferences.allowLocalSourceHiddenFolders(),
|
||||||
title = stringResource(SYMR.strings.pref_local_source_hidden_folders),
|
title = stringResource(SYMR.strings.pref_local_source_hidden_folders),
|
||||||
subtitle = stringResource(SYMR.strings.pref_local_source_hidden_folders_summery),
|
subtitle = stringResource(SYMR.strings.pref_local_source_hidden_folders_summery),
|
||||||
),
|
),
|
||||||
@@ -87,11 +87,11 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
title = stringResource(SYMR.strings.feed),
|
title = stringResource(SYMR.strings.feed),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.hideFeedTab(),
|
preference = uiPreferences.hideFeedTab(),
|
||||||
title = stringResource(SYMR.strings.pref_hide_feed),
|
title = stringResource(SYMR.strings.pref_hide_feed),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = uiPreferences.feedTabInFront(),
|
preference = uiPreferences.feedTabInFront(),
|
||||||
title = stringResource(SYMR.strings.pref_feed_position),
|
title = stringResource(SYMR.strings.pref_feed_position),
|
||||||
subtitle = stringResource(SYMR.strings.pref_feed_position_summery),
|
subtitle = stringResource(SYMR.strings.pref_feed_position_summery),
|
||||||
enabled = hideFeedTab.not(),
|
enabled = hideFeedTab.not(),
|
||||||
@@ -103,7 +103,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.label_sources),
|
title = stringResource(MR.strings.label_sources),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.hideInLibraryItems(),
|
preference = sourcePreferences.hideInLibraryItems(),
|
||||||
title = stringResource(MR.strings.pref_hide_in_library_items),
|
title = stringResource(MR.strings.pref_hide_in_library_items),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
@@ -119,7 +119,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_category_nsfw_content),
|
title = stringResource(MR.strings.pref_category_nsfw_content),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.showNsfwSource(),
|
preference = sourcePreferences.showNsfwSource(),
|
||||||
title = stringResource(MR.strings.pref_show_nsfw_source),
|
title = stringResource(MR.strings.pref_show_nsfw_source),
|
||||||
subtitle = stringResource(MR.strings.requires_app_restart),
|
subtitle = stringResource(MR.strings.requires_app_restart),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
|
|||||||
+204
-11
@@ -7,7 +7,9 @@ import android.net.Uri
|
|||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -15,7 +17,9 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
|
import androidx.compose.material.icons.filled.QrCodeScanner
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
||||||
@@ -24,6 +28,7 @@ import androidx.compose.material3.SegmentedButtonDefaults
|
|||||||
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.LaunchedEffect
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
@@ -31,13 +36,17 @@ 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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import com.google.zxing.client.android.Intents
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import eu.kanade.domain.sync.SyncPreferences
|
import eu.kanade.domain.sync.SyncPreferences
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
||||||
@@ -46,12 +55,16 @@ import eu.kanade.presentation.more.settings.screen.data.StorageInfo
|
|||||||
import eu.kanade.presentation.more.settings.screen.data.SyncSettingsSelector
|
import eu.kanade.presentation.more.settings.screen.data.SyncSettingsSelector
|
||||||
import eu.kanade.presentation.more.settings.screen.data.SyncTriggerOptionsScreen
|
import eu.kanade.presentation.more.settings.screen.data.SyncTriggerOptionsScreen
|
||||||
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
||||||
|
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
import eu.kanade.presentation.util.relativeTimeSpanString
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.cache.PagePreviewCache
|
import eu.kanade.tachiyomi.data.cache.PagePreviewCache
|
||||||
|
import eu.kanade.tachiyomi.data.export.LibraryExporter
|
||||||
|
import eu.kanade.tachiyomi.data.export.LibraryExporter.ExportOptions
|
||||||
import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
||||||
import eu.kanade.tachiyomi.data.sync.SyncManager
|
import eu.kanade.tachiyomi.data.sync.SyncManager
|
||||||
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService
|
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService
|
||||||
@@ -60,6 +73,7 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|||||||
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.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
@@ -69,6 +83,8 @@ import tachiyomi.core.common.util.lang.withUIContext
|
|||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.storage.service.StoragePreferences
|
import tachiyomi.domain.storage.service.StoragePreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
@@ -111,6 +127,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
|
|
||||||
getBackupAndRestoreGroup(backupPreferences = backupPreferences),
|
getBackupAndRestoreGroup(backupPreferences = backupPreferences),
|
||||||
getDataGroup(),
|
getDataGroup(),
|
||||||
|
getExportGroup(),
|
||||||
) + getSyncPreferences(syncPreferences = syncPreferences, syncService = syncService)
|
) + getSyncPreferences(syncPreferences = syncPreferences, syncService = syncService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,8 +272,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
|
|
||||||
// Automatic backups
|
// Automatic backups
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = backupPreferences.backupInterval(),
|
preference = backupPreferences.backupInterval(),
|
||||||
title = stringResource(MR.strings.pref_backup_interval),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
0 to stringResource(MR.strings.off),
|
0 to stringResource(MR.strings.off),
|
||||||
6 to stringResource(MR.strings.update_6hour),
|
6 to stringResource(MR.strings.update_6hour),
|
||||||
@@ -265,6 +281,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
48 to stringResource(MR.strings.update_48hour),
|
48 to stringResource(MR.strings.update_48hour),
|
||||||
168 to stringResource(MR.strings.update_weekly),
|
168 to stringResource(MR.strings.update_weekly),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_backup_interval),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
BackupCreateJob.setupTask(context, it)
|
BackupCreateJob.setupTask(context, it)
|
||||||
true
|
true
|
||||||
@@ -348,13 +365,151 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
// SY <--
|
// SY <--
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = libraryPreferences.autoClearChapterCache(),
|
preference = libraryPreferences.autoClearChapterCache(),
|
||||||
title = stringResource(MR.strings.pref_auto_clear_chapter_cache),
|
title = stringResource(MR.strings.pref_auto_clear_chapter_cache),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getExportGroup(): Preference.PreferenceGroup {
|
||||||
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
var exportOptions by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
ExportOptions(
|
||||||
|
includeTitle = true,
|
||||||
|
includeAuthor = true,
|
||||||
|
includeArtist = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val getFavorites = remember { Injekt.get<GetFavorites>() }
|
||||||
|
var favorites by remember { mutableStateOf<List<Manga>>(emptyList()) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
favorites = getFavorites.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
val saveFileLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.CreateDocument("text/csv"),
|
||||||
|
) { uri ->
|
||||||
|
uri?.let {
|
||||||
|
scope.launch {
|
||||||
|
LibraryExporter.exportToCsv(
|
||||||
|
context = context,
|
||||||
|
uri = it,
|
||||||
|
favorites = favorites,
|
||||||
|
options = exportOptions,
|
||||||
|
onExportComplete = {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
context.toast(MR.strings.library_exported)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDialog) {
|
||||||
|
ColumnSelectionDialog(
|
||||||
|
options = exportOptions,
|
||||||
|
onConfirm = { options ->
|
||||||
|
exportOptions = options
|
||||||
|
saveFileLauncher.launch("mihon_library.csv")
|
||||||
|
},
|
||||||
|
onDismissRequest = { showDialog = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(MR.strings.export),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(MR.strings.library_list),
|
||||||
|
onClick = { showDialog = true },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnSelectionDialog(
|
||||||
|
options: ExportOptions,
|
||||||
|
onConfirm: (ExportOptions) -> Unit,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
) {
|
||||||
|
var titleSelected by remember { mutableStateOf(options.includeTitle) }
|
||||||
|
var authorSelected by remember { mutableStateOf(options.includeAuthor) }
|
||||||
|
var artistSelected by remember { mutableStateOf(options.includeArtist) }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(MR.strings.migration_dialog_what_to_include))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Checkbox(
|
||||||
|
checked = titleSelected,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
titleSelected = checked
|
||||||
|
if (!checked) {
|
||||||
|
authorSelected = false
|
||||||
|
artistSelected = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(text = stringResource(MR.strings.title))
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Checkbox(
|
||||||
|
checked = authorSelected,
|
||||||
|
onCheckedChange = { authorSelected = it },
|
||||||
|
enabled = titleSelected,
|
||||||
|
)
|
||||||
|
Text(text = stringResource(MR.strings.author))
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Checkbox(
|
||||||
|
checked = artistSelected,
|
||||||
|
onCheckedChange = { artistSelected = it },
|
||||||
|
enabled = titleSelected,
|
||||||
|
)
|
||||||
|
Text(text = stringResource(MR.strings.artist))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onConfirm(
|
||||||
|
ExportOptions(
|
||||||
|
includeTitle = titleSelected,
|
||||||
|
includeAuthor = authorSelected,
|
||||||
|
includeArtist = artistSelected,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.action_save))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
@Composable
|
@Composable
|
||||||
private fun getSyncPreferences(syncPreferences: SyncPreferences, syncService: Int): List<Preference> {
|
private fun getSyncPreferences(syncPreferences: SyncPreferences, syncService: Int): List<Preference> {
|
||||||
return listOf(
|
return listOf(
|
||||||
@@ -362,7 +517,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
title = stringResource(SYMR.strings.pref_sync_service_category),
|
title = stringResource(SYMR.strings.pref_sync_service_category),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = syncPreferences.syncService(),
|
preference = syncPreferences.syncService(),
|
||||||
title = stringResource(SYMR.strings.pref_sync_service),
|
title = stringResource(SYMR.strings.pref_sync_service),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
SyncManager.SyncService.NONE.value to stringResource(MR.strings.off),
|
SyncManager.SyncService.NONE.value to stringResource(MR.strings.off),
|
||||||
@@ -502,11 +657,27 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun getSelfHostPreferences(syncPreferences: SyncPreferences): List<Preference> {
|
private fun getSelfHostPreferences(syncPreferences: SyncPreferences): List<Preference> {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val qrScanLauncher = rememberLauncherForActivityResult(ScanContract()) {
|
||||||
|
if (it.contents != null && it.contents.isNotEmpty()) {
|
||||||
|
syncPreferences.clientAPIKey().set(it.contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scanOptions = remember {
|
||||||
|
ScanOptions().apply {
|
||||||
|
setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||||
|
setOrientationLocked(false)
|
||||||
|
setPrompt(SYMR.strings.scan_qr_code.getString(context))
|
||||||
|
addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
title = stringResource(SYMR.strings.pref_sync_host),
|
title = stringResource(SYMR.strings.pref_sync_host),
|
||||||
subtitle = stringResource(SYMR.strings.pref_sync_host_summ),
|
subtitle = stringResource(SYMR.strings.pref_sync_host_summ),
|
||||||
pref = syncPreferences.clientHost(),
|
preference = syncPreferences.clientHost(),
|
||||||
onValueChanged = { newValue ->
|
onValueChanged = { newValue ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
// Trim spaces at the beginning and end, then remove trailing slash if present
|
// Trim spaces at the beginning and end, then remove trailing slash if present
|
||||||
@@ -517,11 +688,32 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
Preference.PreferenceItem.CustomPreference(
|
||||||
title = stringResource(SYMR.strings.pref_sync_api_key),
|
title = stringResource(SYMR.strings.pref_sync_api_key),
|
||||||
subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ),
|
) {
|
||||||
pref = syncPreferences.clientAPIKey(),
|
val values by syncPreferences.clientAPIKey().collectAsState()
|
||||||
),
|
EditTextPreferenceWidget(
|
||||||
|
title = stringResource(SYMR.strings.pref_sync_api_key),
|
||||||
|
subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ),
|
||||||
|
onConfirm = {
|
||||||
|
syncPreferences.clientAPIKey().set(it)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
icon = null,
|
||||||
|
value = values,
|
||||||
|
widget = {
|
||||||
|
IconButton(
|
||||||
|
onClick = { qrScanLauncher.launch(scanOptions) },
|
||||||
|
modifier = Modifier.padding(start = TrailingWidgetBuffer),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.QrCodeScanner,
|
||||||
|
contentDescription = stringResource(SYMR.strings.scan_qr_code),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +729,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)
|
||||||
}
|
}
|
||||||
@@ -567,7 +759,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
title = stringResource(SYMR.strings.pref_sync_automatic_category),
|
title = stringResource(SYMR.strings.pref_sync_automatic_category),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = syncIntervalPref,
|
preference = syncIntervalPref,
|
||||||
title = stringResource(SYMR.strings.pref_sync_interval),
|
title = stringResource(SYMR.strings.pref_sync_interval),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
0 to stringResource(MR.strings.off),
|
0 to stringResource(MR.strings.off),
|
||||||
@@ -591,4 +783,5 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-16
@@ -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,20 +34,20 @@ 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(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = downloadPreferences.downloadOnlyOverWifi(),
|
preference = downloadPreferences.downloadOnlyOverWifi(),
|
||||||
title = stringResource(MR.strings.connected_to_wifi),
|
title = stringResource(MR.strings.connected_to_wifi),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = downloadPreferences.saveChaptersAsCBZ(),
|
preference = downloadPreferences.saveChaptersAsCBZ(),
|
||||||
title = stringResource(MR.strings.save_chapter_as_cbz),
|
title = stringResource(MR.strings.save_chapter_as_cbz),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = downloadPreferences.splitTallImages(),
|
preference = downloadPreferences.splitTallImages(),
|
||||||
title = stringResource(MR.strings.split_tall_images),
|
title = stringResource(MR.strings.split_tall_images),
|
||||||
subtitle = stringResource(MR.strings.split_tall_images_summary),
|
subtitle = stringResource(MR.strings.split_tall_images_summary),
|
||||||
),
|
),
|
||||||
@@ -73,12 +72,11 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_category_delete_chapters),
|
title = stringResource(MR.strings.pref_category_delete_chapters),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = downloadPreferences.removeAfterMarkedAsRead(),
|
preference = downloadPreferences.removeAfterMarkedAsRead(),
|
||||||
title = stringResource(MR.strings.pref_remove_after_marked_as_read),
|
title = stringResource(MR.strings.pref_remove_after_marked_as_read),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = downloadPreferences.removeAfterReadSlots(),
|
preference = downloadPreferences.removeAfterReadSlots(),
|
||||||
title = stringResource(MR.strings.pref_remove_after_read),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
-1 to stringResource(MR.strings.disabled),
|
-1 to stringResource(MR.strings.disabled),
|
||||||
0 to stringResource(MR.strings.last_read_chapter),
|
0 to stringResource(MR.strings.last_read_chapter),
|
||||||
@@ -87,9 +85,10 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
3 to stringResource(MR.strings.fourth_to_last),
|
3 to stringResource(MR.strings.fourth_to_last),
|
||||||
4 to stringResource(MR.strings.fifth_to_last),
|
4 to stringResource(MR.strings.fifth_to_last),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_remove_after_read),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = downloadPreferences.removeBookmarkedChapters(),
|
preference = downloadPreferences.removeBookmarkedChapters(),
|
||||||
title = stringResource(MR.strings.pref_remove_bookmarked_chapters),
|
title = stringResource(MR.strings.pref_remove_bookmarked_chapters),
|
||||||
),
|
),
|
||||||
getExcludedCategoriesPreference(
|
getExcludedCategoriesPreference(
|
||||||
@@ -106,11 +105,11 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
categories: () -> List<Category>,
|
categories: () -> List<Category>,
|
||||||
): Preference.PreferenceItem.MultiSelectListPreference {
|
): Preference.PreferenceItem.MultiSelectListPreference {
|
||||||
return Preference.PreferenceItem.MultiSelectListPreference(
|
return Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = downloadPreferences.removeExcludeCategories(),
|
preference = downloadPreferences.removeExcludeCategories(),
|
||||||
title = stringResource(MR.strings.pref_remove_exclude_categories),
|
|
||||||
entries = categories()
|
entries = categories()
|
||||||
.associate { it.id.toString() to it.visualName }
|
.associate { it.id.toString() to it.visualName }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_remove_exclude_categories),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,11 +149,11 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_category_auto_download),
|
title = stringResource(MR.strings.pref_category_auto_download),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = downloadNewChaptersPref,
|
preference = downloadNewChaptersPref,
|
||||||
title = stringResource(MR.strings.pref_download_new),
|
title = stringResource(MR.strings.pref_download_new),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = downloadNewUnreadChaptersOnlyPref,
|
preference = downloadNewUnreadChaptersOnlyPref,
|
||||||
title = stringResource(MR.strings.pref_download_new_unread_chapters_only),
|
title = stringResource(MR.strings.pref_download_new_unread_chapters_only),
|
||||||
enabled = downloadNewChapters,
|
enabled = downloadNewChapters,
|
||||||
),
|
),
|
||||||
@@ -165,8 +164,8 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
included = included,
|
included = included,
|
||||||
excluded = excluded,
|
excluded = excluded,
|
||||||
),
|
),
|
||||||
onClick = { showDialog = true },
|
|
||||||
enabled = downloadNewChapters,
|
enabled = downloadNewChapters,
|
||||||
|
onClick = { showDialog = true },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -180,8 +179,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.download_ahead),
|
title = stringResource(MR.strings.download_ahead),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = downloadPreferences.autoDownloadWhileReading(),
|
preference = downloadPreferences.autoDownloadWhileReading(),
|
||||||
title = stringResource(MR.strings.auto_download_while_reading),
|
|
||||||
entries = listOf(0, 2, 3, 5, 10)
|
entries = listOf(0, 2, 3, 5, 10)
|
||||||
.associateWith {
|
.associateWith {
|
||||||
if (it == 0) {
|
if (it == 0) {
|
||||||
@@ -191,6 +189,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.auto_download_while_reading),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.download_ahead_info)),
|
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.download_ahead_info)),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
|
||||||
import eu.kanade.presentation.library.components.SyncFavoritesWarningDialog
|
import eu.kanade.presentation.library.components.SyncFavoritesWarningDialog
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
@@ -194,7 +193,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val value by unsortedPreferences.enableExhentai().collectAsState()
|
val value by unsortedPreferences.enableExhentai().collectAsState()
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.enableExhentai(),
|
preference = unsortedPreferences.enableExhentai(),
|
||||||
title = stringResource(SYMR.strings.enable_exhentai),
|
title = stringResource(SYMR.strings.enable_exhentai),
|
||||||
subtitle = if (!value) {
|
subtitle = if (!value) {
|
||||||
stringResource(SYMR.strings.requires_login)
|
stringResource(SYMR.strings.requires_login)
|
||||||
@@ -219,7 +218,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
unsortedPreferences: UnsortedPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.ListPreference<Int> {
|
): Preference.PreferenceItem.ListPreference<Int> {
|
||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
pref = unsortedPreferences.useHentaiAtHome(),
|
preference = unsortedPreferences.useHentaiAtHome(),
|
||||||
title = stringResource(SYMR.strings.use_hentai_at_home),
|
title = stringResource(SYMR.strings.use_hentai_at_home),
|
||||||
subtitle = stringResource(SYMR.strings.use_hentai_at_home_summary),
|
subtitle = stringResource(SYMR.strings.use_hentai_at_home_summary),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
@@ -237,7 +236,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
): Preference.PreferenceItem.SwitchPreference {
|
): Preference.PreferenceItem.SwitchPreference {
|
||||||
val value by unsortedPreferences.useJapaneseTitle().collectAsState()
|
val value by unsortedPreferences.useJapaneseTitle().collectAsState()
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.useJapaneseTitle(),
|
preference = unsortedPreferences.useJapaneseTitle(),
|
||||||
title = stringResource(SYMR.strings.show_japanese_titles),
|
title = stringResource(SYMR.strings.show_japanese_titles),
|
||||||
subtitle = if (value) {
|
subtitle = if (value) {
|
||||||
stringResource(SYMR.strings.show_japanese_titles_option_1)
|
stringResource(SYMR.strings.show_japanese_titles_option_1)
|
||||||
@@ -255,7 +254,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
): Preference.PreferenceItem.SwitchPreference {
|
): Preference.PreferenceItem.SwitchPreference {
|
||||||
val value by unsortedPreferences.exhUseOriginalImages().collectAsState()
|
val value by unsortedPreferences.exhUseOriginalImages().collectAsState()
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.exhUseOriginalImages(),
|
preference = unsortedPreferences.exhUseOriginalImages(),
|
||||||
title = stringResource(SYMR.strings.use_original_images),
|
title = stringResource(SYMR.strings.use_original_images),
|
||||||
subtitle = if (value) {
|
subtitle = if (value) {
|
||||||
stringResource(SYMR.strings.use_original_images_on)
|
stringResource(SYMR.strings.use_original_images_on)
|
||||||
@@ -273,8 +272,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
title = stringResource(SYMR.strings.watched_tags),
|
title = stringResource(SYMR.strings.watched_tags),
|
||||||
subtitle = stringResource(SYMR.strings.watched_tags_summary),
|
subtitle = stringResource(SYMR.strings.watched_tags_summary),
|
||||||
onClick = {
|
onClick = {
|
||||||
startActivity(
|
context.startActivity(
|
||||||
context,
|
|
||||||
WebViewActivity.newIntent(
|
WebViewActivity.newIntent(
|
||||||
context,
|
context,
|
||||||
url = "https://exhentai.org/mytags",
|
url = "https://exhentai.org/mytags",
|
||||||
@@ -802,7 +800,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
unsortedPreferences: UnsortedPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.SwitchPreference {
|
): Preference.PreferenceItem.SwitchPreference {
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.exhWatchedListDefaultState(),
|
preference = unsortedPreferences.exhWatchedListDefaultState(),
|
||||||
title = stringResource(SYMR.strings.watched_list_default),
|
title = stringResource(SYMR.strings.watched_list_default),
|
||||||
subtitle = stringResource(SYMR.strings.watched_list_state_summary),
|
subtitle = stringResource(SYMR.strings.watched_list_state_summary),
|
||||||
enabled = exhentaiEnabled,
|
enabled = exhentaiEnabled,
|
||||||
@@ -815,7 +813,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
unsortedPreferences: UnsortedPreferences,
|
unsortedPreferences: UnsortedPreferences,
|
||||||
): Preference.PreferenceItem.ListPreference<String> {
|
): Preference.PreferenceItem.ListPreference<String> {
|
||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
pref = unsortedPreferences.imageQuality(),
|
preference = unsortedPreferences.imageQuality(),
|
||||||
title = stringResource(SYMR.strings.eh_image_quality_summary),
|
title = stringResource(SYMR.strings.eh_image_quality_summary),
|
||||||
subtitle = stringResource(SYMR.strings.eh_image_quality),
|
subtitle = stringResource(SYMR.strings.eh_image_quality),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
@@ -833,7 +831,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun enhancedEhentaiView(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
fun enhancedEhentaiView(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.enhancedEHentaiView(),
|
preference = unsortedPreferences.enhancedEHentaiView(),
|
||||||
title = stringResource(SYMR.strings.pref_enhanced_e_hentai_view),
|
title = stringResource(SYMR.strings.pref_enhanced_e_hentai_view),
|
||||||
subtitle = stringResource(SYMR.strings.pref_enhanced_e_hentai_view_summary),
|
subtitle = stringResource(SYMR.strings.pref_enhanced_e_hentai_view_summary),
|
||||||
)
|
)
|
||||||
@@ -842,7 +840,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun readOnlySync(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
fun readOnlySync(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.exhReadOnlySync(),
|
preference = unsortedPreferences.exhReadOnlySync(),
|
||||||
title = stringResource(SYMR.strings.disable_favorites_uploading),
|
title = stringResource(SYMR.strings.disable_favorites_uploading),
|
||||||
subtitle = stringResource(SYMR.strings.disable_favorites_uploading_summary),
|
subtitle = stringResource(SYMR.strings.disable_favorites_uploading_summary),
|
||||||
)
|
)
|
||||||
@@ -867,7 +865,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
fun lenientSync(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
fun lenientSync(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference {
|
||||||
return Preference.PreferenceItem.SwitchPreference(
|
return Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.exhLenientSync(),
|
preference = unsortedPreferences.exhLenientSync(),
|
||||||
title = stringResource(SYMR.strings.ignore_sync_errors),
|
title = stringResource(SYMR.strings.ignore_sync_errors),
|
||||||
subtitle = stringResource(SYMR.strings.ignore_sync_errors_summary),
|
subtitle = stringResource(SYMR.strings.ignore_sync_errors_summary),
|
||||||
)
|
)
|
||||||
@@ -942,7 +940,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
val value by unsortedPreferences.exhAutoUpdateFrequency().collectAsState()
|
val value by unsortedPreferences.exhAutoUpdateFrequency().collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
pref = unsortedPreferences.exhAutoUpdateFrequency(),
|
preference = unsortedPreferences.exhAutoUpdateFrequency(),
|
||||||
title = stringResource(SYMR.strings.time_between_batches),
|
title = stringResource(SYMR.strings.time_between_batches),
|
||||||
subtitle = if (value == 0) {
|
subtitle = if (value == 0) {
|
||||||
stringResource(SYMR.strings.time_between_batches_summary_1, stringResource(MR.strings.app_name))
|
stringResource(SYMR.strings.time_between_batches_summary_1, stringResource(MR.strings.app_name))
|
||||||
@@ -978,7 +976,7 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
val value by unsortedPreferences.exhAutoUpdateRequirements().collectAsState()
|
val value by unsortedPreferences.exhAutoUpdateRequirements().collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
return Preference.PreferenceItem.MultiSelectListPreference(
|
return Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = unsortedPreferences.exhAutoUpdateRequirements(),
|
preference = unsortedPreferences.exhAutoUpdateRequirements(),
|
||||||
title = stringResource(SYMR.strings.auto_update_restrictions),
|
title = stringResource(SYMR.strings.auto_update_restrictions),
|
||||||
subtitle = remember(value) {
|
subtitle = remember(value) {
|
||||||
context.stringResource(
|
context.stringResource(
|
||||||
|
|||||||
+35
-33
@@ -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
|
||||||
@@ -39,6 +38,8 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U
|
|||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MARK_DUPLICATE_CHAPTER_READ_EXISTING
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MARK_DUPLICATE_CHAPTER_READ_NEW
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
@@ -57,9 +58,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 <--
|
||||||
@@ -67,7 +66,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
return listOf(
|
return listOf(
|
||||||
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
|
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
|
||||||
getGlobalUpdateGroup(allCategories, libraryPreferences),
|
getGlobalUpdateGroup(allCategories, libraryPreferences),
|
||||||
getChapterSwipeActionsGroup(libraryPreferences),
|
getBehaviorGroup(libraryPreferences),
|
||||||
// SY -->
|
// SY -->
|
||||||
getSortingCategory(LocalNavigator.currentOrThrow, libraryPreferences),
|
getSortingCategory(LocalNavigator.currentOrThrow, libraryPreferences),
|
||||||
getMigrationCategory(unsortedPreferences),
|
getMigrationCategory(unsortedPreferences),
|
||||||
@@ -103,12 +102,12 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
onClick = { navigator.push(CategoryScreen()) },
|
onClick = { navigator.push(CategoryScreen()) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = libraryPreferences.defaultCategory(),
|
preference = libraryPreferences.defaultCategory(),
|
||||||
title = stringResource(MR.strings.default_category),
|
|
||||||
entries = ids.zip(labels).toMap().toImmutableMap(),
|
entries = ids.zip(labels).toMap().toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.default_category),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = libraryPreferences.categorizedDisplaySettings(),
|
preference = libraryPreferences.categorizedDisplaySettings(),
|
||||||
title = stringResource(MR.strings.categorized_display_settings),
|
title = stringResource(MR.strings.categorized_display_settings),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
if (!it) {
|
if (!it) {
|
||||||
@@ -160,8 +159,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_category_library_update),
|
title = stringResource(MR.strings.pref_category_library_update),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = autoUpdateIntervalPref,
|
preference = autoUpdateIntervalPref,
|
||||||
title = stringResource(MR.strings.pref_library_update_interval),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
0 to stringResource(MR.strings.update_never),
|
0 to stringResource(MR.strings.update_never),
|
||||||
12 to stringResource(MR.strings.update_12hour),
|
12 to stringResource(MR.strings.update_12hour),
|
||||||
@@ -170,21 +168,22 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
72 to stringResource(MR.strings.update_72hour),
|
72 to stringResource(MR.strings.update_72hour),
|
||||||
168 to stringResource(MR.strings.update_weekly),
|
168 to stringResource(MR.strings.update_weekly),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_library_update_interval),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
LibraryUpdateJob.setupTask(context, it)
|
LibraryUpdateJob.setupTask(context, it)
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = libraryPreferences.autoUpdateDeviceRestrictions(),
|
preference = libraryPreferences.autoUpdateDeviceRestrictions(),
|
||||||
enabled = autoUpdateInterval > 0,
|
|
||||||
title = stringResource(MR.strings.pref_library_update_restriction),
|
|
||||||
subtitle = stringResource(MR.strings.restrictions),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
|
DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
|
||||||
DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
|
DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
|
||||||
DEVICE_CHARGING to stringResource(MR.strings.charging),
|
DEVICE_CHARGING to stringResource(MR.strings.charging),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_library_update_restriction),
|
||||||
|
subtitle = stringResource(MR.strings.restrictions),
|
||||||
|
enabled = autoUpdateInterval > 0,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
// Post to event looper to allow the preference to be updated.
|
// Post to event looper to allow the preference to be updated.
|
||||||
ContextCompat.getMainExecutor(context).execute { LibraryUpdateJob.setupTask(context) }
|
ContextCompat.getMainExecutor(context).execute { LibraryUpdateJob.setupTask(context) }
|
||||||
@@ -202,7 +201,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
// SY -->
|
// SY -->
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = libraryPreferences.groupLibraryUpdateType(),
|
preference = libraryPreferences.groupLibraryUpdateType(),
|
||||||
title = stringResource(SYMR.strings.library_group_updates),
|
title = stringResource(SYMR.strings.library_group_updates),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
GroupLibraryMode.GLOBAL to stringResource(SYMR.strings.library_group_updates_global),
|
GroupLibraryMode.GLOBAL to stringResource(SYMR.strings.library_group_updates_global),
|
||||||
@@ -213,45 +212,37 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
// SY <--
|
// SY <--
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = libraryPreferences.autoUpdateMetadata(),
|
preference = libraryPreferences.autoUpdateMetadata(),
|
||||||
title = stringResource(MR.strings.pref_library_update_refresh_metadata),
|
title = stringResource(MR.strings.pref_library_update_refresh_metadata),
|
||||||
subtitle = stringResource(MR.strings.pref_library_update_refresh_metadata_summary),
|
subtitle = stringResource(MR.strings.pref_library_update_refresh_metadata_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = libraryPreferences.autoUpdateMangaRestrictions(),
|
preference = libraryPreferences.autoUpdateMangaRestrictions(),
|
||||||
title = stringResource(MR.strings.pref_library_update_smart_update),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
|
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
|
||||||
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
|
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
|
||||||
MANGA_NON_COMPLETED to stringResource(MR.strings.pref_update_only_non_completed),
|
MANGA_NON_COMPLETED to stringResource(MR.strings.pref_update_only_non_completed),
|
||||||
MANGA_OUTSIDE_RELEASE_PERIOD to stringResource(MR.strings.pref_update_only_in_release_period),
|
MANGA_OUTSIDE_RELEASE_PERIOD to stringResource(MR.strings.pref_update_only_in_release_period),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_library_update_smart_update),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = libraryPreferences.newShowUpdatesCount(),
|
preference = libraryPreferences.newShowUpdatesCount(),
|
||||||
title = stringResource(MR.strings.pref_library_update_show_tab_badge),
|
title = stringResource(MR.strings.pref_library_update_show_tab_badge),
|
||||||
),
|
),
|
||||||
// SY -->
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = libraryPreferences.libraryReadDuplicateChapters(),
|
|
||||||
title = stringResource(SYMR.strings.pref_library_mark_duplicate_chapters),
|
|
||||||
subtitle = stringResource(SYMR.strings.pref_library_mark_duplicate_chapters_summary),
|
|
||||||
),
|
|
||||||
// SY <--
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getChapterSwipeActionsGroup(
|
private fun getBehaviorGroup(
|
||||||
libraryPreferences: LibraryPreferences,
|
libraryPreferences: LibraryPreferences,
|
||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.pref_chapter_swipe),
|
title = stringResource(MR.strings.pref_behavior),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = libraryPreferences.swipeToStartAction(),
|
preference = libraryPreferences.swipeToStartAction(),
|
||||||
title = stringResource(MR.strings.pref_chapter_swipe_start),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
LibraryPreferences.ChapterSwipeAction.Disabled to
|
LibraryPreferences.ChapterSwipeAction.Disabled to
|
||||||
stringResource(MR.strings.disabled),
|
stringResource(MR.strings.disabled),
|
||||||
@@ -262,10 +253,10 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
LibraryPreferences.ChapterSwipeAction.Download to
|
LibraryPreferences.ChapterSwipeAction.Download to
|
||||||
stringResource(MR.strings.action_download),
|
stringResource(MR.strings.action_download),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_chapter_swipe_start),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = libraryPreferences.swipeToEndAction(),
|
preference = libraryPreferences.swipeToEndAction(),
|
||||||
title = stringResource(MR.strings.pref_chapter_swipe_end),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
LibraryPreferences.ChapterSwipeAction.Disabled to
|
LibraryPreferences.ChapterSwipeAction.Disabled to
|
||||||
stringResource(MR.strings.disabled),
|
stringResource(MR.strings.disabled),
|
||||||
@@ -276,6 +267,17 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
LibraryPreferences.ChapterSwipeAction.Download to
|
LibraryPreferences.ChapterSwipeAction.Download to
|
||||||
stringResource(MR.strings.action_download),
|
stringResource(MR.strings.action_download),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_chapter_swipe_end),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
|
preference = libraryPreferences.markDuplicateReadChapterAsRead(),
|
||||||
|
entries = persistentMapOf(
|
||||||
|
MARK_DUPLICATE_CHAPTER_READ_EXISTING to
|
||||||
|
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_existing),
|
||||||
|
MARK_DUPLICATE_CHAPTER_READ_NEW to
|
||||||
|
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_new),
|
||||||
|
),
|
||||||
|
title = stringResource(MR.strings.pref_mark_duplicate_read_chapter_read),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -308,7 +310,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
enabled = skipPreMigration || migrationSources.isNotEmpty(),
|
enabled = skipPreMigration || migrationSources.isNotEmpty(),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = unsortedPreferences.skipPreMigration(),
|
preference = unsortedPreferences.skipPreMigration(),
|
||||||
title = stringResource(SYMR.strings.skip_pre_migration),
|
title = stringResource(SYMR.strings.skip_pre_migration),
|
||||||
subtitle = stringResource(SYMR.strings.pref_skip_pre_migration_summary),
|
subtitle = stringResource(SYMR.strings.pref_skip_pre_migration_summary),
|
||||||
),
|
),
|
||||||
|
|||||||
+2
-2
@@ -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,
|
||||||
@@ -178,7 +178,7 @@ object SettingsMangadexScreen : SearchableSettings {
|
|||||||
sourcePreferences: SourcePreferences,
|
sourcePreferences: SourcePreferences,
|
||||||
): Preference.PreferenceItem.ListPreference<String> {
|
): Preference.PreferenceItem.ListPreference<String> {
|
||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
pref = unsortedPreferences.preferredMangaDexId(),
|
preference = unsortedPreferences.preferredMangaDexId(),
|
||||||
title = stringResource(SYMR.strings.mangadex_preffered_source),
|
title = stringResource(SYMR.strings.mangadex_preffered_source),
|
||||||
subtitle = stringResource(SYMR.strings.mangadex_preffered_source_summary),
|
subtitle = stringResource(SYMR.strings.mangadex_preffered_source_summary),
|
||||||
entries = MdUtil.getEnabledMangaDexs(sourcePreferences)
|
entries = MdUtil.getEnabledMangaDexs(sourcePreferences)
|
||||||
|
|||||||
+81
-82
@@ -39,45 +39,45 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPref.defaultReadingMode(),
|
preference = readerPref.defaultReadingMode(),
|
||||||
title = stringResource(MR.strings.pref_viewer_type),
|
|
||||||
entries = ReadingMode.entries.drop(1)
|
entries = ReadingMode.entries.drop(1)
|
||||||
.associate { it.flagValue to stringResource(it.stringRes) }
|
.associate { it.flagValue to stringResource(it.stringRes) }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_viewer_type),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPref.doubleTapAnimSpeed(),
|
preference = readerPref.doubleTapAnimSpeed(),
|
||||||
title = stringResource(MR.strings.pref_double_tap_anim_speed),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
1 to stringResource(MR.strings.double_tap_anim_speed_0),
|
1 to stringResource(MR.strings.double_tap_anim_speed_0),
|
||||||
500 to stringResource(MR.strings.double_tap_anim_speed_normal),
|
500 to stringResource(MR.strings.double_tap_anim_speed_normal),
|
||||||
250 to stringResource(MR.strings.double_tap_anim_speed_fast),
|
250 to stringResource(MR.strings.double_tap_anim_speed_fast),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_double_tap_anim_speed),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPref.showReadingMode(),
|
preference = readerPref.showReadingMode(),
|
||||||
title = stringResource(MR.strings.pref_show_reading_mode),
|
title = stringResource(MR.strings.pref_show_reading_mode),
|
||||||
subtitle = stringResource(MR.strings.pref_show_reading_mode_summary),
|
subtitle = stringResource(MR.strings.pref_show_reading_mode_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPref.showNavigationOverlayOnStart(),
|
preference = readerPref.showNavigationOverlayOnStart(),
|
||||||
title = stringResource(MR.strings.pref_show_navigation_mode),
|
title = stringResource(MR.strings.pref_show_navigation_mode),
|
||||||
subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary),
|
subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary),
|
||||||
),
|
),
|
||||||
// SY -->
|
// SY -->
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPref.forceHorizontalSeekbar(),
|
preference = readerPref.forceHorizontalSeekbar(),
|
||||||
title = stringResource(SYMR.strings.pref_force_horz_seekbar),
|
title = stringResource(SYMR.strings.pref_force_horz_seekbar),
|
||||||
subtitle = stringResource(SYMR.strings.pref_force_horz_seekbar_summary),
|
subtitle = stringResource(SYMR.strings.pref_force_horz_seekbar_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPref.landscapeVerticalSeekbar(),
|
preference = readerPref.landscapeVerticalSeekbar(),
|
||||||
title = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape),
|
title = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape),
|
||||||
subtitle = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape_summary),
|
subtitle = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape_summary),
|
||||||
enabled = !forceHorizontalSeekbar,
|
enabled = !forceHorizontalSeekbar,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPref.leftVerticalSeekbar(),
|
preference = readerPref.leftVerticalSeekbar(),
|
||||||
title = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar),
|
title = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar),
|
||||||
subtitle = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar_summary),
|
subtitle = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar_summary),
|
||||||
enabled = !forceHorizontalSeekbar,
|
enabled = !forceHorizontalSeekbar,
|
||||||
@@ -85,7 +85,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
// SY <--
|
// SY <--
|
||||||
/* SY -->
|
/* SY -->
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPref.pageTransitions(),
|
preference = readerPref.pageTransitions(),
|
||||||
title = stringResource(MR.strings.pref_page_transitions),
|
title = stringResource(MR.strings.pref_page_transitions),
|
||||||
),
|
),
|
||||||
SY <-- */
|
SY <-- */
|
||||||
@@ -114,39 +114,39 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_category_display),
|
title = stringResource(MR.strings.pref_category_display),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.defaultOrientationType(),
|
preference = readerPreferences.defaultOrientationType(),
|
||||||
title = stringResource(MR.strings.pref_rotation_type),
|
|
||||||
entries = ReaderOrientation.entries.drop(1)
|
entries = ReaderOrientation.entries.drop(1)
|
||||||
.associate { it.flagValue to stringResource(it.stringRes) }
|
.associate { it.flagValue to stringResource(it.stringRes) }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_rotation_type),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.readerTheme(),
|
preference = readerPreferences.readerTheme(),
|
||||||
title = stringResource(MR.strings.pref_reader_theme),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
1 to stringResource(MR.strings.black_background),
|
1 to stringResource(MR.strings.black_background),
|
||||||
2 to stringResource(MR.strings.gray_background),
|
2 to stringResource(MR.strings.gray_background),
|
||||||
0 to stringResource(MR.strings.white_background),
|
0 to stringResource(MR.strings.white_background),
|
||||||
3 to stringResource(MR.strings.automatic_background),
|
3 to stringResource(MR.strings.automatic_background),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_reader_theme),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = fullscreenPref,
|
preference = fullscreenPref,
|
||||||
title = stringResource(MR.strings.pref_fullscreen),
|
title = stringResource(MR.strings.pref_fullscreen),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.cutoutShort(),
|
preference = readerPreferences.cutoutShort(),
|
||||||
title = stringResource(MR.strings.pref_cutout_short),
|
title = stringResource(MR.strings.pref_cutout_short),
|
||||||
enabled = fullscreen &&
|
enabled = fullscreen &&
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
||||||
LocalView.current.rootWindowInsets?.displayCutout != null, // has cutout
|
LocalView.current.rootWindowInsets?.displayCutout != null, // has cutout
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.keepScreenOn(),
|
preference = readerPreferences.keepScreenOn(),
|
||||||
title = stringResource(MR.strings.pref_keep_screen_on),
|
title = stringResource(MR.strings.pref_keep_screen_on),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.showPageNumber(),
|
preference = readerPreferences.showPageNumber(),
|
||||||
title = stringResource(MR.strings.pref_show_page_number),
|
title = stringResource(MR.strings.pref_show_page_number),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -169,43 +169,41 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = "E-Ink",
|
title = "E-Ink",
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.flashOnPageChange(),
|
preference = readerPreferences.flashOnPageChange(),
|
||||||
title = stringResource(MR.strings.pref_flash_page),
|
title = stringResource(MR.strings.pref_flash_page),
|
||||||
subtitle = stringResource(MR.strings.pref_flash_page_summ),
|
subtitle = stringResource(MR.strings.pref_flash_page_summ),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||||
min = 1,
|
valueRange = 1..15,
|
||||||
max = 15,
|
|
||||||
title = stringResource(MR.strings.pref_flash_duration),
|
title = stringResource(MR.strings.pref_flash_duration),
|
||||||
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||||
|
enabled = flashPageState,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
|
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
enabled = flashPageState,
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
value = flashInterval,
|
value = flashInterval,
|
||||||
min = 1,
|
valueRange = 1..10,
|
||||||
max = 10,
|
|
||||||
title = stringResource(MR.strings.pref_flash_page_interval),
|
title = stringResource(MR.strings.pref_flash_page_interval),
|
||||||
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||||
|
enabled = flashPageState,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
flashIntervalPref.set(it)
|
flashIntervalPref.set(it)
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
enabled = flashPageState,
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = flashColorPref,
|
preference = flashColorPref,
|
||||||
title = stringResource(MR.strings.pref_flash_with),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
|
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
|
||||||
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
|
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
|
||||||
ReaderPreferences.FlashColor.WHITE_BLACK
|
ReaderPreferences.FlashColor.WHITE_BLACK
|
||||||
to stringResource(MR.strings.pref_flash_style_white_black),
|
to stringResource(MR.strings.pref_flash_style_white_black),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_flash_with),
|
||||||
enabled = flashPageState,
|
enabled = flashPageState,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -218,26 +216,26 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_category_reading),
|
title = stringResource(MR.strings.pref_category_reading),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.skipRead(),
|
preference = readerPreferences.skipRead(),
|
||||||
title = stringResource(MR.strings.pref_skip_read_chapters),
|
title = stringResource(MR.strings.pref_skip_read_chapters),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.skipFiltered(),
|
preference = readerPreferences.skipFiltered(),
|
||||||
title = stringResource(MR.strings.pref_skip_filtered_chapters),
|
title = stringResource(MR.strings.pref_skip_filtered_chapters),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.skipDupe(),
|
preference = readerPreferences.skipDupe(),
|
||||||
title = stringResource(MR.strings.pref_skip_dupe_chapters),
|
title = stringResource(MR.strings.pref_skip_dupe_chapters),
|
||||||
),
|
),
|
||||||
// SY -->
|
// SY -->
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.markReadDupe(),
|
preference = readerPreferences.markReadDupe(),
|
||||||
title = stringResource(SYMR.strings.pref_mark_read_dupe_chapters),
|
title = stringResource(SYMR.strings.pref_mark_read_dupe_chapters),
|
||||||
subtitle = stringResource(SYMR.strings.pref_mark_read_dupe_chapters_summary),
|
subtitle = stringResource(SYMR.strings.pref_mark_read_dupe_chapters_summary),
|
||||||
),
|
),
|
||||||
// SY <--
|
// SY <--
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.alwaysShowChapterTransition(),
|
preference = readerPreferences.alwaysShowChapterTransition(),
|
||||||
title = stringResource(MR.strings.pref_always_show_chapter_transition),
|
title = stringResource(MR.strings.pref_always_show_chapter_transition),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -260,16 +258,15 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pager_viewer),
|
title = stringResource(MR.strings.pager_viewer),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = navModePref,
|
preference = navModePref,
|
||||||
title = stringResource(MR.strings.pref_viewer_nav),
|
|
||||||
entries = ReaderPreferences.TapZones
|
entries = ReaderPreferences.TapZones
|
||||||
.mapIndexed { index, it -> index to stringResource(it) }
|
.mapIndexed { index, it -> index to stringResource(it) }
|
||||||
.toMap()
|
.toMap()
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_viewer_nav),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.pagerNavInverted(),
|
preference = readerPreferences.pagerNavInverted(),
|
||||||
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
|
|
||||||
entries = persistentListOf(
|
entries = persistentListOf(
|
||||||
ReaderPreferences.TappingInvertMode.NONE,
|
ReaderPreferences.TappingInvertMode.NONE,
|
||||||
ReaderPreferences.TappingInvertMode.HORIZONTAL,
|
ReaderPreferences.TappingInvertMode.HORIZONTAL,
|
||||||
@@ -278,46 +275,47 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
.associateWith { stringResource(it.titleRes) }
|
.associateWith { stringResource(it.titleRes) }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
|
||||||
enabled = navMode != 5,
|
enabled = navMode != 5,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = imageScaleTypePref,
|
preference = imageScaleTypePref,
|
||||||
title = stringResource(MR.strings.pref_image_scale_type),
|
|
||||||
entries = ReaderPreferences.ImageScaleType
|
entries = ReaderPreferences.ImageScaleType
|
||||||
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
||||||
.toMap()
|
.toMap()
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_image_scale_type),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.zoomStart(),
|
preference = readerPreferences.zoomStart(),
|
||||||
title = stringResource(MR.strings.pref_zoom_start),
|
|
||||||
entries = ReaderPreferences.ZoomStart
|
entries = ReaderPreferences.ZoomStart
|
||||||
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
||||||
.toMap()
|
.toMap()
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_zoom_start),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.cropBorders(),
|
preference = readerPreferences.cropBorders(),
|
||||||
title = stringResource(MR.strings.pref_crop_borders),
|
title = stringResource(MR.strings.pref_crop_borders),
|
||||||
),
|
),
|
||||||
// SY -->
|
// SY -->
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.pageTransitionsPager(),
|
preference = readerPreferences.pageTransitionsPager(),
|
||||||
title = stringResource(MR.strings.pref_page_transitions),
|
title = stringResource(MR.strings.pref_page_transitions),
|
||||||
),
|
),
|
||||||
// SY <--
|
// SY <--
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.landscapeZoom(),
|
preference = readerPreferences.landscapeZoom(),
|
||||||
title = stringResource(MR.strings.pref_landscape_zoom),
|
title = stringResource(MR.strings.pref_landscape_zoom),
|
||||||
enabled = imageScaleType == 1,
|
enabled = imageScaleType == 1,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.navigateToPan(),
|
preference = readerPreferences.navigateToPan(),
|
||||||
title = stringResource(MR.strings.pref_navigate_pan),
|
title = stringResource(MR.strings.pref_navigate_pan),
|
||||||
enabled = navMode != 5,
|
enabled = navMode != 5,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = dualPageSplitPref,
|
preference = dualPageSplitPref,
|
||||||
title = stringResource(MR.strings.pref_dual_page_split),
|
title = stringResource(MR.strings.pref_dual_page_split),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
rotateToFitPref.set(false)
|
rotateToFitPref.set(false)
|
||||||
@@ -325,13 +323,13 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.dualPageInvertPaged(),
|
preference = readerPreferences.dualPageInvertPaged(),
|
||||||
title = stringResource(MR.strings.pref_dual_page_invert),
|
title = stringResource(MR.strings.pref_dual_page_invert),
|
||||||
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
|
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
|
||||||
enabled = dualPageSplit,
|
enabled = dualPageSplit,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = rotateToFitPref,
|
preference = rotateToFitPref,
|
||||||
title = stringResource(MR.strings.pref_page_rotate),
|
title = stringResource(MR.strings.pref_page_rotate),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
dualPageSplitPref.set(false)
|
dualPageSplitPref.set(false)
|
||||||
@@ -339,7 +337,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.dualPageRotateToFitInvert(),
|
preference = readerPreferences.dualPageRotateToFitInvert(),
|
||||||
title = stringResource(MR.strings.pref_page_rotate_invert),
|
title = stringResource(MR.strings.pref_page_rotate_invert),
|
||||||
enabled = rotateToFit,
|
enabled = rotateToFit,
|
||||||
),
|
),
|
||||||
@@ -365,16 +363,15 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.webtoon_viewer),
|
title = stringResource(MR.strings.webtoon_viewer),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = navModePref,
|
preference = navModePref,
|
||||||
title = stringResource(MR.strings.pref_viewer_nav),
|
|
||||||
entries = ReaderPreferences.TapZones
|
entries = ReaderPreferences.TapZones
|
||||||
.mapIndexed { index, it -> index to stringResource(it) }
|
.mapIndexed { index, it -> index to stringResource(it) }
|
||||||
.toMap()
|
.toMap()
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_viewer_nav),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.webtoonNavInverted(),
|
preference = readerPreferences.webtoonNavInverted(),
|
||||||
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
|
|
||||||
entries = persistentListOf(
|
entries = persistentListOf(
|
||||||
ReaderPreferences.TappingInvertMode.NONE,
|
ReaderPreferences.TappingInvertMode.NONE,
|
||||||
ReaderPreferences.TappingInvertMode.HORIZONTAL,
|
ReaderPreferences.TappingInvertMode.HORIZONTAL,
|
||||||
@@ -383,35 +380,37 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
.associateWith { stringResource(it.titleRes) }
|
.associateWith { stringResource(it.titleRes) }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
|
||||||
enabled = navMode != 5,
|
enabled = navMode != 5,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
value = webtoonSidePadding,
|
value = webtoonSidePadding,
|
||||||
|
valueRange = ReaderPreferences.let {
|
||||||
|
it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX
|
||||||
|
},
|
||||||
title = stringResource(MR.strings.pref_webtoon_side_padding),
|
title = stringResource(MR.strings.pref_webtoon_side_padding),
|
||||||
subtitle = numberFormat.format(webtoonSidePadding / 100f),
|
subtitle = numberFormat.format(webtoonSidePadding / 100f),
|
||||||
min = ReaderPreferences.WEBTOON_PADDING_MIN,
|
|
||||||
max = ReaderPreferences.WEBTOON_PADDING_MAX,
|
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
webtoonSidePaddingPref.set(it)
|
webtoonSidePaddingPref.set(it)
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.readerHideThreshold(),
|
preference = readerPreferences.readerHideThreshold(),
|
||||||
title = stringResource(MR.strings.pref_hide_threshold),
|
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest),
|
ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest),
|
||||||
ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high),
|
ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high),
|
||||||
ReaderPreferences.ReaderHideThreshold.LOW to stringResource(MR.strings.pref_low),
|
ReaderPreferences.ReaderHideThreshold.LOW to stringResource(MR.strings.pref_low),
|
||||||
ReaderPreferences.ReaderHideThreshold.LOWEST to stringResource(MR.strings.pref_lowest),
|
ReaderPreferences.ReaderHideThreshold.LOWEST to stringResource(MR.strings.pref_lowest),
|
||||||
),
|
),
|
||||||
|
title = stringResource(MR.strings.pref_hide_threshold),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.cropBordersWebtoon(),
|
preference = readerPreferences.cropBordersWebtoon(),
|
||||||
title = stringResource(MR.strings.pref_crop_borders),
|
title = stringResource(MR.strings.pref_crop_borders),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = dualPageSplitPref,
|
preference = dualPageSplitPref,
|
||||||
title = stringResource(MR.strings.pref_dual_page_split),
|
title = stringResource(MR.strings.pref_dual_page_split),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
rotateToFitPref.set(false)
|
rotateToFitPref.set(false)
|
||||||
@@ -419,13 +418,13 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.dualPageInvertWebtoon(),
|
preference = readerPreferences.dualPageInvertWebtoon(),
|
||||||
title = stringResource(MR.strings.pref_dual_page_invert),
|
title = stringResource(MR.strings.pref_dual_page_invert),
|
||||||
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
|
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
|
||||||
enabled = dualPageSplit,
|
enabled = dualPageSplit,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = rotateToFitPref,
|
preference = rotateToFitPref,
|
||||||
title = stringResource(MR.strings.pref_page_rotate),
|
title = stringResource(MR.strings.pref_page_rotate),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
dualPageSplitPref.set(false)
|
dualPageSplitPref.set(false)
|
||||||
@@ -433,21 +432,21 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.dualPageRotateToFitInvertWebtoon(),
|
preference = readerPreferences.dualPageRotateToFitInvertWebtoon(),
|
||||||
title = stringResource(MR.strings.pref_page_rotate_invert),
|
title = stringResource(MR.strings.pref_page_rotate_invert),
|
||||||
enabled = rotateToFit,
|
enabled = rotateToFit,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.webtoonDoubleTapZoomEnabled(),
|
preference = readerPreferences.webtoonDoubleTapZoomEnabled(),
|
||||||
title = stringResource(MR.strings.pref_double_tap_zoom),
|
title = stringResource(MR.strings.pref_double_tap_zoom),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.webtoonDisableZoomOut(),
|
preference = readerPreferences.webtoonDisableZoomOut(),
|
||||||
title = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
|
title = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
|
||||||
),
|
),
|
||||||
// SY -->
|
// SY -->
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.pageTransitionsWebtoon(),
|
preference = readerPreferences.pageTransitionsWebtoon(),
|
||||||
title = stringResource(MR.strings.pref_page_transitions),
|
title = stringResource(MR.strings.pref_page_transitions),
|
||||||
),
|
),
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -462,12 +461,12 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.vertical_plus_viewer),
|
title = stringResource(MR.strings.vertical_plus_viewer),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.continuousVerticalTappingByPage(),
|
preference = readerPreferences.continuousVerticalTappingByPage(),
|
||||||
title = stringResource(SYMR.strings.tap_scroll_page),
|
title = stringResource(SYMR.strings.tap_scroll_page),
|
||||||
subtitle = stringResource(SYMR.strings.tap_scroll_page_summary),
|
subtitle = stringResource(SYMR.strings.tap_scroll_page_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.cropBordersContinuousVertical(),
|
preference = readerPreferences.cropBordersContinuousVertical(),
|
||||||
title = stringResource(MR.strings.pref_crop_borders),
|
title = stringResource(MR.strings.pref_crop_borders),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -483,11 +482,11 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_reader_navigation),
|
title = stringResource(MR.strings.pref_reader_navigation),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readWithVolumeKeysPref,
|
preference = readWithVolumeKeysPref,
|
||||||
title = stringResource(MR.strings.pref_read_with_volume_keys),
|
title = stringResource(MR.strings.pref_read_with_volume_keys),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.readWithVolumeKeysInverted(),
|
preference = readerPreferences.readWithVolumeKeysInverted(),
|
||||||
title = stringResource(MR.strings.pref_read_with_volume_keys_inverted),
|
title = stringResource(MR.strings.pref_read_with_volume_keys_inverted),
|
||||||
enabled = readWithVolumeKeys,
|
enabled = readWithVolumeKeys,
|
||||||
),
|
),
|
||||||
@@ -501,11 +500,11 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_reader_actions),
|
title = stringResource(MR.strings.pref_reader_actions),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.readWithLongTap(),
|
preference = readerPreferences.readWithLongTap(),
|
||||||
title = stringResource(MR.strings.pref_read_with_long_tap),
|
title = stringResource(MR.strings.pref_read_with_long_tap),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.folderPerManga(),
|
preference = readerPreferences.folderPerManga(),
|
||||||
title = stringResource(MR.strings.pref_create_folder_per_manga),
|
title = stringResource(MR.strings.pref_create_folder_per_manga),
|
||||||
subtitle = stringResource(MR.strings.pref_create_folder_per_manga_summary),
|
subtitle = stringResource(MR.strings.pref_create_folder_per_manga_summary),
|
||||||
),
|
),
|
||||||
@@ -520,7 +519,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(SYMR.strings.page_downloading),
|
title = stringResource(SYMR.strings.page_downloading),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.preloadSize(),
|
preference = readerPreferences.preloadSize(),
|
||||||
title = stringResource(SYMR.strings.reader_preload_amount),
|
title = stringResource(SYMR.strings.reader_preload_amount),
|
||||||
subtitle = stringResource(SYMR.strings.reader_preload_amount_summary),
|
subtitle = stringResource(SYMR.strings.reader_preload_amount_summary),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
@@ -535,13 +534,13 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.readerThreads(),
|
preference = readerPreferences.readerThreads(),
|
||||||
title = stringResource(SYMR.strings.download_threads),
|
title = stringResource(SYMR.strings.download_threads),
|
||||||
subtitle = stringResource(SYMR.strings.download_threads_summary),
|
subtitle = stringResource(SYMR.strings.download_threads_summary),
|
||||||
entries = List(5) { it }.associateWith { it.toString() }.toImmutableMap(),
|
entries = List(5) { it }.associateWith { it.toString() }.toImmutableMap(),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.cacheSize(),
|
preference = readerPreferences.cacheSize(),
|
||||||
title = stringResource(SYMR.strings.reader_cache_size),
|
title = stringResource(SYMR.strings.reader_cache_size),
|
||||||
subtitle = stringResource(SYMR.strings.reader_cache_size_summary),
|
subtitle = stringResource(SYMR.strings.reader_cache_size_summary),
|
||||||
entries = persistentMapOf(
|
entries = persistentMapOf(
|
||||||
@@ -564,7 +563,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.aggressivePageLoading(),
|
preference = readerPreferences.aggressivePageLoading(),
|
||||||
title = stringResource(SYMR.strings.aggressively_load_pages),
|
title = stringResource(SYMR.strings.aggressively_load_pages),
|
||||||
subtitle = stringResource(SYMR.strings.aggressively_load_pages_summary),
|
subtitle = stringResource(SYMR.strings.aggressively_load_pages_summary),
|
||||||
),
|
),
|
||||||
@@ -579,21 +578,21 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(SYMR.strings.pref_category_fork),
|
title = stringResource(SYMR.strings.pref_category_fork),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.readerInstantRetry(),
|
preference = readerPreferences.readerInstantRetry(),
|
||||||
title = stringResource(SYMR.strings.skip_queue_on_retry),
|
title = stringResource(SYMR.strings.skip_queue_on_retry),
|
||||||
subtitle = stringResource(SYMR.strings.skip_queue_on_retry_summary),
|
subtitle = stringResource(SYMR.strings.skip_queue_on_retry_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.preserveReadingPosition(),
|
preference = readerPreferences.preserveReadingPosition(),
|
||||||
title = stringResource(SYMR.strings.preserve_reading_position),
|
title = stringResource(SYMR.strings.preserve_reading_position),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.useAutoWebtoon(),
|
preference = readerPreferences.useAutoWebtoon(),
|
||||||
title = stringResource(SYMR.strings.auto_webtoon_mode),
|
title = stringResource(SYMR.strings.auto_webtoon_mode),
|
||||||
subtitle = stringResource(SYMR.strings.auto_webtoon_mode_summary),
|
subtitle = stringResource(SYMR.strings.auto_webtoon_mode_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = readerPreferences.readerBottomButtons(),
|
preference = readerPreferences.readerBottomButtons(),
|
||||||
title = stringResource(SYMR.strings.reader_bottom_buttons),
|
title = stringResource(SYMR.strings.reader_bottom_buttons),
|
||||||
subtitle = stringResource(SYMR.strings.reader_bottom_buttons_summary),
|
subtitle = stringResource(SYMR.strings.reader_bottom_buttons_summary),
|
||||||
entries = ReaderBottomButton.entries
|
entries = ReaderBottomButton.entries
|
||||||
@@ -601,7 +600,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.pageLayout(),
|
preference = readerPreferences.pageLayout(),
|
||||||
title = stringResource(SYMR.strings.page_layout),
|
title = stringResource(SYMR.strings.page_layout),
|
||||||
subtitle = stringResource(SYMR.strings.automatic_can_still_switch),
|
subtitle = stringResource(SYMR.strings.automatic_can_still_switch),
|
||||||
entries = ReaderPreferences.PageLayouts
|
entries = ReaderPreferences.PageLayouts
|
||||||
@@ -610,12 +609,12 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.invertDoublePages(),
|
preference = readerPreferences.invertDoublePages(),
|
||||||
title = stringResource(SYMR.strings.invert_double_pages),
|
title = stringResource(SYMR.strings.invert_double_pages),
|
||||||
enabled = pageLayout != PagerConfig.PageLayout.SINGLE_PAGE,
|
enabled = pageLayout != PagerConfig.PageLayout.SINGLE_PAGE,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.centerMarginType(),
|
preference = readerPreferences.centerMarginType(),
|
||||||
title = stringResource(SYMR.strings.center_margin),
|
title = stringResource(SYMR.strings.center_margin),
|
||||||
subtitle = stringResource(SYMR.strings.pref_center_margin_summary),
|
subtitle = stringResource(SYMR.strings.pref_center_margin_summary),
|
||||||
entries = ReaderPreferences.CenterMarginTypes
|
entries = ReaderPreferences.CenterMarginTypes
|
||||||
@@ -624,7 +623,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.archiveReaderMode(),
|
preference = readerPreferences.archiveReaderMode(),
|
||||||
title = stringResource(SYMR.strings.pref_archive_reader_mode),
|
title = stringResource(SYMR.strings.pref_archive_reader_mode),
|
||||||
subtitle = stringResource(SYMR.strings.pref_archive_reader_mode_summary),
|
subtitle = stringResource(SYMR.strings.pref_archive_reader_mode_summary),
|
||||||
entries = ReaderPreferences.archiveModeTypes
|
entries = ReaderPreferences.archiveModeTypes
|
||||||
|
|||||||
+11
-11
@@ -96,7 +96,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_security),
|
title = stringResource(MR.strings.pref_security),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = useAuthPref,
|
preference = useAuthPref,
|
||||||
title = stringResource(MR.strings.lock_with_biometrics),
|
title = stringResource(MR.strings.lock_with_biometrics),
|
||||||
enabled = authSupported,
|
enabled = authSupported,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
@@ -106,9 +106,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = securityPreferences.lockAppAfter(),
|
preference = securityPreferences.lockAppAfter(),
|
||||||
title = stringResource(MR.strings.lock_when_idle),
|
|
||||||
enabled = authSupported && useAuth,
|
|
||||||
entries = LockAfterValues
|
entries = LockAfterValues
|
||||||
.associateWith {
|
.associateWith {
|
||||||
when (it) {
|
when (it) {
|
||||||
@@ -118,6 +116,8 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.lock_when_idle),
|
||||||
|
enabled = authSupported && useAuth,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
(context as FragmentActivity).authenticate(
|
(context as FragmentActivity).authenticate(
|
||||||
title = context.stringResource(MR.strings.lock_when_idle),
|
title = context.stringResource(MR.strings.lock_when_idle),
|
||||||
@@ -125,25 +125,25 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = securityPreferences.hideNotificationContent(),
|
preference = securityPreferences.hideNotificationContent(),
|
||||||
title = stringResource(MR.strings.hide_notification_content),
|
title = stringResource(MR.strings.hide_notification_content),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = securityPreferences.secureScreen(),
|
preference = securityPreferences.secureScreen(),
|
||||||
title = stringResource(MR.strings.secure_screen),
|
|
||||||
entries = SecurityPreferences.SecureScreenMode.entries
|
entries = SecurityPreferences.SecureScreenMode.entries
|
||||||
.associateWith { stringResource(it.titleRes) }
|
.associateWith { stringResource(it.titleRes) }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
|
title = stringResource(MR.strings.secure_screen),
|
||||||
),
|
),
|
||||||
// SY -->
|
// SY -->
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = securityPreferences.passwordProtectDownloads(),
|
preference = securityPreferences.passwordProtectDownloads(),
|
||||||
title = stringResource(SYMR.strings.password_protect_downloads),
|
title = stringResource(SYMR.strings.password_protect_downloads),
|
||||||
subtitle = stringResource(SYMR.strings.password_protect_downloads_summary),
|
subtitle = stringResource(SYMR.strings.password_protect_downloads_summary),
|
||||||
enabled = isCbzPasswordSet,
|
enabled = isCbzPasswordSet,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = securityPreferences.encryptionType(),
|
preference = securityPreferences.encryptionType(),
|
||||||
title = stringResource(SYMR.strings.encryption_type),
|
title = stringResource(SYMR.strings.encryption_type),
|
||||||
entries = SecurityPreferences.EncryptionType.entries
|
entries = SecurityPreferences.EncryptionType.entries
|
||||||
.associateWith { stringResource(it.titleRes) }
|
.associateWith { stringResource(it.titleRes) }
|
||||||
@@ -384,12 +384,12 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_firebase),
|
title = stringResource(MR.strings.pref_firebase),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = privacyPreferences.crashlytics(),
|
preference = privacyPreferences.crashlytics(),
|
||||||
title = stringResource(MR.strings.onboarding_permission_crashlytics),
|
title = stringResource(MR.strings.onboarding_permission_crashlytics),
|
||||||
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
|
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = privacyPreferences.analytics(),
|
preference = privacyPreferences.analytics(),
|
||||||
title = stringResource(MR.strings.onboarding_permission_analytics),
|
title = stringResource(MR.strings.onboarding_permission_analytics),
|
||||||
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
|
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
|
||||||
),
|
),
|
||||||
|
|||||||
+19
-8
@@ -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,10 +54,12 @@ 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
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@@ -85,6 +88,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 {
|
||||||
@@ -122,44 +126,52 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = trackPreferences.autoUpdateTrack(),
|
preference = trackPreferences.autoUpdateTrack(),
|
||||||
title = stringResource(MR.strings.pref_auto_update_manga_sync),
|
title = stringResource(MR.strings.pref_auto_update_manga_sync),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
preference = trackPreferences.autoUpdateTrackOnMarkRead(),
|
||||||
|
entries = AutoTrackState.entries
|
||||||
|
.associateWith { stringResource(it.titleRes) }
|
||||||
|
.toPersistentMap(),
|
||||||
|
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
|
||||||
|
),
|
||||||
|
// SY -->
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = trackPreferences.resolveUsingSourceMetadata(),
|
||||||
|
title = stringResource(SYMR.strings.pref_tracker_resolve_using_source_metadata),
|
||||||
|
subtitle = stringResource(SYMR.strings.pref_tracker_resolve_using_source_metadata_summary),
|
||||||
|
),
|
||||||
|
// SY <--
|
||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.services),
|
title = stringResource(MR.strings.services),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
Preference.PreferenceItem.TrackerPreference(
|
Preference.PreferenceItem.TrackerPreference(
|
||||||
title = trackerManager.myAnimeList.name,
|
|
||||||
tracker = trackerManager.myAnimeList,
|
tracker = trackerManager.myAnimeList,
|
||||||
login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) },
|
login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) },
|
||||||
logout = { dialog = LogoutDialog(trackerManager.myAnimeList) },
|
logout = { dialog = LogoutDialog(trackerManager.myAnimeList) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TrackerPreference(
|
Preference.PreferenceItem.TrackerPreference(
|
||||||
title = trackerManager.aniList.name,
|
|
||||||
tracker = trackerManager.aniList,
|
tracker = trackerManager.aniList,
|
||||||
login = { context.openInBrowser(AnilistApi.authUrl(), forceDefaultBrowser = true) },
|
login = { context.openInBrowser(AnilistApi.authUrl(), forceDefaultBrowser = true) },
|
||||||
logout = { dialog = LogoutDialog(trackerManager.aniList) },
|
logout = { dialog = LogoutDialog(trackerManager.aniList) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TrackerPreference(
|
Preference.PreferenceItem.TrackerPreference(
|
||||||
title = trackerManager.kitsu.name,
|
|
||||||
tracker = trackerManager.kitsu,
|
tracker = trackerManager.kitsu,
|
||||||
login = { dialog = LoginDialog(trackerManager.kitsu, MR.strings.email) },
|
login = { dialog = LoginDialog(trackerManager.kitsu, MR.strings.email) },
|
||||||
logout = { dialog = LogoutDialog(trackerManager.kitsu) },
|
logout = { dialog = LogoutDialog(trackerManager.kitsu) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TrackerPreference(
|
Preference.PreferenceItem.TrackerPreference(
|
||||||
title = trackerManager.mangaUpdates.name,
|
|
||||||
tracker = trackerManager.mangaUpdates,
|
tracker = trackerManager.mangaUpdates,
|
||||||
login = { dialog = LoginDialog(trackerManager.mangaUpdates, MR.strings.username) },
|
login = { dialog = LoginDialog(trackerManager.mangaUpdates, MR.strings.username) },
|
||||||
logout = { dialog = LogoutDialog(trackerManager.mangaUpdates) },
|
logout = { dialog = LogoutDialog(trackerManager.mangaUpdates) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TrackerPreference(
|
Preference.PreferenceItem.TrackerPreference(
|
||||||
title = trackerManager.shikimori.name,
|
|
||||||
tracker = trackerManager.shikimori,
|
tracker = trackerManager.shikimori,
|
||||||
login = { context.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true) },
|
login = { context.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true) },
|
||||||
logout = { dialog = LogoutDialog(trackerManager.shikimori) },
|
logout = { dialog = LogoutDialog(trackerManager.shikimori) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TrackerPreference(
|
Preference.PreferenceItem.TrackerPreference(
|
||||||
title = trackerManager.bangumi.name,
|
|
||||||
tracker = trackerManager.bangumi,
|
tracker = trackerManager.bangumi,
|
||||||
login = { context.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true) },
|
login = { context.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true) },
|
||||||
logout = { dialog = LogoutDialog(trackerManager.bangumi) },
|
logout = { dialog = LogoutDialog(trackerManager.bangumi) },
|
||||||
@@ -173,7 +185,6 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
enhancedTrackers.first
|
enhancedTrackers.first
|
||||||
.map { service ->
|
.map { service ->
|
||||||
Preference.PreferenceItem.TrackerPreference(
|
Preference.PreferenceItem.TrackerPreference(
|
||||||
title = service.name,
|
|
||||||
tracker = service,
|
tracker = service,
|
||||||
login = { (service as EnhancedTracker).loginNoop() },
|
login = { (service as EnhancedTracker).loginNoop() },
|
||||||
logout = service::logout,
|
logout = service::logout,
|
||||||
|
|||||||
+2
@@ -31,6 +31,7 @@ fun EditTextPreferenceWidget(
|
|||||||
subtitle: String?,
|
subtitle: String?,
|
||||||
icon: ImageVector?,
|
icon: ImageVector?,
|
||||||
value: String,
|
value: String,
|
||||||
|
widget: @Composable (() -> Unit)? = null,
|
||||||
onConfirm: suspend (String) -> Boolean,
|
onConfirm: suspend (String) -> Boolean,
|
||||||
) {
|
) {
|
||||||
var isDialogShown by remember { mutableStateOf(false) }
|
var isDialogShown by remember { mutableStateOf(false) }
|
||||||
@@ -39,6 +40,7 @@ fun EditTextPreferenceWidget(
|
|||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle?.format(value),
|
subtitle = subtitle?.format(value),
|
||||||
icon = icon,
|
icon = icon,
|
||||||
|
widget = widget,
|
||||||
onPreferenceClick = { isDialogShown = true },
|
onPreferenceClick = { isDialogShown = true },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -36,12 +37,12 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
if (customBrightness) {
|
if (customBrightness) {
|
||||||
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
|
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(MR.strings.pref_custom_brightness),
|
|
||||||
min = -75,
|
|
||||||
max = 100,
|
|
||||||
value = customBrightnessValue,
|
value = customBrightnessValue,
|
||||||
valueText = customBrightnessValue.toString(),
|
valueRange = -75..100,
|
||||||
|
steps = 0,
|
||||||
|
label = stringResource(MR.strings.pref_custom_brightness),
|
||||||
onChange = { screenModel.preferences.customBrightnessValue().set(it) },
|
onChange = { screenModel.preferences.customBrightnessValue().set(it) },
|
||||||
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,48 +54,52 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
if (colorFilter) {
|
if (colorFilter) {
|
||||||
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
|
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(MR.strings.color_filter_r_value),
|
|
||||||
max = 255,
|
|
||||||
value = colorFilterValue.red,
|
value = colorFilterValue.red,
|
||||||
valueText = colorFilterValue.red.toString(),
|
valueRange = 0..255,
|
||||||
|
steps = 0,
|
||||||
|
label = stringResource(MR.strings.color_filter_r_value),
|
||||||
onChange = { newRValue ->
|
onChange = { newRValue ->
|
||||||
screenModel.preferences.colorFilterValue().getAndSet {
|
screenModel.preferences.colorFilterValue().getAndSet {
|
||||||
getColorValue(it, newRValue, RED_MASK, 16)
|
getColorValue(it, newRValue, RED_MASK, 16)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(MR.strings.color_filter_g_value),
|
|
||||||
max = 255,
|
|
||||||
value = colorFilterValue.green,
|
value = colorFilterValue.green,
|
||||||
valueText = colorFilterValue.green.toString(),
|
valueRange = 0..255,
|
||||||
|
steps = 0,
|
||||||
|
label = stringResource(MR.strings.color_filter_g_value),
|
||||||
onChange = { newGValue ->
|
onChange = { newGValue ->
|
||||||
screenModel.preferences.colorFilterValue().getAndSet {
|
screenModel.preferences.colorFilterValue().getAndSet {
|
||||||
getColorValue(it, newGValue, GREEN_MASK, 8)
|
getColorValue(it, newGValue, GREEN_MASK, 8)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(MR.strings.color_filter_b_value),
|
|
||||||
max = 255,
|
|
||||||
value = colorFilterValue.blue,
|
value = colorFilterValue.blue,
|
||||||
valueText = colorFilterValue.blue.toString(),
|
valueRange = 0..255,
|
||||||
|
steps = 0,
|
||||||
|
label = stringResource(MR.strings.color_filter_b_value),
|
||||||
onChange = { newBValue ->
|
onChange = { newBValue ->
|
||||||
screenModel.preferences.colorFilterValue().getAndSet {
|
screenModel.preferences.colorFilterValue().getAndSet {
|
||||||
getColorValue(it, newBValue, BLUE_MASK, 0)
|
getColorValue(it, newBValue, BLUE_MASK, 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(MR.strings.color_filter_a_value),
|
|
||||||
max = 255,
|
|
||||||
value = colorFilterValue.alpha,
|
value = colorFilterValue.alpha,
|
||||||
valueText = colorFilterValue.alpha.toString(),
|
valueRange = 0..255,
|
||||||
|
steps = 0,
|
||||||
|
label = stringResource(MR.strings.color_filter_a_value),
|
||||||
onChange = { newAValue ->
|
onChange = { newAValue ->
|
||||||
screenModel.preferences.colorFilterValue().getAndSet {
|
screenModel.preferences.colorFilterValue().getAndSet {
|
||||||
getColorValue(it, newAValue, ALPHA_MASK, 24)
|
getColorValue(it, newAValue, ALPHA_MASK, 24)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
|
|
||||||
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
|
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -119,21 +120,21 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
if (flashPageState) {
|
if (flashPageState) {
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||||
|
valueRange = 1..15,
|
||||||
label = stringResource(MR.strings.pref_flash_duration),
|
label = stringResource(MR.strings.pref_flash_duration),
|
||||||
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||||
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
|
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
|
||||||
min = 1,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
max = 15,
|
|
||||||
)
|
)
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = flashInterval,
|
value = flashInterval,
|
||||||
|
valueRange = 1..10,
|
||||||
label = stringResource(MR.strings.pref_flash_page_interval),
|
label = stringResource(MR.strings.pref_flash_page_interval),
|
||||||
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||||
onChange = {
|
onChange = {
|
||||||
flashIntervalPref.set(it)
|
flashIntervalPref.set(it)
|
||||||
},
|
},
|
||||||
min = 1,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
max = 10,
|
|
||||||
)
|
)
|
||||||
SettingsChipRow(MR.strings.pref_flash_with) {
|
SettingsChipRow(MR.strings.pref_flash_with) {
|
||||||
flashColors.map { (labelRes, value) ->
|
flashColors.map { (labelRes, value) ->
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -192,14 +193,14 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
|
|||||||
|
|
||||||
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(MR.strings.pref_webtoon_side_padding),
|
|
||||||
min = ReaderPreferences.WEBTOON_PADDING_MIN,
|
|
||||||
max = ReaderPreferences.WEBTOON_PADDING_MAX,
|
|
||||||
value = webtoonSidePadding,
|
value = webtoonSidePadding,
|
||||||
|
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
|
||||||
|
label = stringResource(MR.strings.pref_webtoon_side_padding),
|
||||||
valueText = numberFormat.format(webtoonSidePadding / 100f),
|
valueText = numberFormat.format(webtoonSidePadding / 100f),
|
||||||
onChange = {
|
onChange = {
|
||||||
screenModel.preferences.webtoonSidePadding().set(it)
|
screenModel.preferences.webtoonSidePadding().set(it)
|
||||||
},
|
},
|
||||||
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import eu.kanade.presentation.theme.colorscheme.GreenAppleColorScheme
|
|||||||
import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme
|
import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme
|
||||||
import eu.kanade.presentation.theme.colorscheme.MidnightDuskColorScheme
|
import eu.kanade.presentation.theme.colorscheme.MidnightDuskColorScheme
|
||||||
import eu.kanade.presentation.theme.colorscheme.MonetColorScheme
|
import eu.kanade.presentation.theme.colorscheme.MonetColorScheme
|
||||||
|
import eu.kanade.presentation.theme.colorscheme.MonochromeColorScheme
|
||||||
import eu.kanade.presentation.theme.colorscheme.NordColorScheme
|
import eu.kanade.presentation.theme.colorscheme.NordColorScheme
|
||||||
import eu.kanade.presentation.theme.colorscheme.StrawberryColorScheme
|
import eu.kanade.presentation.theme.colorscheme.StrawberryColorScheme
|
||||||
import eu.kanade.presentation.theme.colorscheme.TachiyomiColorScheme
|
import eu.kanade.presentation.theme.colorscheme.TachiyomiColorScheme
|
||||||
@@ -79,6 +80,7 @@ private val colorSchemes: Map<AppTheme, BaseColorScheme> = mapOf(
|
|||||||
AppTheme.GREEN_APPLE to GreenAppleColorScheme,
|
AppTheme.GREEN_APPLE to GreenAppleColorScheme,
|
||||||
AppTheme.LAVENDER to LavenderColorScheme,
|
AppTheme.LAVENDER to LavenderColorScheme,
|
||||||
AppTheme.MIDNIGHT_DUSK to MidnightDuskColorScheme,
|
AppTheme.MIDNIGHT_DUSK to MidnightDuskColorScheme,
|
||||||
|
AppTheme.MONOCHROME to MonochromeColorScheme,
|
||||||
AppTheme.NORD to NordColorScheme,
|
AppTheme.NORD to NordColorScheme,
|
||||||
AppTheme.STRAWBERRY_DAIQUIRI to StrawberryColorScheme,
|
AppTheme.STRAWBERRY_DAIQUIRI to StrawberryColorScheme,
|
||||||
AppTheme.TAKO to TakoColorScheme,
|
AppTheme.TAKO to TakoColorScheme,
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package eu.kanade.presentation.theme.colorscheme
|
||||||
|
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
internal object MonochromeColorScheme : BaseColorScheme() {
|
||||||
|
|
||||||
|
override val darkScheme = darkColorScheme(
|
||||||
|
primary = Color(0xFFFFFFFF),
|
||||||
|
onPrimary = Color(0xFF000000),
|
||||||
|
primaryContainer = Color(0xFFFFFFFF),
|
||||||
|
onPrimaryContainer = Color(0xFF000000),
|
||||||
|
secondary = Color(0xFFFFFFFF),
|
||||||
|
onSecondary = Color(0xFF000000),
|
||||||
|
secondaryContainer = Color(0xFF777777),
|
||||||
|
onSecondaryContainer = Color(0xFF000000),
|
||||||
|
tertiary = Color(0xFF777777),
|
||||||
|
onTertiary = Color(0xFFFFFFFF),
|
||||||
|
tertiaryContainer = Color(0xFFFFFFFF),
|
||||||
|
onTertiaryContainer = Color(0xFF000000),
|
||||||
|
error = Color(0xFFFFFFFF),
|
||||||
|
onError = Color(0xFF000000),
|
||||||
|
errorContainer = Color(0xFFFFFFFF),
|
||||||
|
onErrorContainer = Color(0xFF000000),
|
||||||
|
background = Color(0xFF000000),
|
||||||
|
onBackground = Color(0xFFFFFFFF),
|
||||||
|
surface = Color(0xFF000000),
|
||||||
|
onSurface = Color(0xFFFFFFFF),
|
||||||
|
surfaceVariant = Color(0xFF000000),
|
||||||
|
onSurfaceVariant = Color(0xFFFFFFFF),
|
||||||
|
outline = Color(0xFFFFFFFF),
|
||||||
|
outlineVariant = Color(0xFFFFFFFF),
|
||||||
|
scrim = Color(0xFF000000),
|
||||||
|
inverseSurface = Color(0xFFFFFFFF),
|
||||||
|
inverseOnSurface = Color(0xFF000000),
|
||||||
|
inversePrimary = Color(0xFF000000),
|
||||||
|
surfaceDim = Color(0xFF000000),
|
||||||
|
surfaceBright = Color(0xFFFFFFFF),
|
||||||
|
surfaceContainerLowest = Color(0xFF000000),
|
||||||
|
surfaceContainerLow = Color(0xFF000000),
|
||||||
|
surfaceContainer = Color(0xFF000000),
|
||||||
|
surfaceContainerHigh = Color(0xFF000000),
|
||||||
|
surfaceContainerHighest = Color(0xFF000000),
|
||||||
|
)
|
||||||
|
|
||||||
|
override val lightScheme = lightColorScheme(
|
||||||
|
primary = Color(0xFF000000),
|
||||||
|
onPrimary = Color(0xFFFFFFFF),
|
||||||
|
primaryContainer = Color(0xFF000000),
|
||||||
|
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||||
|
secondary = Color(0xFF000000),
|
||||||
|
onSecondary = Color(0xFFFFFFFF),
|
||||||
|
secondaryContainer = Color(0xFF888888),
|
||||||
|
onSecondaryContainer = Color(0xFFFFFFFF),
|
||||||
|
tertiary = Color(0xFF888888),
|
||||||
|
onTertiary = Color(0xFFFFFFFF),
|
||||||
|
tertiaryContainer = Color(0xFF000000),
|
||||||
|
onTertiaryContainer = Color(0xFFFFFFFF),
|
||||||
|
error = Color(0xFF000000),
|
||||||
|
onError = Color(0xFFFFFFFF),
|
||||||
|
errorContainer = Color(0xFF000000),
|
||||||
|
onErrorContainer = Color(0xFFFFFFFF),
|
||||||
|
background = Color(0xFFFFFFFF),
|
||||||
|
onBackground = Color(0xFF000000),
|
||||||
|
surface = Color(0xFFFFFFFF),
|
||||||
|
onSurface = Color(0xFF000000),
|
||||||
|
surfaceVariant = Color(0xFFFFFFFF),
|
||||||
|
onSurfaceVariant = Color(0xFF000000),
|
||||||
|
outline = Color(0xFF000000),
|
||||||
|
outlineVariant = Color(0xFF000000),
|
||||||
|
scrim = Color(0xFF000000),
|
||||||
|
inverseSurface = Color(0xFF000000),
|
||||||
|
inverseOnSurface = Color(0xFFFFFFFF),
|
||||||
|
inversePrimary = Color(0xFFFFFFFF),
|
||||||
|
surfaceDim = Color(0xFFFFFFFF),
|
||||||
|
surfaceBright = Color(0xFFFFFFFF),
|
||||||
|
surfaceContainerLowest = Color(0xFFFFFFFF),
|
||||||
|
surfaceContainerLow = Color(0xFFFFFFFF),
|
||||||
|
surfaceContainer = Color(0xFFFFFFFF),
|
||||||
|
surfaceContainerHigh = Color(0xFFFFFFFF),
|
||||||
|
surfaceContainerHighest = Color(0xFFFFFFFF),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,10 +10,12 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.absoluteOffset
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
@@ -22,6 +24,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
|
import androidx.compose.material3.Badge
|
||||||
|
import androidx.compose.material3.BadgedBox
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -70,6 +75,7 @@ fun TrackInfoDialogHome(
|
|||||||
onOpenInBrowser: (TrackItem) -> Unit,
|
onOpenInBrowser: (TrackItem) -> Unit,
|
||||||
onRemoved: (TrackItem) -> Unit,
|
onRemoved: (TrackItem) -> Unit,
|
||||||
onCopyLink: (TrackItem) -> Unit,
|
onCopyLink: (TrackItem) -> Unit,
|
||||||
|
onTogglePrivate: (TrackItem) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -84,6 +90,7 @@ fun TrackInfoDialogHome(
|
|||||||
if (item.track != null) {
|
if (item.track != null) {
|
||||||
val supportsScoring = item.tracker.getScoreList().isNotEmpty()
|
val supportsScoring = item.tracker.getScoreList().isNotEmpty()
|
||||||
val supportsReadingDates = item.tracker.supportsReadingDates
|
val supportsReadingDates = item.tracker.supportsReadingDates
|
||||||
|
val supportsPrivate = item.tracker.supportsPrivateTracking
|
||||||
TrackInfoItem(
|
TrackInfoItem(
|
||||||
title = item.track.title,
|
title = item.track.title,
|
||||||
tracker = item.tracker,
|
tracker = item.tracker,
|
||||||
@@ -115,6 +122,9 @@ fun TrackInfoDialogHome(
|
|||||||
onOpenInBrowser = { onOpenInBrowser(item) },
|
onOpenInBrowser = { onOpenInBrowser(item) },
|
||||||
onRemoved = { onRemoved(item) },
|
onRemoved = { onRemoved(item) },
|
||||||
onCopyLink = { onCopyLink(item) },
|
onCopyLink = { onCopyLink(item) },
|
||||||
|
private = item.track.private,
|
||||||
|
onTogglePrivate = { onTogglePrivate(item) }
|
||||||
|
.takeIf { supportsPrivate },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TrackInfoItemEmpty(
|
TrackInfoItemEmpty(
|
||||||
@@ -144,17 +154,37 @@ private fun TrackInfoItem(
|
|||||||
onOpenInBrowser: () -> Unit,
|
onOpenInBrowser: () -> Unit,
|
||||||
onRemoved: () -> Unit,
|
onRemoved: () -> Unit,
|
||||||
onCopyLink: () -> Unit,
|
onCopyLink: () -> Unit,
|
||||||
|
private: Boolean,
|
||||||
|
onTogglePrivate: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Column {
|
Column {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
TrackLogoIcon(
|
BadgedBox(
|
||||||
tracker = tracker,
|
badge = {
|
||||||
onClick = onOpenInBrowser,
|
if (private) {
|
||||||
onLongClick = onCopyLink,
|
Badge(
|
||||||
)
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
modifier = Modifier.absoluteOffset(x = (-5).dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.VisibilityOff,
|
||||||
|
contentDescription = stringResource(MR.strings.tracked_privately),
|
||||||
|
modifier = Modifier.size(14.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
TrackLogoIcon(
|
||||||
|
tracker = tracker,
|
||||||
|
onClick = onOpenInBrowser,
|
||||||
|
onLongClick = onCopyLink,
|
||||||
|
)
|
||||||
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(48.dp)
|
.height(48.dp)
|
||||||
@@ -181,6 +211,8 @@ private fun TrackInfoItem(
|
|||||||
onOpenInBrowser = onOpenInBrowser,
|
onOpenInBrowser = onOpenInBrowser,
|
||||||
onRemoved = onRemoved,
|
onRemoved = onRemoved,
|
||||||
onCopyLink = onCopyLink,
|
onCopyLink = onCopyLink,
|
||||||
|
private = private,
|
||||||
|
onTogglePrivate = onTogglePrivate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,6 +323,8 @@ private fun TrackInfoItemMenu(
|
|||||||
onOpenInBrowser: () -> Unit,
|
onOpenInBrowser: () -> Unit,
|
||||||
onRemoved: () -> Unit,
|
onRemoved: () -> Unit,
|
||||||
onCopyLink: () -> Unit,
|
onCopyLink: () -> Unit,
|
||||||
|
private: Boolean,
|
||||||
|
onTogglePrivate: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) {
|
Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) {
|
||||||
@@ -318,6 +352,25 @@ private fun TrackInfoItemMenu(
|
|||||||
expanded = false
|
expanded = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if (onTogglePrivate != null) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
if (private) {
|
||||||
|
MR.strings.action_toggle_private_off
|
||||||
|
} else {
|
||||||
|
MR.strings.action_toggle_private_on
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onTogglePrivate()
|
||||||
|
expanded = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(MR.strings.action_remove)) },
|
text = { Text(stringResource(MR.strings.action_remove)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ internal class TrackInfoDialogHomePreviewProvider :
|
|||||||
remoteUrl = "https://example.com",
|
remoteUrl = "https://example.com",
|
||||||
startDate = 0L,
|
startDate = 0L,
|
||||||
finishDate = 0L,
|
finishDate = 0L,
|
||||||
|
private = false,
|
||||||
)
|
)
|
||||||
|
private val privateTrack = aTrack.copy(private = true)
|
||||||
private val trackItemWithoutTrack = TrackItem(
|
private val trackItemWithoutTrack = TrackItem(
|
||||||
track = null,
|
track = null,
|
||||||
tracker = DummyTracker(
|
tracker = DummyTracker(
|
||||||
@@ -40,6 +42,13 @@ internal class TrackInfoDialogHomePreviewProvider :
|
|||||||
name = "Example Tracker 2",
|
name = "Example Tracker 2",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
private val trackItemWithPrivateTrack = TrackItem(
|
||||||
|
track = privateTrack,
|
||||||
|
tracker = DummyTracker(
|
||||||
|
id = 2L,
|
||||||
|
name = "Example Tracker 2",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
private val trackersWithAndWithoutTrack = @Composable {
|
private val trackersWithAndWithoutTrack = @Composable {
|
||||||
TrackInfoDialogHome(
|
TrackInfoDialogHome(
|
||||||
@@ -57,6 +66,7 @@ internal class TrackInfoDialogHomePreviewProvider :
|
|||||||
onOpenInBrowser = {},
|
onOpenInBrowser = {},
|
||||||
onRemoved = {},
|
onRemoved = {},
|
||||||
onCopyLink = {},
|
onCopyLink = {},
|
||||||
|
onTogglePrivate = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +83,24 @@ internal class TrackInfoDialogHomePreviewProvider :
|
|||||||
onOpenInBrowser = {},
|
onOpenInBrowser = {},
|
||||||
onRemoved = {},
|
onRemoved = {},
|
||||||
onCopyLink = {},
|
onCopyLink = {},
|
||||||
|
onTogglePrivate = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val trackerWithPrivateTracking = @Composable {
|
||||||
|
TrackInfoDialogHome(
|
||||||
|
trackItems = listOf(trackItemWithPrivateTrack),
|
||||||
|
dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
|
||||||
|
onStatusClick = {},
|
||||||
|
onChapterClick = {},
|
||||||
|
onScoreClick = {},
|
||||||
|
onStartDateEdit = {},
|
||||||
|
onEndDateEdit = {},
|
||||||
|
onNewSearch = {},
|
||||||
|
onOpenInBrowser = {},
|
||||||
|
onRemoved = {},
|
||||||
|
onCopyLink = {},
|
||||||
|
onTogglePrivate = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,5 +108,6 @@ internal class TrackInfoDialogHomePreviewProvider :
|
|||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
trackersWithAndWithoutTrack,
|
trackersWithAndWithoutTrack,
|
||||||
noTrackers,
|
noTrackers,
|
||||||
|
trackerWithPrivateTracking,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@@ -90,8 +91,9 @@ fun TrackerSearch(
|
|||||||
queryResult: Result<List<TrackSearch>>?,
|
queryResult: Result<List<TrackSearch>>?,
|
||||||
selected: TrackSearch?,
|
selected: TrackSearch?,
|
||||||
onSelectedChange: (TrackSearch) -> Unit,
|
onSelectedChange: (TrackSearch) -> Unit,
|
||||||
onConfirmSelection: () -> Unit,
|
onConfirmSelection: (private: Boolean) -> Unit,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
|
supportsPrivateTracking: Boolean,
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
@@ -164,15 +166,31 @@ fun TrackerSearch(
|
|||||||
enter = fadeIn() + slideInVertically { it / 2 },
|
enter = fadeIn() + slideInVertically { it / 2 },
|
||||||
exit = slideOutVertically { it / 2 } + fadeOut(),
|
exit = slideOutVertically { it / 2 } + fadeOut(),
|
||||||
) {
|
) {
|
||||||
Button(
|
Row(
|
||||||
onClick = { onConfirmSelection() },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(12.dp)
|
.padding(MaterialTheme.padding.small)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBars)
|
.windowInsetsPadding(WindowInsets.navigationBars)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
elevation = ButtonDefaults.elevatedButtonElevation(),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(MR.strings.action_track))
|
Button(
|
||||||
|
onClick = { onConfirmSelection(false) },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
elevation = ButtonDefaults.elevatedButtonElevation(),
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.action_track))
|
||||||
|
}
|
||||||
|
if (supportsPrivateTracking) {
|
||||||
|
Button(
|
||||||
|
onClick = { onConfirmSelection(true) },
|
||||||
|
elevation = ButtonDefaults.elevatedButtonElevation(),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.VisibilityOff,
|
||||||
|
contentDescription = stringResource(MR.strings.action_toggle_private_on),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -286,6 +304,15 @@ private fun SearchResultItem(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if (trackSearch.authors.isNotEmpty() || trackSearch.artists.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = (trackSearch.authors + trackSearch.artists).distinct().joinToString(),
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (type.isNotBlank()) {
|
if (type.isNotBlank()) {
|
||||||
SearchResultItemDetails(
|
SearchResultItemDetails(
|
||||||
title = stringResource(MR.strings.track_type),
|
title = stringResource(MR.strings.track_type),
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
||||||
@@ -20,6 +23,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
onSelectedChange = {},
|
onSelectedChange = {},
|
||||||
onConfirmSelection = {},
|
onConfirmSelection = {},
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
|
supportsPrivateTracking = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private val fullPageWithoutSelected = @Composable {
|
private val fullPageWithoutSelected = @Composable {
|
||||||
@@ -31,6 +35,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
onSelectedChange = {},
|
onSelectedChange = {},
|
||||||
onConfirmSelection = {},
|
onConfirmSelection = {},
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
|
supportsPrivateTracking = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private val loading = @Composable {
|
private val loading = @Composable {
|
||||||
@@ -42,12 +47,27 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
onSelectedChange = {},
|
onSelectedChange = {},
|
||||||
onConfirmSelection = {},
|
onConfirmSelection = {},
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
|
supportsPrivateTracking = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val fullPageWithPrivateTracking = @Composable {
|
||||||
|
val items = someTrackSearches().take(30).toList()
|
||||||
|
TrackerSearch(
|
||||||
|
state = TextFieldState(initialText = "search text"),
|
||||||
|
onDispatchQuery = {},
|
||||||
|
queryResult = Result.success(items),
|
||||||
|
selected = items[1],
|
||||||
|
onSelectedChange = {},
|
||||||
|
onConfirmSelection = {},
|
||||||
|
onDismissRequest = {},
|
||||||
|
supportsPrivateTracking = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override val values: Sequence<@Composable () -> Unit> = sequenceOf(
|
override val values: Sequence<@Composable () -> Unit> = sequenceOf(
|
||||||
fullPageWithSecondSelected,
|
fullPageWithSecondSelected,
|
||||||
fullPageWithoutSelected,
|
fullPageWithoutSelected,
|
||||||
loading,
|
loading,
|
||||||
|
fullPageWithPrivateTracking,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun someTrackSearches(): Sequence<TrackSearch> = sequence {
|
private fun someTrackSearches(): Sequence<TrackSearch> = sequence {
|
||||||
@@ -56,6 +76,8 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val formatter: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||||
|
|
||||||
private fun randTrackSearch() = TrackSearch().let {
|
private fun randTrackSearch() = TrackSearch().let {
|
||||||
it.id = Random.nextLong()
|
it.id = Random.nextLong()
|
||||||
it.manga_id = Random.nextLong()
|
it.manga_id = Random.nextLong()
|
||||||
@@ -71,11 +93,17 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
it.finished_reading_date = 0L
|
it.finished_reading_date = 0L
|
||||||
it.tracking_url = "https://example.com/tracker-example"
|
it.tracking_url = "https://example.com/tracker-example"
|
||||||
it.cover_url = "https://example.com/cover.png"
|
it.cover_url = "https://example.com/cover.png"
|
||||||
it.start_date = Instant.now().minus((1L..365).random(), ChronoUnit.DAYS).toString()
|
it.start_date = formatter.format(Date.from(Instant.now().minus((1L..365).random(), ChronoUnit.DAYS)))
|
||||||
it.summary = lorem((0..40).random()).joinToString()
|
it.summary = lorem((0..40).random()).joinToString()
|
||||||
|
it.publishing_status = if (Random.nextBoolean()) "Finished" else ""
|
||||||
|
it.publishing_type = if (Random.nextBoolean()) "Oneshot" else ""
|
||||||
|
it.artists = randomNames()
|
||||||
|
it.authors = randomNames()
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun randomNames(): List<String> = (0..(0..3).random()).map { lorem((3..5).random()).joinToString() }
|
||||||
|
|
||||||
private fun lorem(words: Int): Sequence<String> =
|
private fun lorem(words: Int): Sequence<String> =
|
||||||
LoremIpsum(words).values
|
LoremIpsum(words).values
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package eu.kanade.presentation.webview
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.webkit.WebResourceRequest
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebResourceResponse
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -26,6 +27,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.kevinnzou.web.AccompanistWebViewClient
|
import com.kevinnzou.web.AccompanistWebViewClient
|
||||||
@@ -37,13 +39,18 @@ import eu.kanade.presentation.components.AppBar
|
|||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.getHtml
|
import eu.kanade.tachiyomi.util.system.getHtml
|
||||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.Request
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WebViewScreenContent(
|
fun WebViewScreenContent(
|
||||||
@@ -58,8 +65,11 @@ fun WebViewScreenContent(
|
|||||||
) {
|
) {
|
||||||
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
||||||
val navigator = rememberWebViewNavigator()
|
val navigator = rememberWebViewNavigator()
|
||||||
|
val context = LocalContext.current
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val network = remember { Injekt.get<NetworkHelper>() }
|
||||||
|
val spoofedPackageName = remember { WebViewUtil.spoofedPackageName(context) }
|
||||||
|
|
||||||
var currentUrl by remember { mutableStateOf(url) }
|
var currentUrl by remember { mutableStateOf(url) }
|
||||||
var showCloudflareHelp by remember { mutableStateOf(false) }
|
var showCloudflareHelp by remember { mutableStateOf(false) }
|
||||||
@@ -114,6 +124,40 @@ fun WebViewScreenContent(
|
|||||||
}
|
}
|
||||||
return super.shouldOverrideUrlLoading(view, request)
|
return super.shouldOverrideUrlLoading(view, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun shouldInterceptRequest(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?,
|
||||||
|
): WebResourceResponse? {
|
||||||
|
return try {
|
||||||
|
val internalRequest = Request.Builder().apply {
|
||||||
|
url(request!!.url.toString())
|
||||||
|
request.requestHeaders.forEach { (key, value) ->
|
||||||
|
if (key == "X-Requested-With" && value in setOf(context.packageName, spoofedPackageName)) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
addHeader(key, value)
|
||||||
|
}
|
||||||
|
method(request.method, null)
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
val response = network.nonCloudflareClient.newCall(internalRequest).execute()
|
||||||
|
|
||||||
|
val contentType = response.body.contentType()?.let { "${it.type}/${it.subtype}" } ?: "text/html"
|
||||||
|
val contentEncoding = response.body.contentType()?.charset()?.name() ?: "utf-8"
|
||||||
|
|
||||||
|
WebResourceResponse(
|
||||||
|
contentType,
|
||||||
|
contentEncoding,
|
||||||
|
response.code,
|
||||||
|
response.message,
|
||||||
|
response.headers.associate { it.first to it.second },
|
||||||
|
response.body.byteStream(),
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
super.shouldInterceptRequest(view, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -262,18 +277,16 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
try {
|
try {
|
||||||
// Override the value passed as X-Requested-With in WebView requests
|
// Override the value passed as X-Requested-With in WebView requests
|
||||||
val stackTrace = Looper.getMainLooper().thread.stackTrace
|
val stackTrace = Looper.getMainLooper().thread.stackTrace
|
||||||
val chromiumElement = stackTrace.find {
|
val isChromiumCall = stackTrace.any { trace ->
|
||||||
it.className.equals(
|
trace.className.equals("org.chromium.base.BuildInfo", ignoreCase = true) &&
|
||||||
"org.chromium.base.BuildInfo",
|
setOf("getAll", "getPackageName", "<init>").any { trace.methodName.equals(it, ignoreCase = true) }
|
||||||
ignoreCase = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (chromiumElement?.methodName.equals("getAll", ignoreCase = true)) {
|
|
||||||
return WebViewUtil.SPOOF_PACKAGE_NAME
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isChromiumCall) return WebViewUtil.spoofedPackageName(applicationContext)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getPackageName()
|
return super.getPackageName()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,12 +296,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
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import tachiyomi.domain.category.model.Category
|
|||||||
class BackupCategory(
|
class BackupCategory(
|
||||||
@ProtoNumber(1) var name: String,
|
@ProtoNumber(1) var name: String,
|
||||||
@ProtoNumber(2) var order: Long = 0,
|
@ProtoNumber(2) var order: Long = 0,
|
||||||
|
@ProtoNumber(3) var id: Long = 0,
|
||||||
// @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x
|
// @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x
|
||||||
@ProtoNumber(100) var flags: Long = 0,
|
@ProtoNumber(100) var flags: Long = 0,
|
||||||
// SY specific values
|
// SY specific values
|
||||||
@@ -24,6 +25,7 @@ class BackupCategory(
|
|||||||
|
|
||||||
val backupCategoryMapper = { category: Category ->
|
val backupCategoryMapper = { category: Category ->
|
||||||
BackupCategory(
|
BackupCategory(
|
||||||
|
id = category.id,
|
||||||
name = category.name,
|
name = category.name,
|
||||||
order = category.order,
|
order = category.order,
|
||||||
flags = category.flags,
|
flags = category.flags,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ data class BackupTracking(
|
|||||||
@ProtoNumber(10) var startedReadingDate: Long = 0,
|
@ProtoNumber(10) var startedReadingDate: Long = 0,
|
||||||
// finishedReadingDate is called endReadTime in 1.x
|
// finishedReadingDate is called endReadTime in 1.x
|
||||||
@ProtoNumber(11) var finishedReadingDate: Long = 0,
|
@ProtoNumber(11) var finishedReadingDate: Long = 0,
|
||||||
|
@ProtoNumber(12) var private: Boolean = false,
|
||||||
@ProtoNumber(100) var mediaId: Long = 0,
|
@ProtoNumber(100) var mediaId: Long = 0,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ data class BackupTracking(
|
|||||||
startDate = this@BackupTracking.startedReadingDate,
|
startDate = this@BackupTracking.startedReadingDate,
|
||||||
finishDate = this@BackupTracking.finishedReadingDate,
|
finishDate = this@BackupTracking.finishedReadingDate,
|
||||||
remoteUrl = this@BackupTracking.trackingUrl,
|
remoteUrl = this@BackupTracking.trackingUrl,
|
||||||
|
private = this@BackupTracking.private,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +68,7 @@ val backupTrackMapper = {
|
|||||||
remoteUrl: String,
|
remoteUrl: String,
|
||||||
startDate: Long,
|
startDate: Long,
|
||||||
finishDate: Long,
|
finishDate: Long,
|
||||||
|
private: Boolean,
|
||||||
->
|
->
|
||||||
BackupTracking(
|
BackupTracking(
|
||||||
syncId = syncId.toInt(),
|
syncId = syncId.toInt(),
|
||||||
@@ -80,5 +83,6 @@ val backupTrackMapper = {
|
|||||||
startedReadingDate = startDate,
|
startedReadingDate = startDate,
|
||||||
finishedReadingDate = finishDate,
|
finishedReadingDate = finishDate,
|
||||||
trackingUrl = remoteUrl,
|
trackingUrl = remoteUrl,
|
||||||
|
private = private,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class BackupRestorer(
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
if (options.appSettings) {
|
if (options.appSettings) {
|
||||||
restoreAppPreferences(backup.backupPreferences)
|
restoreAppPreferences(backup.backupPreferences, backup.backupCategories.takeIf { options.categories })
|
||||||
}
|
}
|
||||||
if (options.sourceSettings) {
|
if (options.sourceSettings) {
|
||||||
restoreSourcePreferences(backup.backupSourcePreferences)
|
restoreSourcePreferences(backup.backupSourcePreferences)
|
||||||
@@ -173,9 +173,15 @@ class BackupRestorer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.restoreAppPreferences(preferences: List<BackupPreference>) = launch {
|
private fun CoroutineScope.restoreAppPreferences(
|
||||||
|
preferences: List<BackupPreference>,
|
||||||
|
categories: List<BackupCategory>?,
|
||||||
|
) = launch {
|
||||||
ensureActive()
|
ensureActive()
|
||||||
preferenceRestorer.restoreApp(preferences)
|
preferenceRestorer.restoreApp(
|
||||||
|
preferences,
|
||||||
|
categories,
|
||||||
|
)
|
||||||
|
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
notifier.showRestoreProgress(
|
notifier.showRestoreProgress(
|
||||||
|
|||||||
+3
-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,
|
||||||
@@ -446,6 +447,7 @@ class MangaRestorer(
|
|||||||
track.remoteUrl,
|
track.remoteUrl,
|
||||||
track.startDate,
|
track.startDate,
|
||||||
track.finishDate,
|
track.finishDate,
|
||||||
|
track.private,
|
||||||
track.id,
|
track.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+92
-34
@@ -1,7 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.restore.restorers
|
package eu.kanade.tachiyomi.data.backup.restore.restorers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||||
|
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
|
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
|
import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
|
||||||
@@ -14,66 +16,122 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
|||||||
import eu.kanade.tachiyomi.source.sourcePreferences
|
import eu.kanade.tachiyomi.source.sourcePreferences
|
||||||
import tachiyomi.core.common.preference.AndroidPreferenceStore
|
import tachiyomi.core.common.preference.AndroidPreferenceStore
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.common.preference.plusAssign
|
||||||
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class PreferenceRestorer(
|
class PreferenceRestorer(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
private val getCategories: GetCategories = Injekt.get(),
|
||||||
private val preferenceStore: PreferenceStore = Injekt.get(),
|
private val preferenceStore: PreferenceStore = Injekt.get(),
|
||||||
) {
|
) {
|
||||||
|
suspend fun restoreApp(
|
||||||
fun restoreApp(preferences: List<BackupPreference>) {
|
preferences: List<BackupPreference>,
|
||||||
restorePreferences(preferences, preferenceStore)
|
backupCategories: List<BackupCategory>?,
|
||||||
|
) {
|
||||||
|
restorePreferences(
|
||||||
|
preferences,
|
||||||
|
preferenceStore,
|
||||||
|
backupCategories,
|
||||||
|
)
|
||||||
|
|
||||||
LibraryUpdateJob.setupTask(context)
|
LibraryUpdateJob.setupTask(context)
|
||||||
BackupCreateJob.setupTask(context)
|
BackupCreateJob.setupTask(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreSource(preferences: List<BackupSourcePreferences>) {
|
suspend fun restoreSource(preferences: List<BackupSourcePreferences>) {
|
||||||
preferences.forEach {
|
preferences.forEach {
|
||||||
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
|
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
|
||||||
restorePreferences(it.prefs, sourcePrefs)
|
restorePreferences(it.prefs, sourcePrefs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restorePreferences(
|
private suspend fun restorePreferences(
|
||||||
toRestore: List<BackupPreference>,
|
toRestore: List<BackupPreference>,
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
|
backupCategories: List<BackupCategory>? = null,
|
||||||
) {
|
) {
|
||||||
|
val allCategories = if (backupCategories != null) getCategories.await() else emptyList()
|
||||||
|
val categoriesByName = allCategories.associateBy { it.name }
|
||||||
|
val backupCategoriesById = backupCategories?.associateBy { it.id.toString() }.orEmpty()
|
||||||
val prefs = preferenceStore.getAll()
|
val prefs = preferenceStore.getAll()
|
||||||
toRestore.forEach { (key, value) ->
|
toRestore.forEach { (key, value) ->
|
||||||
when (value) {
|
try {
|
||||||
is IntPreferenceValue -> {
|
when (value) {
|
||||||
if (prefs[key] is Int?) {
|
is IntPreferenceValue -> {
|
||||||
preferenceStore.getInt(key).set(value.value)
|
if (prefs[key] is Int?) {
|
||||||
}
|
val newValue = if (key == LibraryPreferences.DEFAULT_CATEGORY_PREF_KEY) {
|
||||||
}
|
backupCategoriesById[value.value.toString()]
|
||||||
is LongPreferenceValue -> {
|
?.let { categoriesByName[it.name]?.id?.toInt() }
|
||||||
if (prefs[key] is Long?) {
|
} else {
|
||||||
preferenceStore.getLong(key).set(value.value)
|
value.value
|
||||||
}
|
}
|
||||||
}
|
|
||||||
is FloatPreferenceValue -> {
|
newValue?.let { preferenceStore.getInt(key).set(it) }
|
||||||
if (prefs[key] is Float?) {
|
}
|
||||||
preferenceStore.getFloat(key).set(value.value)
|
}
|
||||||
}
|
is LongPreferenceValue -> {
|
||||||
}
|
if (prefs[key] is Long?) {
|
||||||
is StringPreferenceValue -> {
|
preferenceStore.getLong(key).set(value.value)
|
||||||
if (prefs[key] is String?) {
|
}
|
||||||
preferenceStore.getString(key).set(value.value)
|
}
|
||||||
}
|
is FloatPreferenceValue -> {
|
||||||
}
|
if (prefs[key] is Float?) {
|
||||||
is BooleanPreferenceValue -> {
|
preferenceStore.getFloat(key).set(value.value)
|
||||||
if (prefs[key] is Boolean?) {
|
}
|
||||||
preferenceStore.getBoolean(key).set(value.value)
|
}
|
||||||
}
|
is StringPreferenceValue -> {
|
||||||
}
|
if (prefs[key] is String?) {
|
||||||
is StringSetPreferenceValue -> {
|
preferenceStore.getString(key).set(value.value)
|
||||||
if (prefs[key] is Set<*>?) {
|
}
|
||||||
preferenceStore.getStringSet(key).set(value.value)
|
}
|
||||||
|
is BooleanPreferenceValue -> {
|
||||||
|
if (prefs[key] is Boolean?) {
|
||||||
|
preferenceStore.getBoolean(key).set(value.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is StringSetPreferenceValue -> {
|
||||||
|
if (prefs[key] is Set<*>?) {
|
||||||
|
val restored = restoreCategoriesPreference(
|
||||||
|
key,
|
||||||
|
value.value,
|
||||||
|
preferenceStore,
|
||||||
|
backupCategoriesById,
|
||||||
|
categoriesByName,
|
||||||
|
)
|
||||||
|
if (!restored) preferenceStore.getStringSet(key).set(value.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PreferenceRestorer", "Failed to restore preference <$key>", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun restoreCategoriesPreference(
|
||||||
|
key: String,
|
||||||
|
value: Set<String>,
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
|
backupCategoriesById: Map<String, BackupCategory>,
|
||||||
|
categoriesByName: Map<String, Category>,
|
||||||
|
): Boolean {
|
||||||
|
val categoryPreferences = LibraryPreferences.categoryPreferenceKeys + DownloadPreferences.categoryPreferenceKeys
|
||||||
|
if (key !in categoryPreferences) return false
|
||||||
|
|
||||||
|
val ids = value.mapNotNull {
|
||||||
|
backupCategoriesById[it]?.name?.let { name ->
|
||||||
|
categoriesByName[name]?.id?.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ids.isNotEmpty()) {
|
||||||
|
preferenceStore.getStringSet(key) += ids
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -27,6 +27,9 @@ interface Chapter : SChapter, Serializable {
|
|||||||
var version: Long
|
var version: Long
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val Chapter.isRecognizedNumber: Boolean
|
||||||
|
get() = chapter_number >= 0f
|
||||||
|
|
||||||
fun Chapter.toDomainChapter(): DomainChapter? {
|
fun Chapter.toDomainChapter(): DomainChapter? {
|
||||||
if (id == null || manga_id == null) return null
|
if (id == null || manga_id == null) return null
|
||||||
return DomainChapter(
|
return DomainChapter(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -32,12 +32,15 @@ interface Track : Serializable {
|
|||||||
|
|
||||||
var tracking_url: String
|
var tracking_url: String
|
||||||
|
|
||||||
fun copyPersonalFrom(other: Track) {
|
var private: Boolean
|
||||||
|
|
||||||
|
fun copyPersonalFrom(other: Track, copyRemotePrivate: Boolean = true) {
|
||||||
last_chapter_read = other.last_chapter_read
|
last_chapter_read = other.last_chapter_read
|
||||||
score = other.score
|
score = other.score
|
||||||
status = other.status
|
status = other.status
|
||||||
started_reading_date = other.started_reading_date
|
started_reading_date = other.started_reading_date
|
||||||
finished_reading_date = other.finished_reading_date
|
finished_reading_date = other.finished_reading_date
|
||||||
|
if (copyRemotePrivate) private = other.private
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -29,4 +29,6 @@ class TrackImpl : Track {
|
|||||||
override var finished_reading_date: Long = 0
|
override var finished_reading_date: Long = 0
|
||||||
|
|
||||||
override var tracking_url: String = ""
|
override var tracking_url: String = ""
|
||||||
|
|
||||||
|
override var private: Boolean = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.download
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import androidx.core.net.toUri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
@@ -483,7 +483,7 @@ private object UniFileAsStringSerializer : KSerializer<UniFile?> {
|
|||||||
|
|
||||||
override fun deserialize(decoder: Decoder): UniFile? {
|
override fun deserialize(decoder: Decoder): UniFile? {
|
||||||
return if (decoder.decodeNotNullMark()) {
|
return if (decoder.decodeNotNullMark()) {
|
||||||
UniFile.fromUri(Injekt.get<Application>(), Uri.parse(decoder.decodeString()))
|
UniFile.fromUri(Injekt.get<Application>(), decoder.decodeString().toUri())
|
||||||
} else {
|
} else {
|
||||||
decoder.decodeNull()
|
decoder.decodeNull()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.export
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
|
object LibraryExporter {
|
||||||
|
|
||||||
|
data class ExportOptions(
|
||||||
|
val includeTitle: Boolean,
|
||||||
|
val includeAuthor: Boolean,
|
||||||
|
val includeArtist: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun exportToCsv(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
favorites: List<Manga>,
|
||||||
|
options: ExportOptions,
|
||||||
|
onExportComplete: () -> Unit,
|
||||||
|
) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||||
|
val csvData = generateCsvData(favorites, options)
|
||||||
|
outputStream.write(csvData.toByteArray())
|
||||||
|
}
|
||||||
|
onExportComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val escapeRequired = listOf("\r", "\n", "\"", ",")
|
||||||
|
|
||||||
|
private fun generateCsvData(favorites: List<Manga>, options: ExportOptions): String {
|
||||||
|
val columnSize = listOf(
|
||||||
|
options.includeTitle,
|
||||||
|
options.includeAuthor,
|
||||||
|
options.includeArtist,
|
||||||
|
)
|
||||||
|
.count { it }
|
||||||
|
|
||||||
|
val rows = buildList(favorites.size) {
|
||||||
|
favorites.forEach { manga ->
|
||||||
|
buildList(columnSize) {
|
||||||
|
if (options.includeTitle) add(manga.title)
|
||||||
|
if (options.includeAuthor) add(manga.author)
|
||||||
|
if (options.includeArtist) add(manga.artist)
|
||||||
|
}
|
||||||
|
.let(::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows.joinToString("\r\n") { columns ->
|
||||||
|
columns.joinToString(",") columns@{ column ->
|
||||||
|
if (column.isNullOrBlank()) return@columns ""
|
||||||
|
if (escapeRequired.any { column.contains(it) }) {
|
||||||
|
column.replace("\"", "\"\"").let { "\"$it\"" }
|
||||||
|
} else {
|
||||||
|
column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -397,29 +401,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val newChapters = updateManga(manga, fetchWindow)
|
val newChapters = updateManga(manga, fetchWindow)
|
||||||
// SY -->
|
.sortedByDescending { it.sourceOrder }
|
||||||
.sortedByDescending { it.sourceOrder }.run {
|
|
||||||
if (libraryPreferences.libraryReadDuplicateChapters().get()) {
|
|
||||||
val readChapters = getChaptersByMangaId.await(manga.id).filter {
|
|
||||||
it.read
|
|
||||||
}
|
|
||||||
val newReadChapters = this.filter { chapter ->
|
|
||||||
chapter.chapterNumber > 0 &&
|
|
||||||
readChapters.any {
|
|
||||||
it.chapterNumber == chapter.chapterNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newReadChapters.isNotEmpty()) {
|
|
||||||
setReadStatus.await(true, *newReadChapters.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
this.filterNot { newReadChapters.contains(it) }
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
if (newChapters.isNotEmpty()) {
|
if (newChapters.isNotEmpty()) {
|
||||||
val chaptersToDownload = filterChaptersForDownload.await(manga, newChapters)
|
val chaptersToDownload = filterChaptersForDownload.await(manga, newChapters)
|
||||||
@@ -768,15 +750,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) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class SyncNotifier(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val completeNotificationBuilder = context.notificationBuilder(
|
private val completeNotificationBuilder = context.notificationBuilder(
|
||||||
Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS,
|
Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE,
|
||||||
) {
|
) {
|
||||||
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
|||||||
@@ -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,8 @@ 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.data.track.model.TrackSearch
|
||||||
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
|
||||||
@@ -37,6 +39,8 @@ abstract class BaseTracker(
|
|||||||
// Application and remote support for reading dates
|
// Application and remote support for reading dates
|
||||||
override val supportsReadingDates: Boolean = false
|
override val supportsReadingDates: Boolean = false
|
||||||
|
|
||||||
|
override val supportsPrivateTracking: Boolean = false
|
||||||
|
|
||||||
// TODO: Store all scores as 10 point in the future maybe?
|
// TODO: Store all scores as 10 point in the future maybe?
|
||||||
override fun get10PointScore(track: DomainTrack): Double {
|
override fun get10PointScore(track: DomainTrack): Double {
|
||||||
return track.score
|
return track.score
|
||||||
@@ -120,6 +124,21 @@ abstract class BaseTracker(
|
|||||||
updateRemote(track)
|
updateRemote(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun setRemotePrivate(track: Track, private: Boolean) {
|
||||||
|
track.private = private
|
||||||
|
updateRemote(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
throw NotImplementedError("Not implemented.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun searchById(id: String): TrackSearch? {
|
||||||
|
throw NotImplementedError("Not implemented.")
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
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
|
||||||
@@ -22,6 +23,8 @@ interface Tracker {
|
|||||||
// Application and remote support for reading dates
|
// Application and remote support for reading dates
|
||||||
val supportsReadingDates: Boolean
|
val supportsReadingDates: Boolean
|
||||||
|
|
||||||
|
val supportsPrivateTracking: Boolean
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
fun getLogoColor(): Int
|
fun getLogoColor(): Int
|
||||||
|
|
||||||
@@ -82,4 +85,12 @@ 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 setRemotePrivate(track: Track, private: Boolean)
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata?
|
||||||
|
|
||||||
|
suspend fun searchById(id: String): TrackSearch?
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ 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
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@@ -43,6 +43,8 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
|
|
||||||
override val supportsReadingDates: Boolean = true
|
override val supportsReadingDates: Boolean = true
|
||||||
|
|
||||||
|
override val supportsPrivateTracking: Boolean = true
|
||||||
|
|
||||||
private val scorePreference = trackPreferences.anilistScoreType()
|
private val scorePreference = trackPreferences.anilistScoreType()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -183,7 +185,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
val remoteTrack = api.findLibManga(track, getUsername().toInt())
|
val remoteTrack = api.findLibManga(track, getUsername().toInt())
|
||||||
return if (remoteTrack != null) {
|
return if (remoteTrack != null) {
|
||||||
track.copyPersonalFrom(remoteTrack)
|
track.copyPersonalFrom(remoteTrack, copyRemotePrivate = false)
|
||||||
track.library_id = remoteTrack.library_id
|
track.library_id = remoteTrack.library_id
|
||||||
|
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
@@ -232,6 +234,16 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
override suspend fun searchById(id: String): TrackSearch {
|
||||||
|
return api.searchById(id)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
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,19 @@ 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.ALIdSearchResult
|
||||||
|
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
|
||||||
@@ -42,8 +46,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
suspend fun addLibManga(track: Track): Track {
|
suspend fun addLibManga(track: Track): Track {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val query = """
|
val query = """
|
||||||
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean) {
|
||||||
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
|
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private) {
|
||||||
| id
|
| id
|
||||||
| status
|
| status
|
||||||
|}
|
|}
|
||||||
@@ -56,6 +60,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("mangaId", track.remote_id)
|
put("mangaId", track.remote_id)
|
||||||
put("progress", track.last_chapter_read.toInt())
|
put("progress", track.last_chapter_read.toInt())
|
||||||
put("status", track.toApiStatus())
|
put("status", track.toApiStatus())
|
||||||
|
put("private", track.private)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
with(json) {
|
with(json) {
|
||||||
@@ -79,11 +84,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
return withIOContext {
|
return withIOContext {
|
||||||
val query = """
|
val query = """
|
||||||
|mutation UpdateManga(
|
|mutation UpdateManga(
|
||||||
|${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus,
|
|${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean,
|
||||||
|${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput
|
|${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput
|
||||||
|) {
|
|) {
|
||||||
|SaveMediaListEntry(
|
|SaveMediaListEntry(
|
||||||
|id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status,
|
|id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private,
|
||||||
|scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt
|
|scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt
|
||||||
|) {
|
|) {
|
||||||
|id
|
|id
|
||||||
@@ -102,6 +107,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("score", track.score.toInt())
|
put("score", track.score.toInt())
|
||||||
put("startedAt", createDate(track.started_reading_date))
|
put("startedAt", createDate(track.started_reading_date))
|
||||||
put("completedAt", createDate(track.finished_reading_date))
|
put("completedAt", createDate(track.finished_reading_date))
|
||||||
|
put("private", track.private)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
|
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
@@ -138,6 +144,19 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|Page (perPage: 50) {
|
|Page (perPage: 50) {
|
||||||
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
||||||
|id
|
|id
|
||||||
|
|staff {
|
||||||
|
|edges {
|
||||||
|
|role
|
||||||
|
|id
|
||||||
|
|node {
|
||||||
|
|name {
|
||||||
|
|full
|
||||||
|
|userPreferred
|
||||||
|
|native
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|title {
|
|title {
|
||||||
|userPreferred
|
|userPreferred
|
||||||
|}
|
|}
|
||||||
@@ -190,6 +209,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|status
|
|status
|
||||||
|scoreRaw: score(format: POINT_100)
|
|scoreRaw: score(format: POINT_100)
|
||||||
|progress
|
|progress
|
||||||
|
|private
|
||||||
|startedAt {
|
|startedAt {
|
||||||
|year
|
|year
|
||||||
|month
|
|month
|
||||||
@@ -217,6 +237,19 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|month
|
|month
|
||||||
|day
|
|day
|
||||||
|}
|
|}
|
||||||
|
|staff {
|
||||||
|
|edges {
|
||||||
|
|role
|
||||||
|
|id
|
||||||
|
|node {
|
||||||
|
|name {
|
||||||
|
|full
|
||||||
|
|userPreferred
|
||||||
|
|native
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|}
|
|}
|
||||||
|}
|
|}
|
||||||
|}
|
|}
|
||||||
@@ -288,6 +321,121 @@ 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 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
suspend fun searchById(id: String): TrackSearch {
|
||||||
|
return withIOContext {
|
||||||
|
val query = """
|
||||||
|
|query (${'$'}mangaId: Int!) {
|
||||||
|
|Media (id: ${'$'}mangaId) {
|
||||||
|
|id
|
||||||
|
|title {
|
||||||
|
|userPreferred
|
||||||
|
|}
|
||||||
|
|coverImage {
|
||||||
|
|large
|
||||||
|
|}
|
||||||
|
|format
|
||||||
|
|status
|
||||||
|
|chapters
|
||||||
|
|description
|
||||||
|
|startDate {
|
||||||
|
|year
|
||||||
|
|month
|
||||||
|
|day
|
||||||
|
|}
|
||||||
|
|averageScore
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("query", query)
|
||||||
|
putJsonObject("variables") {
|
||||||
|
put("mangaId", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
API_URL,
|
||||||
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<ALIdSearchResult>()
|
||||||
|
.data.media
|
||||||
|
.toALManga()
|
||||||
|
.toTrack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
private fun createDate(dateValue: Long): JsonObject {
|
private fun createDate(dateValue: Long): JsonObject {
|
||||||
if (dateValue == 0L) {
|
if (dateValue == 0L) {
|
||||||
return buildJsonObject {
|
return buildJsonObject {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ data class ALManga(
|
|||||||
val startDateFuzzy: Long,
|
val startDateFuzzy: Long,
|
||||||
val totalChapters: Long,
|
val totalChapters: Long,
|
||||||
val averageScore: Int,
|
val averageScore: Int,
|
||||||
|
val staff: ALStaff,
|
||||||
) {
|
) {
|
||||||
fun toTrack() = TrackSearch.create(TrackerManager.ANILIST).apply {
|
fun toTrack() = TrackSearch.create(TrackerManager.ANILIST).apply {
|
||||||
remote_id = remoteId
|
remote_id = remoteId
|
||||||
@@ -38,6 +39,11 @@ data class ALManga(
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
staff.edges.forEach {
|
||||||
|
val name = it.node.name() ?: return@forEach
|
||||||
|
if ("Story" in it.role) authors += name
|
||||||
|
if ("Art" in it.role) artists += name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +55,7 @@ data class ALUserManga(
|
|||||||
val startDateFuzzy: Long,
|
val startDateFuzzy: Long,
|
||||||
val completedDateFuzzy: Long,
|
val completedDateFuzzy: Long,
|
||||||
val manga: ALManga,
|
val manga: ALManga,
|
||||||
|
val private: Boolean,
|
||||||
) {
|
) {
|
||||||
fun toTrack() = Track.create(TrackerManager.ANILIST).apply {
|
fun toTrack() = Track.create(TrackerManager.ANILIST).apply {
|
||||||
remote_id = manga.remoteId
|
remote_id = manga.remoteId
|
||||||
@@ -60,6 +67,7 @@ data class ALUserManga(
|
|||||||
last_chapter_read = chaptersRead.toDouble()
|
last_chapter_read = chaptersRead.toDouble()
|
||||||
library_id = libraryId
|
library_id = libraryId
|
||||||
total_chapters = manga.totalChapters
|
total_chapters = manga.totalChapters
|
||||||
|
private = this@ALUserManga.private
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toTrackStatus() = when (listStatus) {
|
private fun toTrackStatus() = when (listStatus) {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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: ALStaffName,
|
||||||
|
val coverImage: ItemCover,
|
||||||
|
val description: String?,
|
||||||
|
val staff: ALStaff,
|
||||||
|
)
|
||||||
@@ -18,3 +18,16 @@ data class ALSearchPage(
|
|||||||
data class ALSearchMedia(
|
data class ALSearchMedia(
|
||||||
val media: List<ALSearchItem>,
|
val media: List<ALSearchItem>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
@Serializable
|
||||||
|
data class ALIdSearchResult(
|
||||||
|
val data: ALIdSearchMedia,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALIdSearchMedia(
|
||||||
|
@SerialName("Media")
|
||||||
|
val media: ALSearchItem,
|
||||||
|
)
|
||||||
|
// SY <--
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ data class ALSearchItem(
|
|||||||
val startDate: ALFuzzyDate,
|
val startDate: ALFuzzyDate,
|
||||||
val chapters: Long?,
|
val chapters: Long?,
|
||||||
val averageScore: Int?,
|
val averageScore: Int?,
|
||||||
|
val staff: ALStaff,
|
||||||
) {
|
) {
|
||||||
fun toALManga(): ALManga = ALManga(
|
fun toALManga(): ALManga = ALManga(
|
||||||
remoteId = id,
|
remoteId = id,
|
||||||
@@ -24,6 +25,7 @@ data class ALSearchItem(
|
|||||||
startDateFuzzy = startDate.toEpochMilli(),
|
startDateFuzzy = startDate.toEpochMilli(),
|
||||||
totalChapters = chapters ?: 0,
|
totalChapters = chapters ?: 0,
|
||||||
averageScore = averageScore ?: -1,
|
averageScore = averageScore ?: -1,
|
||||||
|
staff = staff,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,3 +38,31 @@ data class ALItemTitle(
|
|||||||
data class ItemCover(
|
data class ItemCover(
|
||||||
val large: String,
|
val large: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaff(
|
||||||
|
val edges: List<ALEdge>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALEdge(
|
||||||
|
val role: String,
|
||||||
|
val id: Int,
|
||||||
|
val node: ALStaffNode,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaffNode(
|
||||||
|
val name: ALStaffName,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaffName(
|
||||||
|
val userPreferred: String? = null,
|
||||||
|
val native: String? = null,
|
||||||
|
val full: String? = null,
|
||||||
|
) {
|
||||||
|
operator fun invoke(): String? {
|
||||||
|
return userPreferred ?: full ?: native
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ data class ALUserListItem(
|
|||||||
val startedAt: ALFuzzyDate,
|
val startedAt: ALFuzzyDate,
|
||||||
val completedAt: ALFuzzyDate,
|
val completedAt: ALFuzzyDate,
|
||||||
val media: ALSearchItem,
|
val media: ALSearchItem,
|
||||||
|
val private: Boolean,
|
||||||
) {
|
) {
|
||||||
fun toALUserManga(): ALUserManga {
|
fun toALUserManga(): ALUserManga {
|
||||||
return ALUserManga(
|
return ALUserManga(
|
||||||
@@ -38,6 +39,7 @@ data class ALUserListItem(
|
|||||||
startDateFuzzy = startedAt.toEpochMilli(),
|
startDateFuzzy = startedAt.toEpochMilli(),
|
||||||
completedDateFuzzy = completedAt.toEpochMilli(),
|
completedDateFuzzy = completedAt.toEpochMilli(),
|
||||||
manga = media.toALManga(),
|
manga = media.toALManga(),
|
||||||
|
private = private,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ 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
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@@ -23,6 +23,8 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
|
|
||||||
private val api by lazy { BangumiApi(id, client, interceptor) }
|
private val api by lazy { BangumiApi(id, client, interceptor) }
|
||||||
|
|
||||||
|
override val supportsPrivateTracking: Boolean = true
|
||||||
|
|
||||||
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
|
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
|
||||||
|
|
||||||
override fun displayScore(track: DomainTrack): String {
|
override fun displayScore(track: DomainTrack): String {
|
||||||
@@ -48,26 +50,23 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
val statusTrack = api.statusLibManga(track)
|
val statusTrack = api.statusLibManga(track, getUsername())
|
||||||
val remoteTrack = api.findLibManga(track)
|
return if (statusTrack != null) {
|
||||||
return if (remoteTrack != null && statusTrack != null) {
|
track.copyPersonalFrom(statusTrack, copyRemotePrivate = false)
|
||||||
track.copyPersonalFrom(remoteTrack)
|
track.library_id = statusTrack.library_id
|
||||||
track.library_id = remoteTrack.library_id
|
track.score = statusTrack.score
|
||||||
|
track.last_chapter_read = statusTrack.last_chapter_read
|
||||||
|
track.total_chapters = statusTrack.total_chapters
|
||||||
if (track.status != COMPLETED) {
|
if (track.status != COMPLETED) {
|
||||||
track.status = if (hasReadChapters) READING else statusTrack.status
|
track.status = if (hasReadChapters) READING else statusTrack.status
|
||||||
}
|
}
|
||||||
|
|
||||||
track.score = statusTrack.score
|
update(track)
|
||||||
track.last_chapter_read = statusTrack.last_chapter_read
|
|
||||||
track.total_chapters = remoteTrack.total_chapters
|
|
||||||
refresh(track)
|
|
||||||
} else {
|
} else {
|
||||||
// Set default fields if it's not found in the list
|
// Set default fields if it's not found in the list
|
||||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||||
track.score = 0.0
|
track.score = 0.0
|
||||||
add(track)
|
add(track)
|
||||||
update(track)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,12 +74,13 @@ 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, getUsername()) ?: throw Exception("Could not find manga")
|
||||||
track.copyPersonalFrom(remoteStatusTrack)
|
track.copyPersonalFrom(remoteStatusTrack)
|
||||||
api.findLibManga(track)?.let { remoteTrack ->
|
|
||||||
track.total_chapters = remoteTrack.total_chapters
|
|
||||||
}
|
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +113,12 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
try {
|
try {
|
||||||
val oauth = api.accessToken(code)
|
val oauth = api.accessToken(code)
|
||||||
interceptor.newAuth(oauth)
|
interceptor.newAuth(oauth)
|
||||||
saveCredentials(oauth.userId.toString(), oauth.accessToken)
|
// Users can set a 'username' (not nickname) once which effectively
|
||||||
} catch (e: Throwable) {
|
// replaces the stringified ID in certain queries.
|
||||||
|
// If no username is set, the API returns the user ID as a strings
|
||||||
|
var username = api.getUsername()
|
||||||
|
saveCredentials(username, oauth.accessToken)
|
||||||
|
} catch (_: Throwable) {
|
||||||
logout()
|
logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +130,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
fun restoreToken(): BGMOAuth? {
|
fun restoreToken(): BGMOAuth? {
|
||||||
return try {
|
return try {
|
||||||
json.decodeFromString<BGMOAuth>(trackPreferences.trackToken(this).get())
|
json.decodeFromString<BGMOAuth>(trackPreferences.trackToken(this).get())
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,11 +142,11 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 3L
|
const val PLAN_TO_READ = 1L
|
||||||
const val COMPLETED = 2L
|
const val COMPLETED = 2L
|
||||||
|
const val READING = 3L
|
||||||
const val ON_HOLD = 4L
|
const val ON_HOLD = 4L
|
||||||
const val DROPPED = 5L
|
const val DROPPED = 5L
|
||||||
const val PLAN_TO_READ = 1L
|
|
||||||
|
|
||||||
private val SCORE_LIST = IntRange(0, 10)
|
private val SCORE_LIST = IntRange(0, 10)
|
||||||
.map(Int::toString)
|
.map(Int::toString)
|
||||||
|
|||||||
@@ -5,22 +5,32 @@ 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.bangumi.dto.BGMCollectionResponse
|
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.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.BGMUser
|
||||||
|
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.HttpException
|
||||||
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.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.add
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlinx.serialization.json.putJsonArray
|
||||||
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.Headers.Companion.headersOf
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.net.URLEncoder
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
|
|
||||||
class BangumiApi(
|
class BangumiApi(
|
||||||
private val trackId: Long,
|
private val trackId: Long,
|
||||||
@@ -34,11 +44,17 @@ class BangumiApi(
|
|||||||
|
|
||||||
suspend fun addLibManga(track: Track): Track {
|
suspend fun addLibManga(track: Track): Track {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val body = FormBody.Builder()
|
val url = "$API_URL/v0/users/-/collections/${track.remote_id}"
|
||||||
.add("rating", track.score.toInt().toString())
|
val body = buildJsonObject {
|
||||||
.add("status", track.toApiStatus())
|
put("type", track.toApiStatus())
|
||||||
.build()
|
put("rate", track.score.toInt().coerceIn(0, 10))
|
||||||
authClient.newCall(POST("$API_URL/collection/${track.remote_id}/update", body = body))
|
put("ep_status", track.last_chapter_read.toInt())
|
||||||
|
put("private", track.private)
|
||||||
|
}
|
||||||
|
.toString()
|
||||||
|
.toRequestBody()
|
||||||
|
// Returns with 202 Accepted on success with no body
|
||||||
|
authClient.newCall(POST(url, body = body, headers = headersOf("Content-Type", APP_JSON)))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
@@ -46,81 +62,106 @@ class BangumiApi(
|
|||||||
|
|
||||||
suspend fun updateLibManga(track: Track): Track {
|
suspend fun updateLibManga(track: Track): Track {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
// read status update
|
val url = "$API_URL/v0/users/-/collections/${track.remote_id}"
|
||||||
val sbody = FormBody.Builder()
|
val body = buildJsonObject {
|
||||||
.add("rating", track.score.toInt().toString())
|
put("type", track.toApiStatus())
|
||||||
.add("status", track.toApiStatus())
|
put("rate", track.score.toInt().coerceIn(0, 10))
|
||||||
.build()
|
put("ep_status", track.last_chapter_read.toInt())
|
||||||
authClient.newCall(POST("$API_URL/collection/${track.remote_id}/update", body = sbody))
|
put("private", track.private)
|
||||||
.awaitSuccess()
|
}
|
||||||
|
.toString()
|
||||||
|
.toRequestBody()
|
||||||
|
|
||||||
// chapter update
|
val request = Request.Builder()
|
||||||
val body = FormBody.Builder()
|
.url(url)
|
||||||
.add("watched_eps", track.last_chapter_read.toInt().toString())
|
.patch(body)
|
||||||
|
.headers(headersOf("Content-Type", APP_JSON))
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(
|
// Returns with 204 No Content
|
||||||
POST("$API_URL/subject/${track.remote_id}/update/watched_eps", body = body),
|
authClient.newCall(request)
|
||||||
).awaitSuccess()
|
.awaitSuccess()
|
||||||
|
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun search(search: String): List<TrackSearch> {
|
suspend fun search(search: String): List<TrackSearch> {
|
||||||
|
// This API is marked as experimental in the documentation
|
||||||
|
// but that has been the case since 2022 with few significant
|
||||||
|
// changes to the schema for this endpoint since
|
||||||
|
// "实验性 API, 本 schema 和实际的 API 行为都可能随时发生改动"
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
|
val url = "$API_URL/v0/search/subjects?limit=20"
|
||||||
.toUri()
|
val body = buildJsonObject {
|
||||||
.buildUpon()
|
put("keyword", search)
|
||||||
.appendQueryParameter("max_results", "20")
|
put("sort", "match")
|
||||||
.build()
|
putJsonObject("filter") {
|
||||||
|
putJsonArray("type") {
|
||||||
|
add(1) // "Book" (书籍) type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toString()
|
||||||
|
.toRequestBody()
|
||||||
with(json) {
|
with(json) {
|
||||||
authClient.newCall(GET(url.toString()))
|
authClient.newCall(POST(url, body = body, headers = headersOf("Content-Type", APP_JSON)))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs<BGMSearchResult>()
|
.parseAs<BGMSearchResult>()
|
||||||
.let { result ->
|
.data
|
||||||
if (result.code == 404) emptyList<TrackSearch>()
|
.map { it.toTrackSearch(trackId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.list
|
suspend fun statusLibManga(track: Track, username: String): Track? {
|
||||||
?.filter { it.type == 1 }
|
return withIOContext {
|
||||||
?.map { it.toTrackSearch(trackId) }
|
val url = "$API_URL/v0/users/$username/collections/${track.remote_id}"
|
||||||
.orEmpty()
|
with(json) {
|
||||||
|
try {
|
||||||
|
authClient.newCall(GET(url, cache = CacheControl.FORCE_NETWORK))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<BGMCollectionResponse>()
|
||||||
|
.let {
|
||||||
|
track.status = it.getStatus()
|
||||||
|
track.last_chapter_read = it.epStatus?.toDouble() ?: 0.0
|
||||||
|
track.score = it.rate?.toDouble() ?: 0.0
|
||||||
|
track.total_chapters = it.subject?.eps?.toLong() ?: 0L
|
||||||
|
track
|
||||||
|
}
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
if (e.code == 404) { // "subject is not collected by user"
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun findLibManga(track: Track): Track? {
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
with(json) {
|
with(json) {
|
||||||
authClient.newCall(GET("$API_URL/subject/${track.remote_id}"))
|
authClient.newCall(GET("${API_URL}/v0/subjects/${track.remoteId}"))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs<BGMSearchItem>()
|
.parseAs<BGMSubject>()
|
||||||
.toTrackSearch(trackId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun statusLibManga(track: Track): Track? {
|
|
||||||
return withIOContext {
|
|
||||||
val urlUserRead = "$API_URL/collection/${track.remote_id}"
|
|
||||||
val requestUserRead = Request.Builder()
|
|
||||||
.url(urlUserRead)
|
|
||||||
.cacheControl(CacheControl.FORCE_NETWORK)
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// TODO: get user readed chapter here
|
|
||||||
with(json) {
|
|
||||||
authClient.newCall(requestUserRead)
|
|
||||||
.awaitSuccess()
|
|
||||||
.parseAs<BGMCollectionResponse>()
|
|
||||||
.let {
|
.let {
|
||||||
if (it.code == 400) return@let null
|
TrackMangaMetadata(
|
||||||
|
remoteId = it.id,
|
||||||
track.status = it.status?.id!!
|
title = it.nameCn,
|
||||||
track.last_chapter_read = it.epStatus!!.toDouble()
|
thumbnailUrl = it.images?.common,
|
||||||
track.score = it.rating!!
|
description = it.summary,
|
||||||
track
|
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(", "),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,24 +169,31 @@ class BangumiApi(
|
|||||||
|
|
||||||
suspend fun accessToken(code: String): BGMOAuth {
|
suspend fun accessToken(code: String): BGMOAuth {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
.add("grant_type", "authorization_code")
|
||||||
|
.add("client_id", CLIENT_ID)
|
||||||
|
.add("client_secret", CLIENT_SECRET)
|
||||||
|
.add("code", code)
|
||||||
|
.add("redirect_uri", REDIRECT_URL)
|
||||||
|
.build()
|
||||||
with(json) {
|
with(json) {
|
||||||
client.newCall(accessTokenRequest(code))
|
client.newCall(POST(OAUTH_URL, body = body))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs()
|
.parseAs<BGMOAuth>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun accessTokenRequest(code: String) = POST(
|
suspend fun getUsername(): String {
|
||||||
OAUTH_URL,
|
return withIOContext {
|
||||||
body = FormBody.Builder()
|
with(json) {
|
||||||
.add("grant_type", "authorization_code")
|
authClient.newCall(GET("$API_URL/v0/me"))
|
||||||
.add("client_id", CLIENT_ID)
|
.awaitSuccess()
|
||||||
.add("client_secret", CLIENT_SECRET)
|
.parseAs<BGMUser>()
|
||||||
.add("code", code)
|
.username
|
||||||
.add("redirect_uri", REDIRECT_URL)
|
}
|
||||||
.build(),
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CLIENT_ID = "bgm291665acbd06a4c28"
|
private const val CLIENT_ID = "bgm291665acbd06a4c28"
|
||||||
@@ -157,6 +205,8 @@ class BangumiApi(
|
|||||||
|
|
||||||
private const val REDIRECT_URL = "mihon://bangumi-auth"
|
private const val REDIRECT_URL = "mihon://bangumi-auth"
|
||||||
|
|
||||||
|
private const val APP_JSON = "application/json"
|
||||||
|
|
||||||
fun authUrl(): Uri =
|
fun authUrl(): Uri =
|
||||||
LOGIN_URL.toUri().buildUpon()
|
LOGIN_URL.toUri().buildUpon()
|
||||||
.appendQueryParameter("client_id", CLIENT_ID)
|
.appendQueryParameter("client_id", CLIENT_ID)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.BuildConfig
|
|||||||
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.isExpired
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.isExpired
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.FormBody
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@@ -21,12 +20,13 @@ class BangumiInterceptor(private val bangumi: Bangumi) : Interceptor {
|
|||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
|
|
||||||
val currAuth = oauth ?: throw Exception("Not authenticated with Bangumi")
|
var currAuth: BGMOAuth = oauth ?: throw Exception("Not authenticated with Bangumi")
|
||||||
|
|
||||||
if (currAuth.isExpired()) {
|
if (currAuth.isExpired()) {
|
||||||
val response = chain.proceed(BangumiApi.refreshTokenRequest(currAuth.refreshToken!!))
|
val response = chain.proceed(BangumiApi.refreshTokenRequest(currAuth.refreshToken!!))
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
newAuth(json.decodeFromString<BGMOAuth>(response.body.string()))
|
currAuth = json.decodeFromString<BGMOAuth>(response.body.string())
|
||||||
|
newAuth(currAuth)
|
||||||
} else {
|
} else {
|
||||||
response.close()
|
response.close()
|
||||||
}
|
}
|
||||||
@@ -38,14 +38,7 @@ class BangumiInterceptor(private val bangumi: Bangumi) : Interceptor {
|
|||||||
"jobobby04/TachiyomiSY/v${BuildConfig.VERSION_NAME} (Android) (http://github.com/jobobby04/tachiyomisy)",
|
"jobobby04/TachiyomiSY/v${BuildConfig.VERSION_NAME} (Android) (http://github.com/jobobby04/tachiyomisy)",
|
||||||
)
|
)
|
||||||
.apply {
|
.apply {
|
||||||
if (originalRequest.method == "GET") {
|
addHeader("Authorization", "Bearer ${currAuth.accessToken}")
|
||||||
val newUrl = originalRequest.url.newBuilder()
|
|
||||||
.addQueryParameter("access_token", currAuth.accessToken)
|
|
||||||
.build()
|
|
||||||
url(newUrl)
|
|
||||||
} else {
|
|
||||||
post(addToken(currAuth.accessToken, originalRequest.body as FormBody))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
.let(chain::proceed)
|
.let(chain::proceed)
|
||||||
@@ -67,13 +60,4 @@ class BangumiInterceptor(private val bangumi: Bangumi) : Interceptor {
|
|||||||
|
|
||||||
bangumi.saveToken(oauth)
|
bangumi.saveToken(oauth)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addToken(token: String, oidFormBody: FormBody): FormBody {
|
|
||||||
val newFormBody = FormBody.Builder()
|
|
||||||
for (i in 0..<oidFormBody.size) {
|
|
||||||
newFormBody.add(oidFormBody.name(i), oidFormBody.value(i))
|
|
||||||
}
|
|
||||||
newFormBody.add("access_token", token)
|
|
||||||
return newFormBody.build()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package eu.kanade.tachiyomi.data.track.bangumi
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
|
||||||
fun Track.toApiStatus() = when (status) {
|
fun Track.toApiStatus() = when (status) {
|
||||||
Bangumi.READING -> "do"
|
Bangumi.PLAN_TO_READ -> 1
|
||||||
Bangumi.COMPLETED -> "collect"
|
Bangumi.COMPLETED -> 2
|
||||||
Bangumi.ON_HOLD -> "on_hold"
|
Bangumi.READING -> 3
|
||||||
Bangumi.DROPPED -> "dropped"
|
Bangumi.ON_HOLD -> 4
|
||||||
Bangumi.PLAN_TO_READ -> "wish"
|
Bangumi.DROPPED -> 5
|
||||||
else -> throw NotImplementedError("Unknown status: $status")
|
else -> throw NotImplementedError("Unknown status: $status")
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user