Compare commits
327 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 917f20894b | |||
| 3a3b719b8b | |||
| 1903437ecf | |||
| 5c26bb3a52 | |||
| 07599ade3a | |||
| 0a9f36402b | |||
| d2b325cd02 | |||
| cdc64aceb7 | |||
| 4bfd6e4026 | |||
| 50eebdf7d3 | |||
| f843de28d7 | |||
| d250a9a680 | |||
| 4130db3920 | |||
| f2cbff04ab | |||
| 061e9359e8 | |||
| 73258e9e05 | |||
| 73e4982ffb | |||
| 185cd923c0 | |||
| 3cfc53bf11 | |||
| 1301acfdb7 | |||
| 9d9dbea48d | |||
| c1df3eb1d0 | |||
| 3154c97aee | |||
| ffe1b160de | |||
| 23272375b7 | |||
| 863b6ee784 | |||
| c4c8d4b9c3 | |||
| b2bbbca585 | |||
| df3b879cf6 | |||
| 47c4f2cc8c | |||
| 905a1c1230 | |||
| bcaf7f6415 | |||
| 4639b3ecc3 | |||
| 2034971cc0 | |||
| bb8698b2a6 | |||
| cd69b09dd0 | |||
| 462b2164e8 | |||
| fb1a4ad828 | |||
| 3bd89cee26 | |||
| 6f43e98fff | |||
| 6feeb4b1ee | |||
| fcfe750fcf | |||
| 6e314e3643 | |||
| 487ca49c11 | |||
| 698abe8667 | |||
| 13c9daf9a9 | |||
| eb21454d6d | |||
| 56347e6d52 | |||
| 5c085a36e8 | |||
| 65ab676946 | |||
| 1f51569a35 | |||
| b0d6e16ca3 | |||
| 85cf54ccc8 | |||
| 602df5a729 | |||
| c8102836ce | |||
| e641575941 | |||
| 83afcee4d1 | |||
| 2102e0594e | |||
| 14c91da6b3 | |||
| 46c1c6463a | |||
| 89a521b836 | |||
| 65c6ed21ab | |||
| 1b911e7e15 | |||
| 0535e41051 | |||
| 3fc802f837 | |||
| 976b5cc03e | |||
| a9fe971337 | |||
| 5d1dbcb390 | |||
| 8d11ef3244 | |||
| 724a61f513 | |||
| 724c774dc9 | |||
| 29e0b2e4a5 | |||
| 2776e41127 | |||
| af1f77418f | |||
| c1df5da062 | |||
| f8f645772d | |||
| b1e6fa65d6 | |||
| 01e8c6cc12 | |||
| b4668c6829 | |||
| 08d6c604bc | |||
| 02cec06535 | |||
| ebdb3f7478 | |||
| 3724d79825 | |||
| c3e2eb6672 | |||
| fa91695add | |||
| e7786bd16f | |||
| 3d70476b9f | |||
| e74e0de8f5 | |||
| a2f552d6d2 | |||
| a6bd0bbd2a | |||
| fd42bba188 | |||
| a0ec735066 | |||
| 89f5fce19d | |||
| bf711a995c | |||
| d977614b7a | |||
| d282df6973 | |||
| db5b3a69cc | |||
| c70c5dff25 | |||
| 25ace80419 | |||
| b8b468cea7 | |||
| 0ffc798e9a | |||
| ad5a76741a | |||
| 003c5ad39a | |||
| 582d0ef121 | |||
| 5566db160b | |||
| 6fb6838656 | |||
| 1e5d490c22 | |||
| 276aeb0f59 | |||
| c62d9d1446 | |||
| 4ff18364d9 | |||
| 6c8e4e951a | |||
| dc1fde628d | |||
| 241b70e5ce | |||
| 64c755ddf3 | |||
| 3ae6c0131b | |||
| e3b43de298 | |||
| 02ff6b4e2f | |||
| ee8379b12a | |||
| 20e1cc0a7d | |||
| 78b434e794 | |||
| 52c8b260e0 | |||
| cccdc99977 | |||
| ca15d2ccc5 | |||
| b7b0ffc885 | |||
| f1a57749c4 | |||
| 26d6c09d21 | |||
| 088e3b6800 | |||
| c8ee2dbff4 | |||
| 835a21b426 | |||
| 8d67c87639 | |||
| 73cd25e8ba | |||
| e9ed861f00 | |||
| e7c1d4deef | |||
| b8eb75fc68 | |||
| 3d34f3dd2f | |||
| 01dc277877 | |||
| b809ae5c6f | |||
| 4c563122f8 | |||
| 4c1124fdb0 | |||
| bdbaecd975 | |||
| 2b8641c1dd | |||
| f1a90000b2 | |||
| 3bd0f0b447 | |||
| cb5b618266 | |||
| c71c07690a | |||
| d85d77da01 | |||
| 164d7bc70f | |||
| db51b09f80 | |||
| 5f8d03ba9b | |||
| 4cae7b27a6 | |||
| edcf939611 | |||
| 208d291b3c | |||
| 19f049189a | |||
| c855276555 | |||
| 9e244e0889 | |||
| bf7a067908 | |||
| 67c4b71b88 | |||
| 1fa8a86cce | |||
| 73eb98960f | |||
| 59704221b7 | |||
| 19c23943ec | |||
| d068559dee | |||
| 8e6b5b8bee | |||
| c99ddbe10f | |||
| 23925c4ba6 | |||
| e71f0afd99 | |||
| 014bf97248 | |||
| fc0d666366 | |||
| eb7465e6f9 | |||
| ff6ad20a77 | |||
| 17c528a206 | |||
| 63f4034a7f | |||
| 6c1bfc2177 | |||
| 45ff1f06ba | |||
| 3e287a593a | |||
| 01420154be | |||
| 1e4c596d0e | |||
| 26cb2bbbd1 | |||
| 588db79a64 | |||
| e5aaf3b31f | |||
| 9c222b128b | |||
| c66e08d43e | |||
| 493d8fc45f | |||
| 47993cb55d | |||
| 4701cbea23 | |||
| 70efd6f2bf | |||
| b85b6a713c | |||
| 6291529a10 | |||
| 160907ab52 | |||
| bccd30a80f | |||
| ef4d3e6c4d | |||
| 4fe7a1375a | |||
| 02bc195068 | |||
| 1e20913237 | |||
| 48b488fa59 | |||
| 893f3b2e34 | |||
| 25be12852f | |||
| 4f0292b000 | |||
| f669fd9205 | |||
| c3e0646b61 | |||
| 0d2cad8693 | |||
| 237916a37b | |||
| 16653f9585 | |||
| 392b1009c9 | |||
| 10c184e58a | |||
| f6b8756dc0 | |||
| f2654807a4 | |||
| ab90b75d73 | |||
| 0b58a081af | |||
| ae98a5fe58 | |||
| 74fa4e3503 | |||
| c20b0f67a8 | |||
| 098c7196de | |||
| 99a25560c1 | |||
| 79d19a2d8b | |||
| 061d0809bd | |||
| 0ce0c45cc7 | |||
| e910362b16 | |||
| 96c05bf113 | |||
| 6d2bda5c9d | |||
| 4a080fba3f | |||
| ac5b3b164f | |||
| c43d7dbb31 | |||
| d218fdfcf4 | |||
| e59af2fd1f | |||
| 3d761b5bf4 | |||
| 1892101359 | |||
| d76d25379e | |||
| c96cf4b11a | |||
| 31601f523d | |||
| f9abe20b84 | |||
| f7030ed800 | |||
| f4173b3766 | |||
| 221a564644 | |||
| 7458eff2d8 | |||
| 187245885a | |||
| e5d8c2edbc | |||
| be4aa39c8a | |||
| 014c697773 | |||
| ea733de80e | |||
| 3ac5dcd66e | |||
| 054de1cc6f | |||
| b525c0988a | |||
| 691efe0831 | |||
| 8317a30d6e | |||
| 633937b0bc | |||
| 3152d2803a | |||
| 883c90adc8 | |||
| 8e658be7d7 | |||
| c27a4e2bf5 | |||
| 257f544a89 | |||
| f8cb08ce52 | |||
| d970eea7cf | |||
| a45fad81c8 | |||
| 76d6cf129a | |||
| 879427446f | |||
| 9edd7b0c04 | |||
| d97b83fe93 | |||
| 027e6bbb05 | |||
| 4cdead8006 | |||
| aaff472317 | |||
| 9b2febcd6d | |||
| 5de355238d | |||
| 644c8ec491 | |||
| 4b516ae4c5 | |||
| 223251f868 | |||
| 9b7d6bace1 | |||
| c15b8b65e5 | |||
| 4e9eaa5e81 | |||
| 066e10246f | |||
| 3294dd6ca8 | |||
| 2315295985 | |||
| 56ddf21107 | |||
| 3f47df21b8 | |||
| 712407f524 | |||
| 3a49a0a21a | |||
| 04b3e13ba1 | |||
| 39bc2b49c0 | |||
| 9765282640 | |||
| 320a620afa | |||
| ec2a720617 | |||
| 83d2ca7a54 | |||
| 891503c793 | |||
| 43abc6c797 | |||
| f916e94a4c | |||
| fc9b2b6e1e | |||
| 66bdd70485 | |||
| 29024ea03a | |||
| 6af33905be | |||
| f97d918728 | |||
| 6a38e501d9 | |||
| 8310733c4c | |||
| 28f0946877 | |||
| e135a0dc71 | |||
| 05a65773f0 | |||
| 8d794560a0 | |||
| 4341a98413 | |||
| 24f5a36350 | |||
| f125db6973 | |||
| 4ec969657e | |||
| cb7e790086 | |||
| 9012dafed4 | |||
| 8b24d7eded | |||
| dcc537accb | |||
| ab67775f13 | |||
| e105828e07 | |||
| 3c83edaad2 | |||
| b4f278e5fa | |||
| 29ca687a26 | |||
| ed975cae63 | |||
| 4fce0944b4 | |||
| 4a52898f08 | |||
| 92b48319ed | |||
| 9e113d80f7 | |||
| f960554cf8 | |||
| 0bdea705a5 | |||
| 33361ea7f6 | |||
| 717240f53c | |||
| 5156248a96 | |||
| e074df469e | |||
| d1277ecb02 | |||
| 8bcc235490 | |||
| aaa7171c10 | |||
| 887311b440 | |||
| 1c90aac059 | |||
| 3ad9765dcf | |||
| cc934607c8 |
+6
-3
@@ -7,7 +7,7 @@ indent_style = space
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.{xml,sq,sqm}]
|
[*.{xml,sq,sqm,aidl}]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
# noinspection EditorConfigKeyCorrectness
|
# noinspection EditorConfigKeyCorrectness
|
||||||
@@ -23,9 +23,14 @@ ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
|||||||
ktlint_code_style = intellij_idea
|
ktlint_code_style = intellij_idea
|
||||||
ktlint_function_naming_ignore_when_annotated_with = Composable
|
ktlint_function_naming_ignore_when_annotated_with = Composable
|
||||||
ktlint_standard_class-signature = disabled
|
ktlint_standard_class-signature = disabled
|
||||||
|
ktlint_standard_comment-wrapping = disabled
|
||||||
ktlint_standard_discouraged-comment-location = disabled
|
ktlint_standard_discouraged-comment-location = disabled
|
||||||
ktlint_standard_function-expression-body = disabled
|
ktlint_standard_function-expression-body = disabled
|
||||||
ktlint_standard_function-signature = disabled
|
ktlint_standard_function-signature = disabled
|
||||||
|
ktlint_standard_type-argument-comment = disabled
|
||||||
|
ktlint_standard_type-parameter-comment = disabled
|
||||||
|
ktlint_standard_blank-line-between-when-conditions = disabled
|
||||||
|
|
||||||
# SY
|
# SY
|
||||||
ktlint_standard_filename = disabled
|
ktlint_standard_filename = disabled
|
||||||
ktlint_standard_argument-list-wrapping = disabled
|
ktlint_standard_argument-list-wrapping = disabled
|
||||||
@@ -33,8 +38,6 @@ ktlint_standard_function-naming = disabled
|
|||||||
ktlint_standard_property-naming = disabled
|
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_max-line-length = disabled
|
ktlint_standard_max-line-length = disabled
|
||||||
ktlint_standard_type-argument-comment = disabled
|
|
||||||
ktlint_standard_value-argument-comment = disabled
|
ktlint_standard_value-argument-comment = disabled
|
||||||
ktlint_standard_value-parameter-comment = disabled
|
ktlint_standard_value-parameter-comment = disabled
|
||||||
@@ -100,5 +100,5 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
||||||
required: true
|
required: true
|
||||||
- label: I understand that **Mihon does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions.
|
- label: I understand that **TachiyomiSY 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
|
||||||
|
|||||||
@@ -12,22 +12,22 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
|
|
||||||
- name: Set up gradle
|
- name: Set up gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: ./gradlew spotlessCheck assembleDevDebug
|
run: ./gradlew spotlessCheck assembleDevDebug
|
||||||
|
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: TachiyomiSY-${{ github.sha }}.apk
|
name: TachiyomiSY-${{ github.sha }}.apk
|
||||||
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
||||||
|
|||||||
@@ -15,20 +15,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- 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"
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
|
|
||||||
- name: Set up gradle
|
- name: Set up gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
# SY -->
|
# SY -->
|
||||||
- name: Write google-services.json
|
- name: Write google-services.json
|
||||||
@@ -71,24 +71,10 @@ jobs:
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk TachiyomiSY.apk
|
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk TachiyomiSY.apk
|
||||||
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
|
|
||||||
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
mv 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 }'`
|
|
||||||
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
mv 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 }'`
|
|
||||||
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
mv 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 }'`
|
|
||||||
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
mv 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 }'`
|
|
||||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
@@ -96,19 +82,10 @@ jobs:
|
|||||||
tag_name: ${{ github.run_number }}
|
tag_name: ${{ github.run_number }}
|
||||||
name: TachiyomiSY
|
name: TachiyomiSY
|
||||||
body: |
|
body: |
|
||||||
---
|
<!-->
|
||||||
|
> [!TIP]
|
||||||
### Checksums
|
>
|
||||||
|
> ### If you are unsure which version to download then go with `TachiyomiSY.apk`
|
||||||
| Variant | SHA-256 |
|
|
||||||
| ------- | ------- |
|
|
||||||
| Universal | ${{ env.APK_UNIVERSAL_SHA }} |
|
|
||||||
| arm64-v8a | ${{ env.APK_ARM64_V8A_SHA }} |
|
|
||||||
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }} |
|
|
||||||
| x86 | ${{ env.APK_X86_SHA }} |
|
|
||||||
| x86_64 | ${{ env.APK_X86_64_SHA }} |
|
|
||||||
|
|
||||||
## If you are unsure which version to choose then go with TachiyomiSY.apk
|
|
||||||
files: |
|
files: |
|
||||||
TachiyomiSY.apk
|
TachiyomiSY.apk
|
||||||
TachiyomiSY-arm64-v8a.apk
|
TachiyomiSY-arm64-v8a.apk
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
|
|
||||||
- name: Set up gradle
|
- name: Set up gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Create Tag
|
- name: Create Tag
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check PR and Add Label
|
- name: Check PR and Add Label
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const prAuthor = context.payload.pull_request.user.login;
|
const prAuthor = context.payload.pull_request.user.login;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ Additional features for some extensions, features include custom description, op
|
|||||||
* Mangadex
|
* Mangadex
|
||||||
* NHentai
|
* NHentai
|
||||||
* Puruin
|
* Puruin
|
||||||
* Tsumino
|
* LANraragi
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest).
|
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest).
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi.sy"
|
applicationId = "eu.kanade.tachiyomi.sy"
|
||||||
|
|
||||||
versionCode = 75
|
versionCode = 76
|
||||||
versionName = "1.12.0"
|
versionName = "1.12.0"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
@@ -129,9 +129,9 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
|
aidl = true
|
||||||
|
|
||||||
// Disable some unused things
|
// Disable some unused things
|
||||||
aidl = false
|
|
||||||
renderScript = false
|
renderScript = false
|
||||||
shaders = false
|
shaders = false
|
||||||
}
|
}
|
||||||
@@ -150,12 +150,14 @@ kotlin {
|
|||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3ExpressiveApi",
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||||
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
"-Xannotation-default-target=param-property",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +258,6 @@ dependencies {
|
|||||||
implementation(libs.directionalviewpager) {
|
implementation(libs.directionalviewpager) {
|
||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
}
|
}
|
||||||
implementation(libs.insetter)
|
|
||||||
implementation(libs.richeditor.compose)
|
implementation(libs.richeditor.compose)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
@@ -266,6 +267,7 @@ dependencies {
|
|||||||
implementation(libs.compose.grid)
|
implementation(libs.compose.grid)
|
||||||
implementation(libs.reorderable)
|
implementation(libs.reorderable)
|
||||||
implementation(libs.bundles.markdown)
|
implementation(libs.bundles.markdown)
|
||||||
|
implementation(libs.materialKolor)
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
@@ -278,8 +280,12 @@ dependencies {
|
|||||||
// Shizuku
|
// Shizuku
|
||||||
implementation(libs.bundles.shizuku)
|
implementation(libs.bundles.shizuku)
|
||||||
|
|
||||||
|
// String similarity
|
||||||
|
implementation(libs.stringSimilarity)
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
testImplementation(libs.bundles.test)
|
testImplementation(libs.bundles.test)
|
||||||
|
testRuntimeOnly(libs.junit.platform.launcher)
|
||||||
|
|
||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||||
// debugImplementation(libs.leakcanary.android)
|
// debugImplementation(libs.leakcanary.android)
|
||||||
@@ -288,9 +294,6 @@ dependencies {
|
|||||||
testImplementation(kotlinx.coroutines.test)
|
testImplementation(kotlinx.coroutines.test)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
// Text distance (EH)
|
|
||||||
implementation(sylibs.simularity)
|
|
||||||
|
|
||||||
// Firebase (EH)
|
// Firebase (EH)
|
||||||
implementation(platform(libs.firebase.bom))
|
implementation(platform(libs.firebase.bom))
|
||||||
implementation(libs.firebase.analytics)
|
implementation(libs.firebase.analytics)
|
||||||
|
|||||||
Vendored
+2
@@ -299,3 +299,5 @@
|
|||||||
-dontwarn org.ietf.jgss.GSSManager
|
-dontwarn org.ietf.jgss.GSSManager
|
||||||
-dontwarn org.ietf.jgss.GSSName
|
-dontwarn org.ietf.jgss.GSSName
|
||||||
-dontwarn org.ietf.jgss.Oid
|
-dontwarn org.ietf.jgss.Oid
|
||||||
|
-dontwarn com.google.re2j.Matcher
|
||||||
|
-dontwarn com.google.re2j.Pattern
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package mihon.app.shizuku;
|
||||||
|
|
||||||
|
interface IShellInterface {
|
||||||
|
void install(in AssetFileDescriptor apk) = 1;
|
||||||
|
|
||||||
|
void destroy() = 16777114;
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
|
|||||||
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
||||||
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||||
import mihon.domain.extensionrepo.service.ExtensionRepoService
|
import mihon.domain.extensionrepo.service.ExtensionRepoService
|
||||||
|
import mihon.domain.migration.usecases.MigrateMangaUseCase
|
||||||
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||||
@@ -59,6 +60,7 @@ import tachiyomi.domain.category.interactor.SetMangaCategories
|
|||||||
import tachiyomi.domain.category.interactor.SetSortModeForCategory
|
import tachiyomi.domain.category.interactor.SetSortModeForCategory
|
||||||
import tachiyomi.domain.category.interactor.UpdateCategory
|
import tachiyomi.domain.category.interactor.UpdateCategory
|
||||||
import tachiyomi.domain.category.repository.CategoryRepository
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetBookmarkedChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapter
|
import tachiyomi.domain.chapter.interactor.GetChapter
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
@@ -133,6 +135,11 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
addFactory { GetExcludedScanlators(get()) }
|
addFactory { GetExcludedScanlators(get()) }
|
||||||
addFactory { SetExcludedScanlators(get()) }
|
addFactory { SetExcludedScanlators(get()) }
|
||||||
|
addFactory {
|
||||||
|
MigrateMangaUseCase(
|
||||||
|
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||||
addFactory { GetApplicationRelease(get(), get()) }
|
addFactory { GetApplicationRelease(get(), get()) }
|
||||||
@@ -150,6 +157,7 @@ class DomainModule : InjektModule {
|
|||||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||||
addFactory { GetChapter(get()) }
|
addFactory { GetChapter(get()) }
|
||||||
addFactory { GetChaptersByMangaId(get()) }
|
addFactory { GetChaptersByMangaId(get()) }
|
||||||
|
addFactory { GetBookmarkedChaptersByMangaId(get(), get(), get()) }
|
||||||
addFactory { GetChapterByUrlAndMangaId(get()) }
|
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||||
addFactory { UpdateChapter(get()) }
|
addFactory { UpdateChapter(get()) }
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ class SyncChaptersWithSource(
|
|||||||
downloadManager.isChapterDownloaded(
|
downloadManager.isChapterDownloaded(
|
||||||
dbChapter.name,
|
dbChapter.name,
|
||||||
dbChapter.scanlator,
|
dbChapter.scanlator,
|
||||||
|
dbChapter.url,
|
||||||
/* SY --> */ manga.ogTitle /* SY <-- */,
|
/* SY --> */ manga.ogTitle /* SY <-- */,
|
||||||
manga.source,
|
manga.source,
|
||||||
)
|
)
|
||||||
@@ -126,12 +127,14 @@ class SyncChaptersWithSource(
|
|||||||
if (shouldRenameChapter) {
|
if (shouldRenameChapter) {
|
||||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
var toChangeChapter = dbChapter.copy(
|
var toChangeChapter = dbChapter.copy(
|
||||||
name = chapter.name,
|
name = chapter.name,
|
||||||
chapterNumber = chapter.chapterNumber,
|
chapterNumber = chapter.chapterNumber,
|
||||||
scanlator = chapter.scanlator,
|
scanlator = chapter.scanlator,
|
||||||
sourceOrder = chapter.sourceOrder,
|
sourceOrder = chapter.sourceOrder,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (chapter.dateUpload != 0L) {
|
if (chapter.dateUpload != 0L) {
|
||||||
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ fun List<Chapter>.applyFilters(
|
|||||||
val downloaded = downloadManager.isChapterDownloaded(
|
val downloaded = downloadManager.isChapterDownloaded(
|
||||||
chapter.name,
|
chapter.name,
|
||||||
chapter.scanlator,
|
chapter.scanlator,
|
||||||
|
chapter.url,
|
||||||
/* SY --> */ manga.ogTitle /* SY <-- */,
|
/* SY --> */ manga.ogTitle /* SY <-- */,
|
||||||
manga.source,
|
manga.source,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ fun Manga.toSManga(): SManga = SManga.create().also {
|
|||||||
it.description = ogDescription
|
it.description = ogDescription
|
||||||
it.genre = ogGenre.orEmpty().joinToString()
|
it.genre = ogGenre.orEmpty().joinToString()
|
||||||
it.status = ogStatus.toInt()
|
it.status = ogStatus.toInt()
|
||||||
//SY <--
|
// SY <--
|
||||||
it.thumbnail_url = thumbnailUrl
|
it.thumbnail_url = thumbnailUrl
|
||||||
it.initialized = initialized
|
it.initialized = initialized
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ package eu.kanade.domain.source.service
|
|||||||
|
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import mihon.domain.migration.models.MigrationFlag
|
||||||
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
|
import tachiyomi.core.common.preference.getEnum
|
||||||
|
import tachiyomi.core.common.preference.getLongArray
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
|
||||||
class SourcePreferences(
|
class SourcePreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun sourceDisplayMode() = preferenceStore.getObject(
|
fun sourceDisplayMode() = preferenceStore.getObjectFromString(
|
||||||
"pref_display_mode_catalogue",
|
"pref_display_mode_catalogue",
|
||||||
LibraryDisplayMode.default,
|
LibraryDisplayMode.default,
|
||||||
LibraryDisplayMode.Serializer::serialize,
|
LibraryDisplayMode.Serializer::serialize,
|
||||||
@@ -89,22 +91,6 @@ class SourcePreferences(
|
|||||||
WSRV_NL,
|
WSRV_NL,
|
||||||
}
|
}
|
||||||
|
|
||||||
fun migrateFlags() = preferenceStore.getInt("migrate_flags", Int.MAX_VALUE)
|
|
||||||
|
|
||||||
fun defaultMangaOrder() = preferenceStore.getString("default_manga_order", "")
|
|
||||||
|
|
||||||
fun migrationSources() = preferenceStore.getString("migrate_sources", "")
|
|
||||||
|
|
||||||
fun smartMigration() = preferenceStore.getBoolean("smart_migrate", false)
|
|
||||||
|
|
||||||
fun useSourceWithMost() = preferenceStore.getBoolean("use_source_with_most", false)
|
|
||||||
|
|
||||||
fun skipPreMigration() = preferenceStore.getBoolean(Preference.appStateKey("skip_pre_migration"), false)
|
|
||||||
|
|
||||||
fun hideNotFoundMigration() = preferenceStore.getBoolean("hide_not_found_migration", false)
|
|
||||||
|
|
||||||
fun showOnlyUpdatesMigration() = preferenceStore.getBoolean("show_only_updates_migration", false)
|
|
||||||
|
|
||||||
fun allowLocalSourceHiddenFolders() = preferenceStore.getBoolean("allow_local_source_hidden_folders", false)
|
fun allowLocalSourceHiddenFolders() = preferenceStore.getBoolean("allow_local_source_hidden_folders", false)
|
||||||
|
|
||||||
fun preferredMangaDexId() = preferenceStore.getString("preferred_mangaDex_id", "0")
|
fun preferredMangaDexId() = preferenceStore.getString("preferred_mangaDex_id", "0")
|
||||||
@@ -116,4 +102,21 @@ class SourcePreferences(
|
|||||||
|
|
||||||
fun recommendationSearchFlags() = preferenceStore.getInt("rec_search_flags", Int.MAX_VALUE)
|
fun recommendationSearchFlags() = preferenceStore.getInt("rec_search_flags", Int.MAX_VALUE)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
|
fun migrationSources() = preferenceStore.getLongArray("migration_sources", emptyList())
|
||||||
|
|
||||||
|
fun migrationFlags() = preferenceStore.getObjectFromInt(
|
||||||
|
key = "migration_flags",
|
||||||
|
defaultValue = MigrationFlag.entries.toSet(),
|
||||||
|
serializer = { MigrationFlag.toBit(it) },
|
||||||
|
deserializer = { value: Int -> MigrationFlag.fromBit(value) },
|
||||||
|
)
|
||||||
|
|
||||||
|
fun migrationDeepSearchMode() = preferenceStore.getBoolean("migration_deep_search", false)
|
||||||
|
|
||||||
|
fun migrationPrioritizeByChapters() = preferenceStore.getBoolean("migration_prioritize_by_chapters", false)
|
||||||
|
|
||||||
|
fun migrationHideUnmatched() = preferenceStore.getBoolean("migration_hide_unmatched", false)
|
||||||
|
|
||||||
|
fun migrationHideWithoutUpdates() = preferenceStore.getBoolean("migration_hide_without_updates", false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ class UiPreferences(
|
|||||||
|
|
||||||
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
|
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
|
||||||
|
|
||||||
|
fun imagesInDescription() = preferenceStore.getBoolean("pref_render_images_description", true)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
||||||
fun expandFilters() = preferenceStore.getBoolean("eh_expand_filters", false)
|
fun expandFilters() = preferenceStore.getBoolean("eh_expand_filters", false)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import tachiyomi.i18n.MR
|
|||||||
enum class AppTheme(val titleRes: StringResource?) {
|
enum class AppTheme(val titleRes: StringResource?) {
|
||||||
DEFAULT(MR.strings.label_default),
|
DEFAULT(MR.strings.label_default),
|
||||||
MONET(MR.strings.theme_monet),
|
MONET(MR.strings.theme_monet),
|
||||||
|
CATPPUCCIN(MR.strings.theme_catppuccin),
|
||||||
GREEN_APPLE(MR.strings.theme_greenapple),
|
GREEN_APPLE(MR.strings.theme_greenapple),
|
||||||
LAVENDER(MR.strings.theme_lavender),
|
LAVENDER(MR.strings.theme_lavender),
|
||||||
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
||||||
|
|||||||
@@ -351,13 +351,17 @@ private fun ExtensionItemContent(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
) {
|
) {
|
||||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||||
|
var hasAlreadyShownAnElement by remember { mutableStateOf(false) }
|
||||||
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
||||||
|
hasAlreadyShownAnElement = true
|
||||||
Text(
|
Text(
|
||||||
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extension.versionName.isNotEmpty()) {
|
if (extension.versionName.isNotEmpty()) {
|
||||||
|
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
|
||||||
|
hasAlreadyShownAnElement = true
|
||||||
Text(
|
Text(
|
||||||
text = extension.versionName,
|
text = extension.versionName,
|
||||||
)
|
)
|
||||||
@@ -373,6 +377,8 @@ private fun ExtensionItemContent(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (warning != null) {
|
if (warning != null) {
|
||||||
|
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
|
||||||
|
hasAlreadyShownAnElement = true
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(warning).uppercase(),
|
text = stringResource(warning).uppercase(),
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
@@ -380,6 +386,12 @@ private fun ExtensionItemContent(
|
|||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (extension is Extension.Installed && !extension.isShared) {
|
||||||
|
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.ext_installer_private),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!installStep.isCompleted()) {
|
if (!installStep.isCompleted()) {
|
||||||
DotSeparatorNoSpaceText()
|
DotSeparatorNoSpaceText()
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ fun GlobalSearchScreen(
|
|||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
hideSourceFilter = false,
|
||||||
sourceFilter = state.sourceFilter,
|
sourceFilter = state.sourceFilter,
|
||||||
onChangeSearchFilter = onChangeSearchFilter,
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
onlyShowHasResults = state.onlyShowHasResults,
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
|
||||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.i18n.MR
|
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MigrateMangaScreen(
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
title: String?,
|
|
||||||
state: MigrateMangaScreenModel.State,
|
|
||||||
onClickItem: (Manga) -> Unit,
|
|
||||||
onClickCover: (Manga) -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
topBar = { scrollBehavior ->
|
|
||||||
AppBar(
|
|
||||||
title = title,
|
|
||||||
navigateUp = navigateUp,
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { contentPadding ->
|
|
||||||
if (state.isEmpty) {
|
|
||||||
EmptyScreen(
|
|
||||||
stringRes = MR.strings.empty_screen,
|
|
||||||
modifier = Modifier.padding(contentPadding),
|
|
||||||
)
|
|
||||||
return@Scaffold
|
|
||||||
}
|
|
||||||
|
|
||||||
MigrateMangaContent(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
state = state,
|
|
||||||
onClickItem = onClickItem,
|
|
||||||
onClickCover = onClickCover,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun MigrateMangaContent(
|
|
||||||
contentPadding: PaddingValues,
|
|
||||||
state: MigrateMangaScreenModel.State,
|
|
||||||
onClickItem: (Manga) -> Unit,
|
|
||||||
onClickCover: (Manga) -> Unit,
|
|
||||||
) {
|
|
||||||
FastScrollLazyColumn(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
) {
|
|
||||||
items(state.titles) { manga ->
|
|
||||||
MigrateMangaItem(
|
|
||||||
manga = manga,
|
|
||||||
onClickItem = onClickItem,
|
|
||||||
onClickCover = onClickCover,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun MigrateMangaItem(
|
|
||||||
manga: Manga,
|
|
||||||
onClickItem: (Manga) -> Unit,
|
|
||||||
onClickCover: (Manga) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
BaseMangaListItem(
|
|
||||||
modifier = modifier,
|
|
||||||
manga = manga,
|
|
||||||
onClickItem = { onClickItem(manga) },
|
|
||||||
onClickCover = { onClickCover(manga) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,7 @@ fun MigrateSearchScreen(
|
|||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
hideSourceFilter = true,
|
||||||
sourceFilter = state.sourceFilter,
|
sourceFilter = state.sourceFilter,
|
||||||
onChangeSearchFilter = onChangeSearchFilter,
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
onlyShowHasResults = state.onlyShowHasResults,
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
||||||
import androidx.compose.material.icons.outlined.ArrowForward
|
|
||||||
import androidx.compose.material.icons.outlined.ContentCopy
|
import androidx.compose.material.icons.outlined.ContentCopy
|
||||||
import androidx.compose.material.icons.outlined.CopyAll
|
import androidx.compose.material.icons.outlined.CopyAll
|
||||||
import androidx.compose.material.icons.outlined.Done
|
import androidx.compose.material.icons.outlined.Done
|
||||||
|
|||||||
+13
-12
@@ -30,8 +30,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import com.gowtham.ratingbar.RatingBar
|
import com.gowtham.ratingbar.ComposeStars
|
||||||
import com.gowtham.ratingbar.RatingBarConfig
|
import com.gowtham.ratingbar.RatingBarStyle
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
import exh.metadata.MetadataUtil
|
import exh.metadata.MetadataUtil
|
||||||
@@ -222,17 +222,18 @@ fun BrowseSourceEHentaiListItem(
|
|||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
) {
|
) {
|
||||||
RatingBar(
|
ComposeStars(
|
||||||
value = rating,
|
value = rating,
|
||||||
onValueChange = {},
|
numOfStars = 5,
|
||||||
onRatingChanged = {},
|
size = 18.dp,
|
||||||
config = RatingBarConfig().apply {
|
spaceBetween = 2.dp,
|
||||||
isIndicator(true)
|
hideInactiveStars = false,
|
||||||
numStars(5)
|
style = RatingBarStyle.Fill(
|
||||||
size(18.dp)
|
activeColor = Color(0xFF005ED7),
|
||||||
activeColor(Color(0xFF005ED7))
|
inActiveColor = Color(0xE1E2ECFF),
|
||||||
inactiveColor(Color(0xE1E2ECFF))
|
),
|
||||||
},
|
painterEmpty = null,
|
||||||
|
painterFilled = null,
|
||||||
)
|
)
|
||||||
val color = genre?.first?.color
|
val color = genre?.first?.color
|
||||||
val res = genre?.second
|
val res = genre?.second
|
||||||
|
|||||||
+2
-2
@@ -3,12 +3,12 @@ package eu.kanade.presentation.browse.components
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.SmallExtendedFloatingActionButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -17,7 +17,7 @@ fun BrowseSourceFloatingActionButton(
|
|||||||
onFabClick: () -> Unit,
|
onFabClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
ExtendedFloatingActionButton(
|
SmallExtendedFloatingActionButton(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
text = {
|
text = {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.filled.ViewList
|
import androidx.compose.material.icons.automirrored.filled.ViewList
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Help
|
import androidx.compose.material.icons.automirrored.outlined.Help
|
||||||
import androidx.compose.material.icons.filled.ViewModule
|
import androidx.compose.material.icons.filled.ViewModule
|
||||||
import androidx.compose.material.icons.outlined.Help
|
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ fun GlobalSearchToolbar(
|
|||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
hideSourceFilter: Boolean,
|
||||||
sourceFilter: SourceFilter,
|
sourceFilter: SourceFilter,
|
||||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
onlyShowHasResults: Boolean,
|
onlyShowHasResults: Boolean,
|
||||||
@@ -73,38 +74,40 @@ fun GlobalSearchToolbar(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
// TODO: make this UX better; it only applies when triggering a new search
|
// TODO: make this UX better; it only applies when triggering a new search
|
||||||
FilterChip(
|
if (!hideSourceFilter) {
|
||||||
selected = sourceFilter == SourceFilter.PinnedOnly,
|
FilterChip(
|
||||||
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
|
selected = sourceFilter == SourceFilter.PinnedOnly,
|
||||||
leadingIcon = {
|
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
|
||||||
Icon(
|
leadingIcon = {
|
||||||
imageVector = Icons.Outlined.PushPin,
|
Icon(
|
||||||
contentDescription = null,
|
imageVector = Icons.Outlined.PushPin,
|
||||||
modifier = Modifier
|
contentDescription = null,
|
||||||
.size(FilterChipDefaults.IconSize),
|
modifier = Modifier
|
||||||
)
|
.size(FilterChipDefaults.IconSize),
|
||||||
},
|
)
|
||||||
label = {
|
},
|
||||||
Text(text = stringResource(MR.strings.pinned_sources))
|
label = {
|
||||||
},
|
Text(text = stringResource(MR.strings.pinned_sources))
|
||||||
)
|
},
|
||||||
FilterChip(
|
)
|
||||||
selected = sourceFilter == SourceFilter.All,
|
FilterChip(
|
||||||
onClick = { onChangeSearchFilter(SourceFilter.All) },
|
selected = sourceFilter == SourceFilter.All,
|
||||||
leadingIcon = {
|
onClick = { onChangeSearchFilter(SourceFilter.All) },
|
||||||
Icon(
|
leadingIcon = {
|
||||||
imageVector = Icons.Outlined.DoneAll,
|
Icon(
|
||||||
contentDescription = null,
|
imageVector = Icons.Outlined.DoneAll,
|
||||||
modifier = Modifier
|
contentDescription = null,
|
||||||
.size(FilterChipDefaults.IconSize),
|
modifier = Modifier
|
||||||
)
|
.size(FilterChipDefaults.IconSize),
|
||||||
},
|
)
|
||||||
label = {
|
},
|
||||||
Text(text = stringResource(MR.strings.all))
|
label = {
|
||||||
},
|
Text(text = stringResource(MR.strings.all))
|
||||||
)
|
},
|
||||||
|
)
|
||||||
|
|
||||||
VerticalDivider()
|
VerticalDivider()
|
||||||
|
}
|
||||||
|
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = onlyShowHasResults,
|
selected = onlyShowHasResults,
|
||||||
|
|||||||
+2
-2
@@ -4,11 +4,11 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Add
|
import androidx.compose.material.icons.outlined.Add
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.SmallExtendedFloatingActionButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ fun CategoryFloatingActionButton(
|
|||||||
onCreate: () -> Unit,
|
onCreate: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
ExtendedFloatingActionButton(
|
SmallExtendedFloatingActionButton(
|
||||||
text = { Text(text = stringResource(MR.strings.action_add)) },
|
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||||
onClick = onCreate,
|
onClick = onCreate,
|
||||||
|
|||||||
-1
@@ -7,7 +7,6 @@ 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.automirrored.outlined.Label
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.Label
|
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import androidx.compose.material.icons.automirrored.outlined.Label
|
|||||||
import androidx.compose.material.icons.outlined.ArrowDropDown
|
import androidx.compose.material.icons.outlined.ArrowDropDown
|
||||||
import androidx.compose.material.icons.outlined.ArrowDropUp
|
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.Label
|
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
|||||||
-1
@@ -9,7 +9,6 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
import androidx.compose.material.icons.outlined.Label
|
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.compose.animation.SizeTransform
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
@@ -32,9 +32,10 @@ fun NavigatorAdaptiveSheet(
|
|||||||
) {
|
) {
|
||||||
ScreenTransition(
|
ScreenTransition(
|
||||||
navigator = sheetNavigator,
|
navigator = sheetNavigator,
|
||||||
enterTransition = { fadeIn(animationSpec = tween(220, delayMillis = 90)) },
|
transition = {
|
||||||
exitTransition = { fadeOut(animationSpec = tween(90)) },
|
fadeIn(animationSpec = tween(220, delayMillis = 90)) togetherWith
|
||||||
sizeTransform = { SizeTransform() },
|
fadeOut(animationSpec = tween(90))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.PlainTooltip
|
import androidx.compose.material3.PlainTooltip
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
|
import androidx.compose.material3.TooltipAnchorPosition
|
||||||
import androidx.compose.material3.TooltipBox
|
import androidx.compose.material3.TooltipBox
|
||||||
import androidx.compose.material3.TooltipDefaults
|
import androidx.compose.material3.TooltipDefaults.rememberTooltipPositionProvider
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
@@ -195,7 +196,7 @@ fun AppBarActions(
|
|||||||
|
|
||||||
actions.filterIsInstance<AppBar.Action>().map {
|
actions.filterIsInstance<AppBar.Action>().map {
|
||||||
TooltipBox(
|
TooltipBox(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||||
tooltip = {
|
tooltip = {
|
||||||
PlainTooltip {
|
PlainTooltip {
|
||||||
Text(it.title)
|
Text(it.title)
|
||||||
@@ -220,7 +221,7 @@ fun AppBarActions(
|
|||||||
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
|
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
|
||||||
if (overflowActions.isNotEmpty()) {
|
if (overflowActions.isNotEmpty()) {
|
||||||
TooltipBox(
|
TooltipBox(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||||
tooltip = {
|
tooltip = {
|
||||||
PlainTooltip {
|
PlainTooltip {
|
||||||
Text(stringResource(MR.strings.action_menu_overflow_description))
|
Text(stringResource(MR.strings.action_menu_overflow_description))
|
||||||
@@ -349,7 +350,7 @@ fun SearchToolbar(
|
|||||||
// Don't show search action
|
// Don't show search action
|
||||||
} else if (searchQuery == null) {
|
} else if (searchQuery == null) {
|
||||||
TooltipBox(
|
TooltipBox(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||||
tooltip = {
|
tooltip = {
|
||||||
PlainTooltip {
|
PlainTooltip {
|
||||||
Text(stringResource(MR.strings.action_search))
|
Text(stringResource(MR.strings.action_search))
|
||||||
@@ -369,7 +370,7 @@ fun SearchToolbar(
|
|||||||
}
|
}
|
||||||
} else if (searchQuery.isNotEmpty()) {
|
} else if (searchQuery.isNotEmpty()) {
|
||||||
TooltipBox(
|
TooltipBox(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||||
tooltip = {
|
tooltip = {
|
||||||
PlainTooltip {
|
PlainTooltip {
|
||||||
Text(stringResource(MR.strings.action_reset))
|
Text(stringResource(MR.strings.action_reset))
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fun relativeDateText(
|
|||||||
Instant.ofEpochMilli(dateEpochMillis),
|
Instant.ofEpochMilli(dateEpochMillis),
|
||||||
ZoneId.systemDefault(),
|
ZoneId.systemDefault(),
|
||||||
)
|
)
|
||||||
.takeIf { dateEpochMillis > 0L },
|
.takeIf { dateEpochMillis != 0L },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import androidx.compose.material3.DropdownMenuItem
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
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
|
||||||
@@ -12,10 +13,44 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadDropdownMenu(
|
fun DownloadDropdownMenu(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onDownloadClicked: (DownloadAction) -> Unit,
|
onDownloadClicked: (DownloadAction) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
offset: DpOffset? = null,
|
||||||
|
) {
|
||||||
|
if (offset != null) {
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
modifier = modifier,
|
||||||
|
offset = offset,
|
||||||
|
content = {
|
||||||
|
DownloadDropdownMenuItems(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onDownloadClicked = onDownloadClicked,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
modifier = modifier,
|
||||||
|
content = {
|
||||||
|
DownloadDropdownMenuItems(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onDownloadClicked = onDownloadClicked,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DownloadDropdownMenuItems(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onDownloadClicked: (DownloadAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
val options = persistentListOf(
|
val options = persistentListOf(
|
||||||
DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1),
|
DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1),
|
||||||
@@ -23,21 +58,16 @@ fun DownloadDropdownMenu(
|
|||||||
DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10),
|
DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10),
|
||||||
DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25),
|
DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25),
|
||||||
DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread),
|
DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread),
|
||||||
|
DownloadAction.BOOKMARKED_CHAPTERS to stringResource(MR.strings.download_bookmarked),
|
||||||
)
|
)
|
||||||
|
|
||||||
DropdownMenu(
|
options.map { (downloadAction, string) ->
|
||||||
expanded = expanded,
|
DropdownMenuItem(
|
||||||
onDismissRequest = onDismissRequest,
|
text = { Text(text = string) },
|
||||||
modifier = modifier,
|
onClick = {
|
||||||
) {
|
onDownloadClicked(downloadAction)
|
||||||
options.map { (downloadAction, string) ->
|
onDismissRequest()
|
||||||
DropdownMenuItem(
|
},
|
||||||
text = { Text(text = string) },
|
)
|
||||||
onClick = {
|
|
||||||
onDownloadClicked(downloadAction)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ private fun ColumnScope.DisplayPage(
|
|||||||
value = columns,
|
value = columns,
|
||||||
valueRange = 0..10,
|
valueRange = 0..10,
|
||||||
label = stringResource(MR.strings.pref_library_columns),
|
label = stringResource(MR.strings.pref_library_columns),
|
||||||
valueText = if (columns > 0) {
|
valueString = if (columns > 0) {
|
||||||
columns.toString()
|
columns.toString()
|
||||||
} else {
|
} else {
|
||||||
stringResource(MR.strings.label_auto)
|
stringResource(MR.strings.label_auto)
|
||||||
|
|||||||
+2
-3
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.util.fastAny
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@@ -15,7 +14,7 @@ internal fun LibraryComfortableGrid(
|
|||||||
items: List<LibraryItem>,
|
items: List<LibraryItem>,
|
||||||
columns: Int,
|
columns: Int,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
selection: List<LibraryManga>,
|
selection: Set<Long>,
|
||||||
onClick: (LibraryManga) -> Unit,
|
onClick: (LibraryManga) -> Unit,
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
onLongClick: (LibraryManga) -> Unit,
|
||||||
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||||
@@ -35,7 +34,7 @@ internal fun LibraryComfortableGrid(
|
|||||||
) { libraryItem ->
|
) { libraryItem ->
|
||||||
val manga = libraryItem.libraryManga.manga
|
val manga = libraryItem.libraryManga.manga
|
||||||
MangaComfortableGridItem(
|
MangaComfortableGridItem(
|
||||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
isSelected = manga.id in selection,
|
||||||
title = manga.title,
|
title = manga.title,
|
||||||
coverData = MangaCover(
|
coverData = MangaCover(
|
||||||
mangaId = manga.id,
|
mangaId = manga.id,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.util.fastAny
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@@ -16,7 +15,7 @@ internal fun LibraryCompactGrid(
|
|||||||
showTitle: Boolean,
|
showTitle: Boolean,
|
||||||
columns: Int,
|
columns: Int,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
selection: List<LibraryManga>,
|
selection: Set<Long>,
|
||||||
onClick: (LibraryManga) -> Unit,
|
onClick: (LibraryManga) -> Unit,
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
onLongClick: (LibraryManga) -> Unit,
|
||||||
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||||
@@ -36,7 +35,7 @@ internal fun LibraryCompactGrid(
|
|||||||
) { libraryItem ->
|
) { libraryItem ->
|
||||||
val manga = libraryItem.libraryManga.manga
|
val manga = libraryItem.libraryManga.manga
|
||||||
MangaCompactGridItem(
|
MangaCompactGridItem(
|
||||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
isSelected = manga.id in selection,
|
||||||
title = manga.title.takeIf { showTitle },
|
title = manga.title.takeIf { showTitle },
|
||||||
coverData = MangaCover(
|
coverData = MangaCover(
|
||||||
mangaId = manga.id,
|
mangaId = manga.id,
|
||||||
|
|||||||
@@ -29,22 +29,22 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
fun LibraryContent(
|
fun LibraryContent(
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
selection: List<LibraryManga>,
|
selection: Set<Long>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
currentPage: () -> Int,
|
currentPage: Int,
|
||||||
hasActiveFilters: Boolean,
|
hasActiveFilters: Boolean,
|
||||||
showPageTabs: Boolean,
|
showPageTabs: Boolean,
|
||||||
onChangeCurrentPage: (Int) -> Unit,
|
onChangeCurrentPage: (Int) -> Unit,
|
||||||
onMangaClicked: (Long) -> Unit,
|
onClickManga: (Long) -> Unit,
|
||||||
onContinueReadingClicked: ((LibraryManga) -> Unit)?,
|
onContinueReadingClicked: ((LibraryManga) -> Unit)?,
|
||||||
onToggleSelection: (LibraryManga) -> Unit,
|
onToggleSelection: (Category, LibraryManga) -> Unit,
|
||||||
onToggleRangeSelection: (LibraryManga) -> Unit,
|
onToggleRangeSelection: (Category, LibraryManga) -> Unit,
|
||||||
onRefresh: (Category?) -> Boolean,
|
onRefresh: () -> Boolean,
|
||||||
onGlobalSearchClicked: () -> Unit,
|
onGlobalSearchClicked: () -> Unit,
|
||||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
getItemCountForCategory: (Category) -> Int?,
|
||||||
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
|
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
|
||||||
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
||||||
getLibraryForPage: (Int) -> List<LibraryItem>,
|
getItemsForCategory: (Category) -> List<LibraryItem>,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
@@ -53,15 +53,14 @@ fun LibraryContent(
|
|||||||
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
// SY -->
|
val coercedCurrentPage = remember(categories, currentPage) { currentPage.coerceIn(0, categories.lastIndex) }
|
||||||
val coercedCurrentPage = remember(categories) { currentPage().coerceIn(0, categories.lastIndex) }
|
|
||||||
val pagerState = rememberPagerState(coercedCurrentPage) { categories.size }
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
val pagerState = rememberPagerState(coercedCurrentPage) { categories.size }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
||||||
|
|
||||||
if (showPageTabs && categories.size > 1) {
|
if (showPageTabs && categories.isNotEmpty() && (categories.size > 1 || !categories.first().isSystemCategory)) {
|
||||||
LaunchedEffect(categories) {
|
LaunchedEffect(categories) {
|
||||||
if (categories.size <= pagerState.currentPage) {
|
if (categories.size <= pagerState.currentPage) {
|
||||||
pagerState.scrollToPage(categories.size - 1)
|
pagerState.scrollToPage(categories.size - 1)
|
||||||
@@ -70,23 +69,20 @@ fun LibraryContent(
|
|||||||
LibraryTabs(
|
LibraryTabs(
|
||||||
categories = categories,
|
categories = categories,
|
||||||
pagerState = pagerState,
|
pagerState = pagerState,
|
||||||
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
|
getItemCountForCategory = getItemCountForCategory,
|
||||||
) { scope.launch { pagerState.animateScrollToPage(it) } }
|
onTabItemClick = {
|
||||||
}
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(it)
|
||||||
val notSelectionMode = selection.isEmpty()
|
}
|
||||||
val onClickManga = { manga: LibraryManga ->
|
},
|
||||||
if (notSelectionMode) {
|
)
|
||||||
onMangaClicked(manga.manga.id)
|
|
||||||
} else {
|
|
||||||
onToggleSelection(manga)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = isRefreshing,
|
refreshing = isRefreshing,
|
||||||
|
enabled = selection.isEmpty(),
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
val started = onRefresh(categories.getOrNull(currentPage()) ?: return@PullRefresh)
|
val started = onRefresh()
|
||||||
if (!started) return@PullRefresh
|
if (!started) return@PullRefresh
|
||||||
scope.launch {
|
scope.launch {
|
||||||
// Fake refresh status but hide it after a second as it's a long running task
|
// Fake refresh status but hide it after a second as it's a long running task
|
||||||
@@ -95,19 +91,25 @@ fun LibraryContent(
|
|||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = notSelectionMode,
|
|
||||||
) {
|
) {
|
||||||
LibraryPager(
|
LibraryPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
||||||
hasActiveFilters = hasActiveFilters,
|
hasActiveFilters = hasActiveFilters,
|
||||||
selectedManga = selection,
|
selection = selection,
|
||||||
searchQuery = searchQuery,
|
searchQuery = searchQuery,
|
||||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
onGlobalSearchClicked = onGlobalSearchClicked,
|
||||||
|
getCategoryForPage = { page -> categories[page] },
|
||||||
getDisplayMode = getDisplayMode,
|
getDisplayMode = getDisplayMode,
|
||||||
getColumnsForOrientation = getColumnsForOrientation,
|
getColumnsForOrientation = getColumnsForOrientation,
|
||||||
getLibraryForPage = getLibraryForPage,
|
getItemsForCategory = getItemsForCategory,
|
||||||
onClickManga = onClickManga,
|
onClickManga = { category, manga ->
|
||||||
|
if (selection.isNotEmpty()) {
|
||||||
|
onToggleSelection(category, manga)
|
||||||
|
} else {
|
||||||
|
onClickManga(manga.manga.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
onLongClickManga = onToggleRangeSelection,
|
onLongClickManga = onToggleRangeSelection,
|
||||||
onClickContinueReading = onContinueReadingClicked,
|
onClickContinueReading = onContinueReadingClicked,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastAny
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@@ -18,7 +17,7 @@ import tachiyomi.presentation.core.util.plus
|
|||||||
internal fun LibraryList(
|
internal fun LibraryList(
|
||||||
items: List<LibraryItem>,
|
items: List<LibraryItem>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
selection: List<LibraryManga>,
|
selection: Set<Long>,
|
||||||
onClick: (LibraryManga) -> Unit,
|
onClick: (LibraryManga) -> Unit,
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
onLongClick: (LibraryManga) -> Unit,
|
||||||
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||||
@@ -45,7 +44,7 @@ internal fun LibraryList(
|
|||||||
) { libraryItem ->
|
) { libraryItem ->
|
||||||
val manga = libraryItem.libraryManga.manga
|
val manga = libraryItem.libraryManga.manga
|
||||||
MangaListItem(
|
MangaListItem(
|
||||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
isSelected = manga.id in selection,
|
||||||
title = manga.title,
|
title = manga.title,
|
||||||
coverData = MangaCover(
|
coverData = MangaCover(
|
||||||
mangaId = manga.id,
|
mangaId = manga.id,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import androidx.compose.ui.platform.LocalConfiguration
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.core.preference.PreferenceMutableState
|
import eu.kanade.core.preference.PreferenceMutableState
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -31,14 +32,15 @@ fun LibraryPager(
|
|||||||
state: PagerState,
|
state: PagerState,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
hasActiveFilters: Boolean,
|
hasActiveFilters: Boolean,
|
||||||
selectedManga: List<LibraryManga>,
|
selection: Set<Long>,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onGlobalSearchClicked: () -> Unit,
|
onGlobalSearchClicked: () -> Unit,
|
||||||
|
getCategoryForPage: (Int) -> Category,
|
||||||
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
|
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
|
||||||
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
||||||
getLibraryForPage: (Int) -> List<LibraryItem>,
|
getItemsForCategory: (Category) -> List<LibraryItem>,
|
||||||
onClickManga: (LibraryManga) -> Unit,
|
onClickManga: (Category, LibraryManga) -> Unit,
|
||||||
onLongClickManga: (LibraryManga) -> Unit,
|
onLongClickManga: (Category, LibraryManga) -> Unit,
|
||||||
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||||
) {
|
) {
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
@@ -50,9 +52,10 @@ fun LibraryPager(
|
|||||||
// To make sure only one offscreen page is being composed
|
// To make sure only one offscreen page is being composed
|
||||||
return@HorizontalPager
|
return@HorizontalPager
|
||||||
}
|
}
|
||||||
val library = getLibraryForPage(page)
|
val category = getCategoryForPage(page)
|
||||||
|
val items = getItemsForCategory(category)
|
||||||
|
|
||||||
if (library.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
LibraryPagerEmptyScreen(
|
LibraryPagerEmptyScreen(
|
||||||
searchQuery = searchQuery,
|
searchQuery = searchQuery,
|
||||||
hasActiveFilters = hasActiveFilters,
|
hasActiveFilters = hasActiveFilters,
|
||||||
@@ -72,12 +75,15 @@ fun LibraryPager(
|
|||||||
remember { mutableIntStateOf(0) }
|
remember { mutableIntStateOf(0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val onClickManga: (LibraryManga) -> Unit = { onClickManga(category, it) }
|
||||||
|
val onLongClickManga: (LibraryManga) -> Unit = { onLongClickManga(category, it) }
|
||||||
|
|
||||||
when (displayMode) {
|
when (displayMode) {
|
||||||
LibraryDisplayMode.List -> {
|
LibraryDisplayMode.List -> {
|
||||||
LibraryList(
|
LibraryList(
|
||||||
items = library,
|
items = items,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
selection = selectedManga,
|
selection = selection,
|
||||||
onClick = onClickManga,
|
onClick = onClickManga,
|
||||||
onLongClick = onLongClickManga,
|
onLongClick = onLongClickManga,
|
||||||
onClickContinueReading = onClickContinueReading,
|
onClickContinueReading = onClickContinueReading,
|
||||||
@@ -87,11 +93,11 @@ fun LibraryPager(
|
|||||||
}
|
}
|
||||||
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
|
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
|
||||||
LibraryCompactGrid(
|
LibraryCompactGrid(
|
||||||
items = library,
|
items = items,
|
||||||
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
|
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
selection = selectedManga,
|
selection = selection,
|
||||||
onClick = onClickManga,
|
onClick = onClickManga,
|
||||||
onLongClick = onLongClickManga,
|
onLongClick = onLongClickManga,
|
||||||
onClickContinueReading = onClickContinueReading,
|
onClickContinueReading = onClickContinueReading,
|
||||||
@@ -101,10 +107,10 @@ fun LibraryPager(
|
|||||||
}
|
}
|
||||||
LibraryDisplayMode.ComfortableGrid -> {
|
LibraryDisplayMode.ComfortableGrid -> {
|
||||||
LibraryComfortableGrid(
|
LibraryComfortableGrid(
|
||||||
items = library,
|
items = items,
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
selection = selectedManga,
|
selection = selection,
|
||||||
onClick = onClickManga,
|
onClick = onClickManga,
|
||||||
onLongClick = onLongClickManga,
|
onLongClick = onLongClickManga,
|
||||||
onClickContinueReading = onClickContinueReading,
|
onClickContinueReading = onClickContinueReading,
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ import tachiyomi.presentation.core.components.material.TabText
|
|||||||
internal fun LibraryTabs(
|
internal fun LibraryTabs(
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
pagerState: PagerState,
|
pagerState: PagerState,
|
||||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
getItemCountForCategory: (Category) -> Int?,
|
||||||
onTabItemClick: (Int) -> Unit,
|
onTabItemClick: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
||||||
Column(
|
Column(modifier = Modifier.zIndex(2f)) {
|
||||||
modifier = Modifier.zIndex(1f),
|
|
||||||
) {
|
|
||||||
PrimaryScrollableTabRow(
|
PrimaryScrollableTabRow(
|
||||||
selectedTabIndex = currentPageIndex,
|
selectedTabIndex = currentPageIndex,
|
||||||
edgePadding = 0.dp,
|
edgePadding = 0.dp,
|
||||||
@@ -39,7 +37,7 @@ internal fun LibraryTabs(
|
|||||||
text = {
|
text = {
|
||||||
TabText(
|
TabText(
|
||||||
text = category.visualName,
|
text = category.visualName,
|
||||||
badgeCount = getNumberOfMangaForCategory(category),
|
badgeCount = getItemCountForCategory(category),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package eu.kanade.presentation.manga
|
package eu.kanade.presentation.manga
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@@ -27,9 +24,11 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.SmallExtendedFloatingActionButton
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.animateFloatingActionButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -69,6 +68,7 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.Lanraragi
|
||||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
import eu.kanade.tachiyomi.source.online.all.NHentai
|
import eu.kanade.tachiyomi.source.online.all.NHentai
|
||||||
import eu.kanade.tachiyomi.source.online.english.EightMuses
|
import eu.kanade.tachiyomi.source.online.english.EightMuses
|
||||||
@@ -87,6 +87,7 @@ import exh.source.isEhBasedManga
|
|||||||
import exh.ui.metadata.adapters.EHentaiDescription
|
import exh.ui.metadata.adapters.EHentaiDescription
|
||||||
import exh.ui.metadata.adapters.EightMusesDescription
|
import exh.ui.metadata.adapters.EightMusesDescription
|
||||||
import exh.ui.metadata.adapters.HBrowseDescription
|
import exh.ui.metadata.adapters.HBrowseDescription
|
||||||
|
import exh.ui.metadata.adapters.LanraragiDescription
|
||||||
import exh.ui.metadata.adapters.MangaDexDescription
|
import exh.ui.metadata.adapters.MangaDexDescription
|
||||||
import exh.ui.metadata.adapters.NHentaiDescription
|
import exh.ui.metadata.adapters.NHentaiDescription
|
||||||
import exh.ui.metadata.adapters.PururinDescription
|
import exh.ui.metadata.adapters.PururinDescription
|
||||||
@@ -99,7 +100,6 @@ import tachiyomi.domain.source.model.StubSource
|
|||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.TwoPanelBox
|
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||||
import tachiyomi.presentation.core.components.VerticalFastScroller
|
import tachiyomi.presentation.core.components.VerticalFastScroller
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
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
|
||||||
@@ -165,7 +165,7 @@ fun MangaScreen(
|
|||||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -329,7 +329,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -416,25 +416,23 @@ private fun MangaScreenSmallImpl(
|
|||||||
val isFABVisible = remember(chapters) {
|
val isFABVisible = remember(chapters) {
|
||||||
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
||||||
}
|
}
|
||||||
AnimatedVisibility(
|
SmallExtendedFloatingActionButton(
|
||||||
visible = isFABVisible,
|
text = {
|
||||||
enter = fadeIn(),
|
val isReading = remember(state.chapters) {
|
||||||
exit = fadeOut(),
|
state.chapters.fastAny { it.chapter.read }
|
||||||
) {
|
}
|
||||||
ExtendedFloatingActionButton(
|
Text(
|
||||||
text = {
|
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
|
||||||
val isReading = remember(state.chapters) {
|
)
|
||||||
state.chapters.fastAny { it.chapter.read }
|
},
|
||||||
}
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
Text(
|
onClick = onContinueReading,
|
||||||
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
|
expanded = chapterListState.shouldExpandFAB(),
|
||||||
)
|
modifier = Modifier.animateFloatingActionButton(
|
||||||
},
|
visible = isFABVisible,
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
alignment = Alignment.BottomEnd,
|
||||||
onClick = onContinueReading,
|
),
|
||||||
expanded = chapterListState.shouldExpandFAB(),
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
val topPadding = contentPadding.calculateTopPadding()
|
val topPadding = contentPadding.calculateTopPadding()
|
||||||
@@ -527,7 +525,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
// SY -->
|
// SY -->
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
||||||
SearchMetadataChips(state.meta, state.source, state.manga.genre)
|
SearchMetadataChips(state.meta, state.source.id, state.manga.genre)
|
||||||
},
|
},
|
||||||
// SY <--
|
// SY <--
|
||||||
)
|
)
|
||||||
@@ -652,7 +650,7 @@ fun MangaScreenLargeImpl(
|
|||||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -735,27 +733,25 @@ fun MangaScreenLargeImpl(
|
|||||||
val isFABVisible = remember(chapters) {
|
val isFABVisible = remember(chapters) {
|
||||||
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
||||||
}
|
}
|
||||||
AnimatedVisibility(
|
SmallExtendedFloatingActionButton(
|
||||||
visible = isFABVisible,
|
text = {
|
||||||
enter = fadeIn(),
|
val isReading = remember(state.chapters) {
|
||||||
exit = fadeOut(),
|
state.chapters.fastAny { it.chapter.read }
|
||||||
) {
|
}
|
||||||
ExtendedFloatingActionButton(
|
Text(
|
||||||
text = {
|
text = stringResource(
|
||||||
val isReading = remember(state.chapters) {
|
if (isReading) MR.strings.action_resume else MR.strings.action_start,
|
||||||
state.chapters.fastAny { it.chapter.read }
|
),
|
||||||
}
|
)
|
||||||
Text(
|
},
|
||||||
text = stringResource(
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
if (isReading) MR.strings.action_resume else MR.strings.action_start,
|
onClick = onContinueReading,
|
||||||
),
|
expanded = chapterListState.shouldExpandFAB(),
|
||||||
)
|
modifier = Modifier.animateFloatingActionButton(
|
||||||
},
|
visible = isFABVisible,
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
alignment = Alignment.BottomEnd,
|
||||||
onClick = onContinueReading,
|
),
|
||||||
expanded = chapterListState.shouldExpandFAB(),
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
@@ -822,7 +818,7 @@ fun MangaScreenLargeImpl(
|
|||||||
// SY -->
|
// SY -->
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
||||||
SearchMetadataChips(state.meta, state.source, state.manga.genre)
|
SearchMetadataChips(state.meta, state.source.id, state.manga.genre)
|
||||||
},
|
},
|
||||||
// SY <--
|
// SY <--
|
||||||
)
|
)
|
||||||
@@ -951,7 +947,7 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
// SY <--
|
// SY <--
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
@@ -1018,14 +1014,14 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
onChapterSelected(item, !item.selected, true, true)
|
onChapterSelected(item, !item.selected, true)
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
onChapterItemClick(
|
onChapterItemClick(
|
||||||
chapterItem = item,
|
chapterItem = item,
|
||||||
isAnyChapterSelected = isAnyChapterSelected,
|
isAnyChapterSelected = isAnyChapterSelected,
|
||||||
onToggleSelection = { onChapterSelected(item, !item.selected, true, false) },
|
onToggleSelection = { onChapterSelected(item, !item.selected, false) },
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -1089,6 +1085,9 @@ fun metadataDescription(source: Source): MetadataDescriptionComposable? {
|
|||||||
is Tsumino -> { state, openMetadataViewer, _ ->
|
is Tsumino -> { state, openMetadataViewer, _ ->
|
||||||
TsuminoDescription(state, openMetadataViewer)
|
TsuminoDescription(state, openMetadataViewer)
|
||||||
}
|
}
|
||||||
|
is Lanraragi -> { state, openMetadataViewer, _ ->
|
||||||
|
LanraragiDescription(state, openMetadataViewer)
|
||||||
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ enum class DownloadAction {
|
|||||||
NEXT_10_CHAPTERS,
|
NEXT_10_CHAPTERS,
|
||||||
NEXT_25_CHAPTERS,
|
NEXT_25_CHAPTERS,
|
||||||
UNREAD_CHAPTERS,
|
UNREAD_CHAPTERS,
|
||||||
|
BOOKMARKED_CHAPTERS,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class EditCoverAction {
|
enum class EditCoverAction {
|
||||||
|
|||||||
+36
-38
@@ -9,6 +9,7 @@ import androidx.compose.animation.fadeOut
|
|||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
@@ -30,7 +31,6 @@ 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.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.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -52,6 +52,7 @@ import androidx.compose.ui.platform.LocalConfiguration
|
|||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.DownloadDropdownMenu
|
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
@@ -92,10 +93,10 @@ fun MangaBottomActionMenu(
|
|||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
|
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
|
||||||
var resetJob: Job? = remember { null }
|
var resetJob by remember { mutableStateOf<Job?>(null) }
|
||||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
(0..<7).forEach { i -> confirm[i] = i == toConfirmIndex }
|
confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||||
resetJob?.cancel()
|
resetJob?.cancel()
|
||||||
resetJob = scope.launch {
|
resetJob = scope.launch {
|
||||||
delay(1.seconds)
|
delay(1.seconds)
|
||||||
@@ -192,7 +193,7 @@ private fun RowScope.Button(
|
|||||||
targetValue = if (toConfirm) 2f else 1f,
|
targetValue = if (toConfirm) 2f else 1f,
|
||||||
label = "weight",
|
label = "weight",
|
||||||
)
|
)
|
||||||
Column(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.weight(animatedWeight)
|
.weight(animatedWeight)
|
||||||
@@ -202,24 +203,28 @@ private fun RowScope.Button(
|
|||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.Center,
|
contentAlignment = Alignment.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Column(
|
||||||
imageVector = icon,
|
verticalArrangement = Arrangement.Center,
|
||||||
contentDescription = title,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
)
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = toConfirm,
|
|
||||||
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
|
||||||
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Icon(
|
||||||
text = title,
|
imageVector = icon,
|
||||||
overflow = TextOverflow.Visible,
|
contentDescription = title,
|
||||||
maxLines = 1,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
)
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = toConfirm,
|
||||||
|
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||||
|
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
overflow = TextOverflow.Visible,
|
||||||
|
maxLines = 1,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
content?.invoke()
|
content?.invoke()
|
||||||
}
|
}
|
||||||
@@ -233,9 +238,9 @@ fun LibraryBottomActionMenu(
|
|||||||
onMarkAsUnreadClicked: () -> Unit,
|
onMarkAsUnreadClicked: () -> Unit,
|
||||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||||
onDeleteClicked: () -> Unit,
|
onDeleteClicked: () -> Unit,
|
||||||
|
onMigrateClicked: (() -> Unit)?,
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickCleanTitles: (() -> Unit)?,
|
onClickCleanTitles: (() -> Unit)?,
|
||||||
onClickMigrate: (() -> Unit)?,
|
|
||||||
onClickCollectRecommendations: (() -> Unit)?,
|
onClickCollectRecommendations: (() -> Unit)?,
|
||||||
onClickAddToMangaDex: (() -> Unit)?,
|
onClickAddToMangaDex: (() -> Unit)?,
|
||||||
onClickResetInfo: (() -> Unit)?,
|
onClickResetInfo: (() -> Unit)?,
|
||||||
@@ -254,12 +259,11 @@ fun LibraryBottomActionMenu(
|
|||||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val confirm =
|
val confirm = remember { mutableStateListOf(false, false, false, false, false, false) }
|
||||||
remember { mutableStateListOf(false, false, false, false, false /* SY --> */, false /* SY <-- */) }
|
var resetJob by remember { mutableStateOf<Job?>(null) }
|
||||||
var resetJob: Job? = remember { null }
|
|
||||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
(0..<5).forEach { i -> confirm[i] = i == toConfirmIndex }
|
confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||||
resetJob?.cancel()
|
resetJob?.cancel()
|
||||||
resetJob = scope.launch {
|
resetJob = scope.launch {
|
||||||
delay(1.seconds)
|
delay(1.seconds)
|
||||||
@@ -270,7 +274,8 @@ fun LibraryBottomActionMenu(
|
|||||||
val showOverflow = onClickCleanTitles != null ||
|
val showOverflow = onClickCleanTitles != null ||
|
||||||
onClickAddToMangaDex != null ||
|
onClickAddToMangaDex != null ||
|
||||||
onClickResetInfo != null ||
|
onClickResetInfo != null ||
|
||||||
onClickCollectRecommendations != null
|
onClickCollectRecommendations != null ||
|
||||||
|
onMigrateClicked != 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) }
|
||||||
@@ -299,11 +304,11 @@ fun LibraryBottomActionMenu(
|
|||||||
onLongClick = { onLongClickItem(3) },
|
onLongClick = { onLongClickItem(3) },
|
||||||
onClick = { downloadExpanded = !downloadExpanded },
|
onClick = { downloadExpanded = !downloadExpanded },
|
||||||
) {
|
) {
|
||||||
val onDismissRequest = { downloadExpanded = false }
|
|
||||||
DownloadDropdownMenu(
|
DownloadDropdownMenu(
|
||||||
expanded = downloadExpanded,
|
expanded = downloadExpanded,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = { downloadExpanded = false },
|
||||||
onDownloadClicked = onDownloadClicked,
|
onDownloadClicked = onDownloadClicked,
|
||||||
|
offset = BottomBarMenuDpOffset,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,10 +360,10 @@ fun LibraryBottomActionMenu(
|
|||||||
onClick = onClickCleanTitles,
|
onClick = onClickCleanTitles,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (onClickMigrate != null) {
|
if (onMigrateClicked != null) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(MR.strings.migrate)) },
|
text = { Text(stringResource(MR.strings.migrate)) },
|
||||||
onClick = onClickMigrate,
|
onClick = onMigrateClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (onClickCollectRecommendations != null) {
|
if (onClickCollectRecommendations != null) {
|
||||||
@@ -388,18 +393,11 @@ fun LibraryBottomActionMenu(
|
|||||||
onLongClick = { onLongClickItem(2) },
|
onLongClick = { onLongClickItem(2) },
|
||||||
onClick = onMarkAsUnreadClicked,
|
onClick = onMarkAsUnreadClicked,
|
||||||
)
|
)
|
||||||
if (onClickMigrate != null) {
|
|
||||||
Button(
|
|
||||||
title = stringResource(MR.strings.migrate),
|
|
||||||
icon = Icons.Outlined.SwapCalls,
|
|
||||||
toConfirm = confirm[5],
|
|
||||||
onLongClick = { onLongClickItem(5) },
|
|
||||||
onClick = onClickMigrate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val BottomBarMenuDpOffset = DpOffset(0.dp, 0.dp)
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ package eu.kanade.presentation.manga.components
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.activity.compose.PredictiveBackHandler
|
|
||||||
import androidx.compose.animation.core.animate
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -28,18 +25,15 @@ import androidx.compose.material3.SnackbarHostState
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.lerp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
@@ -56,14 +50,11 @@ import eu.kanade.presentation.components.DropdownMenu
|
|||||||
import eu.kanade.presentation.manga.EditCoverAction
|
import eu.kanade.presentation.manga.EditCoverAction
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import soup.compose.material.motion.MotionConstants
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.PredictiveBack
|
|
||||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaCoverDialog(
|
fun MangaCoverDialog(
|
||||||
@@ -162,32 +153,10 @@ fun MangaCoverDialog(
|
|||||||
val statusBarPaddingPx = with(LocalDensity.current) { contentPadding.calculateTopPadding().roundToPx() }
|
val statusBarPaddingPx = with(LocalDensity.current) { contentPadding.calculateTopPadding().roundToPx() }
|
||||||
val bottomPaddingPx = with(LocalDensity.current) { contentPadding.calculateBottomPadding().roundToPx() }
|
val bottomPaddingPx = with(LocalDensity.current) { contentPadding.calculateBottomPadding().roundToPx() }
|
||||||
|
|
||||||
var scale by remember { mutableFloatStateOf(1f) }
|
|
||||||
PredictiveBackHandler { progress ->
|
|
||||||
try {
|
|
||||||
progress.collect { backEvent ->
|
|
||||||
scale = lerp(1f, 0.8f, PredictiveBack.transform(backEvent.progress))
|
|
||||||
}
|
|
||||||
onDismissRequest()
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
animate(
|
|
||||||
initialValue = scale,
|
|
||||||
targetValue = 1f,
|
|
||||||
animationSpec = tween(durationMillis = MotionConstants.DefaultMotionDuration),
|
|
||||||
) { value, _ ->
|
|
||||||
scale = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clickableNoIndication(onClick = onDismissRequest)
|
.clickableNoIndication(onClick = onDismissRequest),
|
||||||
.graphicsLayer {
|
|
||||||
scaleX = scale
|
|
||||||
scaleY = scale
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = {
|
factory = {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.text.appendInlineContent
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.CallMerge
|
import androidx.compose.material.icons.automirrored.outlined.CallMerge
|
||||||
@@ -33,7 +34,6 @@ import androidx.compose.material.icons.filled.PersonOutline
|
|||||||
import androidx.compose.material.icons.filled.Warning
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material.icons.outlined.AttachMoney
|
import androidx.compose.material.icons.outlined.AttachMoney
|
||||||
import androidx.compose.material.icons.outlined.Block
|
import androidx.compose.material.icons.outlined.Block
|
||||||
import androidx.compose.material.icons.outlined.CallMerge
|
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material.icons.outlined.Done
|
import androidx.compose.material.icons.outlined.Done
|
||||||
import androidx.compose.material.icons.outlined.DoneAll
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
@@ -52,6 +52,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
@@ -67,9 +68,13 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.Layout
|
import androidx.compose.ui.layout.Layout
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.text.withLink
|
||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.unit.Constraints
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -79,10 +84,15 @@ import coil3.request.ImageRequest
|
|||||||
import coil3.request.crossfade
|
import coil3.request.crossfade
|
||||||
import com.mikepenz.markdown.model.markdownAnnotator
|
import com.mikepenz.markdown.model.markdownAnnotator
|
||||||
import com.mikepenz.markdown.model.markdownAnnotatorConfig
|
import com.mikepenz.markdown.model.markdownAnnotatorConfig
|
||||||
|
import com.mikepenz.markdown.utils.getUnescapedTextInNode
|
||||||
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import org.intellij.markdown.MarkdownElementTypes
|
||||||
|
import org.intellij.markdown.MarkdownTokenTypes
|
||||||
|
import org.intellij.markdown.ast.findChildOfType
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
@@ -93,6 +103,8 @@ import tachiyomi.presentation.core.i18n.pluralStringResource
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -593,19 +605,47 @@ private fun ColumnScope.MangaContentInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val descriptionAnnotator = markdownAnnotator(
|
@Composable
|
||||||
annotate = { content, child ->
|
private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = remember(loadImages, linkStyle) {
|
||||||
if (child.type in DISALLOWED_MARKDOWN_TYPES) {
|
markdownAnnotator(
|
||||||
append(content.substring(child.startOffset, child.endOffset))
|
annotate = { content, child ->
|
||||||
return@markdownAnnotator true
|
if (!loadImages && child.type == MarkdownElementTypes.IMAGE) {
|
||||||
}
|
val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK)
|
||||||
|
|
||||||
false
|
val url = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
|
||||||
},
|
?.getUnescapedTextInNode(content)
|
||||||
config = markdownAnnotatorConfig(
|
?: inlineLink?.findChildOfType(MarkdownElementTypes.AUTOLINK)
|
||||||
eolAsNewLine = true,
|
?.findChildOfType(MarkdownTokenTypes.AUTOLINK)
|
||||||
),
|
?.getUnescapedTextInNode(content)
|
||||||
)
|
?: return@markdownAnnotator false
|
||||||
|
|
||||||
|
val textNode = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TITLE)
|
||||||
|
?: inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TEXT)
|
||||||
|
val altText = textNode?.findChildOfType(MarkdownTokenTypes.TEXT)
|
||||||
|
?.getUnescapedTextInNode(content).orEmpty()
|
||||||
|
|
||||||
|
withLink(LinkAnnotation.Url(url = url)) {
|
||||||
|
pushStyle(linkStyle)
|
||||||
|
appendInlineContent(MARKDOWN_INLINE_IMAGE_TAG)
|
||||||
|
append(altText)
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return@markdownAnnotator true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.type in DISALLOWED_MARKDOWN_TYPES) {
|
||||||
|
append(content.substring(child.startOffset, child.endOffset))
|
||||||
|
return@markdownAnnotator true
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
},
|
||||||
|
config = markdownAnnotatorConfig(
|
||||||
|
eolAsNewLine = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MangaSummary(
|
private fun MangaSummary(
|
||||||
@@ -615,10 +655,13 @@ private fun MangaSummary(
|
|||||||
onEditNotesClicked: () -> Unit,
|
onEditNotesClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val preferences = remember { Injekt.get<UiPreferences>() }
|
||||||
|
val loadImages = remember { preferences.imagesInDescription().get() }
|
||||||
val animProgress by animateFloatAsState(
|
val animProgress by animateFloatAsState(
|
||||||
targetValue = if (expanded) 1f else 0f,
|
targetValue = if (expanded) 1f else 0f,
|
||||||
label = "summary",
|
label = "summary",
|
||||||
)
|
)
|
||||||
|
var infoHeight by remember { mutableIntStateOf(0) }
|
||||||
Layout(
|
Layout(
|
||||||
modifier = modifier.clipToBounds(),
|
modifier = modifier.clipToBounds(),
|
||||||
contents = listOf(
|
contents = listOf(
|
||||||
@@ -631,21 +674,11 @@ private fun MangaSummary(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Column {
|
Column(
|
||||||
MangaNotesSection(
|
modifier = Modifier.onSizeChanged { size ->
|
||||||
content = notes,
|
infoHeight = size.height
|
||||||
expanded = true,
|
},
|
||||||
onEditNotes = onEditNotesClicked,
|
) {
|
||||||
)
|
|
||||||
MarkdownRender(
|
|
||||||
content = description,
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
annotator = descriptionAnnotator,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Column {
|
|
||||||
MangaNotesSection(
|
MangaNotesSection(
|
||||||
content = notes,
|
content = notes,
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
@@ -655,7 +688,11 @@ private fun MangaSummary(
|
|||||||
MarkdownRender(
|
MarkdownRender(
|
||||||
content = description,
|
content = description,
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
annotator = descriptionAnnotator,
|
annotator = descriptionAnnotator(
|
||||||
|
loadImages = loadImages,
|
||||||
|
linkStyle = getMarkdownLinkStyle().toSpanStyle(),
|
||||||
|
),
|
||||||
|
loadImages = loadImages,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -678,14 +715,11 @@ private fun MangaSummary(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
) { (shrunk, expanded, actual, scrim), constraints ->
|
) { (shrunk, actual, scrim), constraints ->
|
||||||
val shrunkHeight = shrunk.single()
|
val shrunkHeight = shrunk.single()
|
||||||
.measure(constraints)
|
.measure(constraints)
|
||||||
.height
|
.height
|
||||||
val expandedHeight = expanded.single()
|
val heightDelta = infoHeight - shrunkHeight
|
||||||
.measure(constraints)
|
|
||||||
.height
|
|
||||||
val heightDelta = expandedHeight - shrunkHeight
|
|
||||||
val scrimHeight = 24.dp.roundToPx()
|
val scrimHeight = 24.dp.roundToPx()
|
||||||
|
|
||||||
val actualPlaceable = actual.single()
|
val actualPlaceable = actual.single()
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
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.foundation.text.InlineTextContent
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Image
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.FirstBaseline
|
import androidx.compose.ui.layout.FirstBaseline
|
||||||
|
import androidx.compose.ui.text.Placeholder
|
||||||
|
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.TextLinkStyles
|
import androidx.compose.ui.text.TextLinkStyles
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
@@ -32,11 +37,13 @@ import com.mikepenz.markdown.compose.elements.MarkdownTableRow
|
|||||||
import com.mikepenz.markdown.compose.elements.MarkdownText
|
import com.mikepenz.markdown.compose.elements.MarkdownText
|
||||||
import com.mikepenz.markdown.compose.elements.listDepth
|
import com.mikepenz.markdown.compose.elements.listDepth
|
||||||
import com.mikepenz.markdown.model.DefaultMarkdownColors
|
import com.mikepenz.markdown.model.DefaultMarkdownColors
|
||||||
|
import com.mikepenz.markdown.model.DefaultMarkdownInlineContent
|
||||||
import com.mikepenz.markdown.model.DefaultMarkdownTypography
|
import com.mikepenz.markdown.model.DefaultMarkdownTypography
|
||||||
import com.mikepenz.markdown.model.MarkdownAnnotator
|
import com.mikepenz.markdown.model.MarkdownAnnotator
|
||||||
import com.mikepenz.markdown.model.MarkdownColors
|
import com.mikepenz.markdown.model.MarkdownColors
|
||||||
import com.mikepenz.markdown.model.MarkdownPadding
|
import com.mikepenz.markdown.model.MarkdownPadding
|
||||||
import com.mikepenz.markdown.model.MarkdownTypography
|
import com.mikepenz.markdown.model.MarkdownTypography
|
||||||
|
import com.mikepenz.markdown.model.NoOpImageTransformerImpl
|
||||||
import com.mikepenz.markdown.model.markdownAnnotator
|
import com.mikepenz.markdown.model.markdownAnnotator
|
||||||
import com.mikepenz.markdown.model.rememberMarkdownState
|
import com.mikepenz.markdown.model.rememberMarkdownState
|
||||||
import org.intellij.markdown.MarkdownTokenTypes.Companion.HTML_TAG
|
import org.intellij.markdown.MarkdownTokenTypes.Companion.HTML_TAG
|
||||||
@@ -59,12 +66,15 @@ import org.intellij.markdown.parser.markerblocks.providers.ListMarkerProvider
|
|||||||
import org.intellij.markdown.parser.markerblocks.providers.SetextHeaderProvider
|
import org.intellij.markdown.parser.markerblocks.providers.SetextHeaderProvider
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
|
const val MARKDOWN_INLINE_IMAGE_TAG = "MARKDOWN_INLINE_IMAGE"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MarkdownRender(
|
fun MarkdownRender(
|
||||||
content: String,
|
content: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
flavour: MarkdownFlavourDescriptor = SimpleMarkdownFlavourDescriptor,
|
flavour: MarkdownFlavourDescriptor = SimpleMarkdownFlavourDescriptor,
|
||||||
annotator: MarkdownAnnotator = remember { markdownAnnotator() },
|
annotator: MarkdownAnnotator = remember { markdownAnnotator() },
|
||||||
|
loadImages: Boolean = true,
|
||||||
) {
|
) {
|
||||||
Markdown(
|
Markdown(
|
||||||
markdownState = rememberMarkdownState(
|
markdownState = rememberMarkdownState(
|
||||||
@@ -77,7 +87,10 @@ fun MarkdownRender(
|
|||||||
typography = getMarkdownTypography(),
|
typography = getMarkdownTypography(),
|
||||||
padding = markdownPadding,
|
padding = markdownPadding,
|
||||||
components = markdownComponents,
|
components = markdownComponents,
|
||||||
imageTransformer = Coil3ImageTransformerImpl,
|
imageTransformer = remember(loadImages) {
|
||||||
|
if (loadImages) Coil3ImageTransformerImpl else NoOpImageTransformerImpl()
|
||||||
|
},
|
||||||
|
inlineContent = getMarkdownInlineContent(),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -88,24 +101,24 @@ private fun getMarkdownColors(): MarkdownColors {
|
|||||||
val codeBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
|
val codeBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
|
||||||
return DefaultMarkdownColors(
|
return DefaultMarkdownColors(
|
||||||
text = MaterialTheme.colorScheme.onSurface,
|
text = MaterialTheme.colorScheme.onSurface,
|
||||||
codeText = Color.Unspecified,
|
|
||||||
inlineCodeText = Color.Unspecified,
|
|
||||||
linkText = Color.Unspecified,
|
|
||||||
codeBackground = codeBackground,
|
codeBackground = codeBackground,
|
||||||
inlineCodeBackground = codeBackground,
|
inlineCodeBackground = codeBackground,
|
||||||
dividerColor = MaterialTheme.colorScheme.outlineVariant,
|
dividerColor = MaterialTheme.colorScheme.outlineVariant,
|
||||||
tableText = Color.Unspecified,
|
|
||||||
tableBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f),
|
tableBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
fun getMarkdownLinkStyle() = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
private fun getMarkdownTypography(): MarkdownTypography {
|
private fun getMarkdownTypography(): MarkdownTypography {
|
||||||
val link = MaterialTheme.typography.bodyMedium.copy(
|
val link = getMarkdownLinkStyle()
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
)
|
|
||||||
return DefaultMarkdownTypography(
|
return DefaultMarkdownTypography(
|
||||||
h1 = MaterialTheme.typography.headlineMedium,
|
h1 = MaterialTheme.typography.headlineMedium,
|
||||||
h2 = MaterialTheme.typography.headlineSmall,
|
h2 = MaterialTheme.typography.headlineSmall,
|
||||||
@@ -121,7 +134,6 @@ private fun getMarkdownTypography(): MarkdownTypography {
|
|||||||
ordered = MaterialTheme.typography.bodyMedium,
|
ordered = MaterialTheme.typography.bodyMedium,
|
||||||
bullet = MaterialTheme.typography.bodyMedium,
|
bullet = MaterialTheme.typography.bodyMedium,
|
||||||
list = MaterialTheme.typography.bodyMedium,
|
list = MaterialTheme.typography.bodyMedium,
|
||||||
link = link,
|
|
||||||
textLink = TextLinkStyles(style = link.toSpanStyle()),
|
textLink = TextLinkStyles(style = link.toSpanStyle()),
|
||||||
table = MaterialTheme.typography.bodyMedium,
|
table = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
@@ -216,6 +228,27 @@ private val markdownComponents = markdownComponents(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
private fun getMarkdownInlineContent() = DefaultMarkdownInlineContent(
|
||||||
|
inlineContent = mapOf(
|
||||||
|
MARKDOWN_INLINE_IMAGE_TAG to InlineTextContent(
|
||||||
|
placeholder = Placeholder(
|
||||||
|
width = MaterialTheme.typography.bodyMedium.fontSize * 1.25,
|
||||||
|
height = MaterialTheme.typography.bodyMedium.fontSize * 1.25,
|
||||||
|
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter,
|
||||||
|
),
|
||||||
|
children = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Image,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
private object SimpleMarkdownFlavourDescriptor : CommonMarkFlavourDescriptor() {
|
private object SimpleMarkdownFlavourDescriptor : CommonMarkFlavourDescriptor() {
|
||||||
override val markerProcessorFactory: MarkerProcessorFactory = SimpleMarkdownProcessFactory
|
override val markerProcessorFactory: MarkerProcessorFactory = SimpleMarkdownProcessFactory
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import androidx.compose.runtime.CompositionLocalProvider
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -25,8 +24,6 @@ import eu.kanade.presentation.components.ChipBorder
|
|||||||
import eu.kanade.presentation.components.SuggestionChip
|
import eu.kanade.presentation.components.SuggestionChip
|
||||||
import eu.kanade.presentation.components.SuggestionChipDefaults
|
import eu.kanade.presentation.components.SuggestionChipDefaults
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
|
||||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
import exh.metadata.metadata.base.RaisedTag
|
import exh.metadata.metadata.base.RaisedTag
|
||||||
@@ -49,7 +46,7 @@ value class SearchMetadataChips(
|
|||||||
val tags: Map<String, List<DisplayTag>>,
|
val tags: Map<String, List<DisplayTag>>,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
operator fun invoke(meta: RaisedSearchMetadata?, source: Source, tags: List<String>?): SearchMetadataChips? {
|
operator fun invoke(meta: RaisedSearchMetadata?, sourceId: Long, tags: List<String>?): SearchMetadataChips? {
|
||||||
return if (meta != null) {
|
return if (meta != null) {
|
||||||
SearchMetadataChips(
|
SearchMetadataChips(
|
||||||
meta.tags
|
meta.tags
|
||||||
@@ -59,11 +56,11 @@ value class SearchMetadataChips(
|
|||||||
namespace = it.namespace,
|
namespace = it.namespace,
|
||||||
text = it.name,
|
text = it.name,
|
||||||
search = if (!it.namespace.isNullOrEmpty()) {
|
search = if (!it.namespace.isNullOrEmpty()) {
|
||||||
SourceTagsUtil.getWrappedTag(source.id, namespace = it.namespace, tag = it.name)
|
SourceTagsUtil.getWrappedTag(sourceId, namespace = it.namespace, tag = it.name)
|
||||||
} else {
|
} else {
|
||||||
SourceTagsUtil.getWrappedTag(source.id, fullTag = it.name)
|
SourceTagsUtil.getWrappedTag(sourceId, fullTag = it.name)
|
||||||
} ?: it.name,
|
} ?: it.name,
|
||||||
border = if (source.id == EXH_SOURCE_ID || source.id == EH_SOURCE_ID) {
|
border = if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID) {
|
||||||
when (it.type) {
|
when (it.type) {
|
||||||
EHentaiSearchMetadata.TAG_TYPE_NORMAL -> 2
|
EHentaiSearchMetadata.TAG_TYPE_NORMAL -> 2
|
||||||
EHentaiSearchMetadata.TAG_TYPE_LIGHT -> 1
|
EHentaiSearchMetadata.TAG_TYPE_LIGHT -> 1
|
||||||
@@ -178,7 +175,6 @@ fun TagsChip(
|
|||||||
fun NamespaceTagsPreview() {
|
fun NamespaceTagsPreview() {
|
||||||
TachiyomiPreviewTheme {
|
TachiyomiPreviewTheme {
|
||||||
Surface {
|
Surface {
|
||||||
val context = LocalContext.current
|
|
||||||
NamespaceTags(
|
NamespaceTags(
|
||||||
tags = remember {
|
tags = remember {
|
||||||
EHentaiSearchMetadata().apply {
|
EHentaiSearchMetadata().apply {
|
||||||
@@ -216,7 +212,7 @@ fun NamespaceTagsPreview() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}.let { SearchMetadataChips(it, EHentai(EXH_SOURCE_ID, true, context), emptyList()) }!!
|
}.let { SearchMetadataChips(it, EXH_SOURCE_ID, emptyList()) }!!
|
||||||
},
|
},
|
||||||
onClick = {},
|
onClick = {},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,12 +7,9 @@ import androidx.compose.material.icons.automirrored.outlined.Label
|
|||||||
import androidx.compose.material.icons.automirrored.outlined.PlaylistAdd
|
import androidx.compose.material.icons.automirrored.outlined.PlaylistAdd
|
||||||
import androidx.compose.material.icons.outlined.CloudOff
|
import androidx.compose.material.icons.outlined.CloudOff
|
||||||
import androidx.compose.material.icons.outlined.GetApp
|
import androidx.compose.material.icons.outlined.GetApp
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
|
||||||
import androidx.compose.material.icons.outlined.History
|
import androidx.compose.material.icons.outlined.History
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material.icons.outlined.Label
|
|
||||||
import androidx.compose.material.icons.outlined.NewReleases
|
import androidx.compose.material.icons.outlined.NewReleases
|
||||||
import androidx.compose.material.icons.outlined.PlaylistAdd
|
|
||||||
import androidx.compose.material.icons.outlined.QueryStats
|
import androidx.compose.material.icons.outlined.QueryStats
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.Storage
|
import androidx.compose.material.icons.outlined.Storage
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ sealed class Preference {
|
|||||||
abstract val title: String
|
abstract val title: String
|
||||||
abstract val enabled: Boolean
|
abstract val enabled: Boolean
|
||||||
|
|
||||||
sealed class PreferenceItem<T> : Preference() {
|
sealed class PreferenceItem<T, R> : Preference() {
|
||||||
// SY -->
|
// SY -->
|
||||||
abstract val subtitle: CharSequence?
|
abstract val subtitle: CharSequence?
|
||||||
|
|
||||||
// SY <--
|
// SY <--
|
||||||
abstract val icon: ImageVector?
|
abstract val icon: ImageVector?
|
||||||
abstract val onValueChanged: suspend (value: T) -> Boolean
|
abstract val onValueChanged: suspend (value: T) -> R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic [PreferenceItem] that only displays texts.
|
* A basic [PreferenceItem] that only displays texts.
|
||||||
@@ -31,9 +31,9 @@ sealed class Preference {
|
|||||||
override val subtitle: CharSequence? = null,
|
override val subtitle: CharSequence? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
val onClick: (() -> Unit)? = null,
|
val onClick: (() -> Unit)? = null,
|
||||||
) : PreferenceItem<String>() {
|
) : PreferenceItem<String, Unit>() {
|
||||||
override val icon: ImageVector? = null
|
override val icon: ImageVector? = null
|
||||||
override val onValueChanged: suspend (value: String) -> Boolean = { true }
|
override val onValueChanged: suspend (value: String) -> Unit = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +45,7 @@ sealed class Preference {
|
|||||||
override val subtitle: CharSequence? = null,
|
override val subtitle: CharSequence? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (value: Boolean) -> Boolean = { true },
|
override val onValueChanged: suspend (value: Boolean) -> Boolean = { true },
|
||||||
) : PreferenceItem<Boolean>() {
|
) : PreferenceItem<Boolean, Boolean>() {
|
||||||
override val icon: ImageVector? = null
|
override val icon: ImageVector? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,12 +55,13 @@ sealed class Preference {
|
|||||||
data class SliderPreference(
|
data class SliderPreference(
|
||||||
val value: Int,
|
val value: Int,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
|
override val subtitle: String? = null,
|
||||||
|
val valueString: String? = null,
|
||||||
val valueRange: IntProgression = 0..1,
|
val valueRange: IntProgression = 0..1,
|
||||||
@IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 },
|
@IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 },
|
||||||
override val subtitle: String? = null,
|
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (value: Int) -> Boolean = { true },
|
override val onValueChanged: suspend (value: Int) -> Unit = {},
|
||||||
) : PreferenceItem<Int>() {
|
) : PreferenceItem<Int, Unit>() {
|
||||||
override val icon: ImageVector? = null
|
override val icon: ImageVector? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ sealed class Preference {
|
|||||||
override val icon: ImageVector? = null,
|
override val icon: ImageVector? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (value: T) -> Boolean = { true },
|
override val onValueChanged: suspend (value: T) -> Boolean = { true },
|
||||||
) : PreferenceItem<T>() {
|
) : PreferenceItem<T, Boolean>() {
|
||||||
internal fun internalSet(value: Any) = preference.set(value as T)
|
internal fun internalSet(value: Any) = preference.set(value as T)
|
||||||
internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T)
|
internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T)
|
||||||
|
|
||||||
@@ -99,8 +100,8 @@ sealed class Preference {
|
|||||||
{ 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 (value: String) -> Boolean = { true },
|
override val onValueChanged: suspend (value: String) -> Unit = {},
|
||||||
) : PreferenceItem<String>()
|
) : PreferenceItem<String, Unit>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] that displays a list of entries as a dialog.
|
* A [PreferenceItem] that displays a list of entries as a dialog.
|
||||||
@@ -124,7 +125,7 @@ sealed class Preference {
|
|||||||
override val icon: ImageVector? = null,
|
override val icon: ImageVector? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true },
|
override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true },
|
||||||
) : PreferenceItem<Set<String>>()
|
) : PreferenceItem<Set<String>, Boolean>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] that shows a EditText in the dialog.
|
* A [PreferenceItem] that shows a EditText in the dialog.
|
||||||
@@ -135,7 +136,7 @@ sealed class Preference {
|
|||||||
override val subtitle: String? = "%s",
|
override val subtitle: String? = "%s",
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (value: String) -> Boolean = { true },
|
override val onValueChanged: suspend (value: String) -> Boolean = { true },
|
||||||
) : PreferenceItem<String>() {
|
) : PreferenceItem<String, Boolean>() {
|
||||||
override val icon: ImageVector? = null
|
override val icon: ImageVector? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,31 +147,31 @@ sealed class Preference {
|
|||||||
val tracker: Tracker,
|
val tracker: Tracker,
|
||||||
val login: () -> Unit,
|
val login: () -> Unit,
|
||||||
val logout: () -> Unit,
|
val logout: () -> Unit,
|
||||||
) : PreferenceItem<String>() {
|
) : PreferenceItem<String, Unit>() {
|
||||||
override val title: 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 (value: String) -> Boolean = { true }
|
override val onValueChanged: suspend (value: String) -> Unit = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class InfoPreference(
|
data class InfoPreference(
|
||||||
override val title: String,
|
override val title: String,
|
||||||
) : PreferenceItem<String>() {
|
) : PreferenceItem<String, 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 (value: String) -> Boolean = { true }
|
override val onValueChanged: suspend (value: String) -> Unit = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class CustomPreference(
|
data class CustomPreference(
|
||||||
override val title: String,
|
override val title: String,
|
||||||
val content: @Composable () -> Unit,
|
val content: @Composable () -> Unit,
|
||||||
) : PreferenceItem<Unit>() {
|
) : PreferenceItem<Unit, 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 (value: Unit) -> Boolean = { true }
|
override val onValueChanged: suspend (value: Unit) -> Unit = {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +179,6 @@ sealed class Preference {
|
|||||||
override val title: String,
|
override val title: String,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
|
|
||||||
val preferenceItems: ImmutableList<PreferenceItem<out Any>>,
|
val preferenceItems: ImmutableList<PreferenceItem<out Any, out Any>>,
|
||||||
) : Preference()
|
) : Preference()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StatusWrapper(
|
fun StatusWrapper(
|
||||||
item: Preference.PreferenceItem<*>,
|
item: Preference.PreferenceItem<*, *>,
|
||||||
highlightKey: String?,
|
highlightKey: String?,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -56,7 +56,7 @@ fun StatusWrapper(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun PreferenceItem(
|
internal fun PreferenceItem(
|
||||||
item: Preference.PreferenceItem<*>,
|
item: Preference.PreferenceItem<*, *>,
|
||||||
highlightKey: String?,
|
highlightKey: String?,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -83,17 +83,18 @@ internal fun PreferenceItem(
|
|||||||
}
|
}
|
||||||
is Preference.PreferenceItem.SliderPreference -> {
|
is Preference.PreferenceItem.SliderPreference -> {
|
||||||
BaseSliderItem(
|
BaseSliderItem(
|
||||||
label = item.title,
|
|
||||||
value = item.value,
|
value = item.value,
|
||||||
valueRange = item.valueRange,
|
valueRange = item.valueRange,
|
||||||
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
|
||||||
steps = item.steps,
|
steps = item.steps,
|
||||||
labelStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
|
title = item.title,
|
||||||
|
subtitle = item.subtitle,
|
||||||
|
valueString = item.valueString.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
||||||
onChange = {
|
onChange = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
item.onValueChanged(it)
|
item.onValueChanged(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
titleStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
horizontal = PrefsHorizontalPadding,
|
horizontal = PrefsHorizontalPadding,
|
||||||
vertical = PrefsVerticalPadding,
|
vertical = PrefsVerticalPadding,
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ fun PreferenceScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create Preference Item
|
// Create Preference Item
|
||||||
is Preference.PreferenceItem<*> -> item {
|
is Preference.PreferenceItem<*, *> -> item {
|
||||||
PreferenceItem(
|
PreferenceItem(
|
||||||
item = preference,
|
item = preference,
|
||||||
highlightKey = highlightKey,
|
highlightKey = highlightKey,
|
||||||
|
|||||||
+38
-2
@@ -3,6 +3,7 @@ package eu.kanade.presentation.more.settings.screen
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.webkit.WebStorage
|
import android.webkit.WebStorage
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
@@ -88,6 +89,7 @@ import tachiyomi.core.common.util.lang.withUIContext
|
|||||||
import tachiyomi.core.common.util.system.ImageUtil
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.interactor.GetAllManga
|
import tachiyomi.domain.manga.interactor.GetAllManga
|
||||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
@@ -116,6 +118,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
||||||
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
||||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
||||||
|
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
@@ -147,9 +150,18 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(MR.strings.pref_manage_notifications),
|
title = stringResource(MR.strings.pref_manage_notifications),
|
||||||
onClick = {
|
onClick = {
|
||||||
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
|
// SY -->
|
||||||
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
val intent = Intent().apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||||
|
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||||
|
} else {
|
||||||
|
setAction("android.settings.APP_NOTIFICATION_SETTINGS")
|
||||||
|
putExtra("app_package", context.packageName)
|
||||||
|
putExtra("app_uid", context.applicationInfo.uid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// SY <--
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -157,6 +169,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
getDataGroup(),
|
getDataGroup(),
|
||||||
getNetworkGroup(networkPreferences = networkPreferences),
|
getNetworkGroup(networkPreferences = networkPreferences),
|
||||||
getLibraryGroup(libraryPreferences = libraryPreferences),
|
getLibraryGroup(libraryPreferences = libraryPreferences),
|
||||||
|
getDownloadsGroup(downloadPreferences = downloadPreferences),
|
||||||
getReaderGroup(basePreferences = basePreferences),
|
getReaderGroup(basePreferences = basePreferences),
|
||||||
getExtensionsGroup(basePreferences = basePreferences),
|
getExtensionsGroup(basePreferences = basePreferences),
|
||||||
// SY -->
|
// SY -->
|
||||||
@@ -359,10 +372,33 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_update_library_manga_titles),
|
title = stringResource(MR.strings.pref_update_library_manga_titles),
|
||||||
subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary),
|
subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = libraryPreferences.disallowNonAsciiFilenames(),
|
||||||
|
title = stringResource(MR.strings.pref_disallow_non_ascii_filenames),
|
||||||
|
subtitle = stringResource(MR.strings.pref_disallow_non_ascii_filenames_details),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY ->
|
||||||
|
@Composable
|
||||||
|
private fun getDownloadsGroup(
|
||||||
|
downloadPreferences: DownloadPreferences,
|
||||||
|
): Preference.PreferenceGroup {
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(MR.strings.pref_category_downloads),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = downloadPreferences.includeChapterUrlHash(),
|
||||||
|
title = stringResource(SYMR.strings.pref_include_chapter_url_hash),
|
||||||
|
subtitle = stringResource(SYMR.strings.pref_include_chapter_url_hash_desc),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// <- SY
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getReaderGroup(
|
private fun getReaderGroup(
|
||||||
basePreferences: BasePreferences,
|
basePreferences: BasePreferences,
|
||||||
|
|||||||
+4
@@ -151,6 +151,10 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
formattedNow,
|
formattedNow,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = uiPreferences.imagesInDescription(),
|
||||||
|
title = stringResource(MR.strings.pref_display_images_description),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,24 +129,6 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.parental_controls_info)),
|
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.parental_controls_info)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
getMigrationCategory(sourcePreferences),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun getMigrationCategory(sourcePreferences: SourcePreferences): Preference.PreferenceGroup {
|
|
||||||
val skipPreMigration by sourcePreferences.skipPreMigration().collectAsState()
|
|
||||||
val migrationSources by sourcePreferences.migrationSources().collectAsState()
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
stringResource(SYMR.strings.migration),
|
|
||||||
enabled = skipPreMigration || migrationSources.isNotEmpty(),
|
|
||||||
preferenceItems = persistentListOf(
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
preference = sourcePreferences.skipPreMigration(),
|
|
||||||
title = stringResource(SYMR.strings.skip_pre_migration),
|
|
||||||
subtitle = stringResource(SYMR.strings.pref_skip_pre_migration_summary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -37,6 +37,8 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
||||||
|
|
||||||
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
|
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
|
||||||
|
val parallelSourceLimit by downloadPreferences.parallelSourceLimit().collectAsState()
|
||||||
|
val parallelPageLimit by downloadPreferences.parallelPageLimit().collectAsState()
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = downloadPreferences.downloadOnlyOverWifi(),
|
preference = downloadPreferences.downloadOnlyOverWifi(),
|
||||||
@@ -51,6 +53,19 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
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),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SliderPreference(
|
||||||
|
value = parallelSourceLimit,
|
||||||
|
valueRange = 1..10,
|
||||||
|
title = stringResource(MR.strings.pref_download_concurrent_sources),
|
||||||
|
onValueChanged = { downloadPreferences.parallelSourceLimit().set(it) },
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SliderPreference(
|
||||||
|
value = parallelPageLimit,
|
||||||
|
valueRange = 1..15,
|
||||||
|
title = stringResource(MR.strings.pref_download_concurrent_pages),
|
||||||
|
subtitle = stringResource(MR.strings.pref_download_concurrent_pages_summary),
|
||||||
|
onValueChanged = { downloadPreferences.parallelPageLimit().set(it) },
|
||||||
|
),
|
||||||
getDeleteChaptersGroup(
|
getDeleteChaptersGroup(
|
||||||
downloadPreferences = downloadPreferences,
|
downloadPreferences = downloadPreferences,
|
||||||
categories = allCategories,
|
categories = allCategories,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ChromeReaderMode
|
import androidx.compose.material.icons.automirrored.outlined.ChromeReaderMode
|
||||||
import androidx.compose.material.icons.outlined.ChromeReaderMode
|
|
||||||
import androidx.compose.material.icons.outlined.Code
|
import androidx.compose.material.icons.outlined.Code
|
||||||
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
||||||
import androidx.compose.material.icons.outlined.Explore
|
import androidx.compose.material.icons.outlined.Explore
|
||||||
|
|||||||
+10
-21
@@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -12,6 +11,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
|||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
|
||||||
|
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||||
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
|
||||||
@@ -135,11 +135,9 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_fullscreen),
|
title = stringResource(MR.strings.pref_fullscreen),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = readerPreferences.cutoutShort(),
|
preference = readerPreferences.drawUnderCutout(),
|
||||||
title = stringResource(MR.strings.pref_cutout_short),
|
title = stringResource(MR.strings.pref_cutout_short),
|
||||||
enabled = fullscreen &&
|
enabled = LocalView.current.hasDisplayCutout() && fullscreen,
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
|
||||||
LocalView.current.rootWindowInsets?.displayCutout != null, // has cutout
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = readerPreferences.keepScreenOn(),
|
preference = readerPreferences.keepScreenOn(),
|
||||||
@@ -177,23 +175,17 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||||
valueRange = 1..15,
|
valueRange = 1..15,
|
||||||
title = stringResource(MR.strings.pref_flash_duration),
|
title = stringResource(MR.strings.pref_flash_duration),
|
||||||
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
valueString = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||||
enabled = flashPageState,
|
enabled = flashPageState,
|
||||||
onValueChanged = {
|
onValueChanged = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
|
||||||
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
|
|
||||||
true
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
value = flashInterval,
|
value = flashInterval,
|
||||||
valueRange = 1..10,
|
valueRange = 1..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),
|
valueString = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||||
enabled = flashPageState,
|
enabled = flashPageState,
|
||||||
onValueChanged = {
|
onValueChanged = { flashIntervalPref.set(it) },
|
||||||
flashIntervalPref.set(it)
|
|
||||||
true
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
preference = flashColorPref,
|
preference = flashColorPref,
|
||||||
@@ -382,11 +374,8 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX
|
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),
|
valueString = numberFormat.format(webtoonSidePadding / 100f),
|
||||||
onValueChanged = {
|
onValueChanged = { webtoonSidePaddingPref.set(it) },
|
||||||
webtoonSidePaddingPref.set(it)
|
|
||||||
true
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
preference = readerPreferences.readerHideThreshold(),
|
preference = readerPreferences.readerHideThreshold(),
|
||||||
@@ -597,7 +586,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
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
|
||||||
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
.mapIndexed { index, it -> index to stringResource(it) }
|
||||||
.toMap()
|
.toMap()
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
),
|
),
|
||||||
|
|||||||
+1
-1
@@ -183,7 +183,7 @@ private fun SearchResult(
|
|||||||
emptySequence()
|
emptySequence()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem<*> -> sequenceOf(null to p)
|
is Preference.PreferenceItem<*, *> -> sequenceOf(null to p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Don't show info preference
|
// Don't show info preference
|
||||||
|
|||||||
@@ -245,7 +245,6 @@ object AboutScreen : Screen() {
|
|||||||
is GetApplicationRelease.Result.OsTooOld -> {
|
is GetApplicationRelease.Result.OsTooOld -> {
|
||||||
context.toast(MR.strings.update_check_eol)
|
context.toast(MR.strings.update_check_eol)
|
||||||
}
|
}
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
context.toast(e.message)
|
context.toast(e.message)
|
||||||
|
|||||||
+5
@@ -2,13 +2,16 @@ package eu.kanade.presentation.more.settings.screen.about
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
|
||||||
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
||||||
import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent
|
import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
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
|
||||||
@@ -27,7 +30,9 @@ class OpenSourceLicensesScreen : Screen() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
|
val libraries by produceLibraries(R.raw.aboutlibraries)
|
||||||
LibrariesContainer(
|
LibrariesContainer(
|
||||||
|
libraries = libraries,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
|
|||||||
+4
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
|
|||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
@@ -27,6 +28,7 @@ class ExtensionReposScreenModel(
|
|||||||
private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(),
|
private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(),
|
||||||
private val replaceExtensionRepo: ReplaceExtensionRepo = Injekt.get(),
|
private val replaceExtensionRepo: ReplaceExtensionRepo = Injekt.get(),
|
||||||
private val updateExtensionRepo: UpdateExtensionRepo = Injekt.get(),
|
private val updateExtensionRepo: UpdateExtensionRepo = Injekt.get(),
|
||||||
|
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||||
) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) {
|
) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) {
|
||||||
|
|
||||||
private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE)
|
private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE)
|
||||||
@@ -53,6 +55,7 @@ class ExtensionReposScreenModel(
|
|||||||
fun createRepo(baseUrl: String) {
|
fun createRepo(baseUrl: String) {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
when (val result = createExtensionRepo.await(baseUrl)) {
|
when (val result = createExtensionRepo.await(baseUrl)) {
|
||||||
|
CreateExtensionRepo.Result.Success -> extensionManager.findAvailableExtensions()
|
||||||
CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
|
CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
|
||||||
CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists)
|
CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists)
|
||||||
is CreateExtensionRepo.Result.DuplicateFingerprint -> {
|
is CreateExtensionRepo.Result.DuplicateFingerprint -> {
|
||||||
@@ -93,6 +96,7 @@ class ExtensionReposScreenModel(
|
|||||||
fun deleteRepo(baseUrl: String) {
|
fun deleteRepo(baseUrl: String) {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
deleteExtensionRepo.await(baseUrl)
|
deleteExtensionRepo.await(baseUrl)
|
||||||
|
extensionManager.findAvailableExtensions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen.browse.components
|
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
@@ -57,10 +58,11 @@ fun ExtensionRepoCreateDialog(
|
|||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
Text(text = stringResource(MR.strings.action_add_repo_message))
|
Text(text = stringResource(MR.strings.action_add_repo_message, stringResource(MR.strings.app_name)))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
value = name,
|
value = name,
|
||||||
onValueChange = { name = it },
|
onValueChange = { name = it },
|
||||||
|
|||||||
+2
-2
@@ -78,7 +78,7 @@ class DebugInfoScreen : Screen() {
|
|||||||
val status by produceState(initialValue = "-") {
|
val status by produceState(initialValue = "-") {
|
||||||
val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode
|
val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode
|
||||||
value = when (result) {
|
value = when (result) {
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE_INSTALLED -> "No profile installed"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
|
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
|
||||||
"Compiled non-matching"
|
"Compiled non-matching"
|
||||||
@@ -98,7 +98,7 @@ class DebugInfoScreen : Screen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getDeviceInfoGroup(): Preference.PreferenceGroup {
|
private fun getDeviceInfoGroup(): Preference.PreferenceGroup {
|
||||||
val items = persistentListOf<Preference.PreferenceItem<out Any>>().mutate {
|
val items = persistentListOf<Preference.PreferenceItem<out Any, out Any>>().mutate {
|
||||||
it.add(
|
it.add(
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = "Model",
|
title = "Model",
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ fun ChapterListDialog(
|
|||||||
downloadManager.isChapterDownloaded(
|
downloadManager.isChapterDownloaded(
|
||||||
chapterItem.chapter.name,
|
chapterItem.chapter.name,
|
||||||
chapterItem.chapter.scanlator,
|
chapterItem.chapter.scanlator,
|
||||||
|
chapterItem.chapter.url,
|
||||||
chapterItem.manga.ogTitle,
|
chapterItem.manga.ogTitle,
|
||||||
chapterItem.manga.source,
|
chapterItem.manga.source,
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-7
@@ -6,6 +6,7 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
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.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
@@ -15,13 +16,12 @@ import androidx.compose.ui.unit.sp
|
|||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PageIndicatorText(
|
fun ReaderPageIndicator(
|
||||||
// SY -->
|
currentPage: Int,
|
||||||
currentPage: String,
|
|
||||||
// SY <--
|
|
||||||
totalPages: Int,
|
totalPages: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (currentPage.isEmpty() || totalPages <= 0) return
|
if (currentPage <= 0 || totalPages <= 0) return
|
||||||
|
|
||||||
val text = "$currentPage / $totalPages"
|
val text = "$currentPage / $totalPages"
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ fun PageIndicatorText(
|
|||||||
|
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
@@ -52,10 +53,10 @@ fun PageIndicatorText(
|
|||||||
|
|
||||||
@PreviewLightDark
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun PageIndicatorTextPreview() {
|
private fun ReaderPageIndicatorPreview() {
|
||||||
TachiyomiPreviewTheme {
|
TachiyomiPreviewTheme {
|
||||||
Surface {
|
Surface {
|
||||||
PageIndicatorText(currentPage = "10", totalPages = 69)
|
ReaderPageIndicator(currentPage = 10, totalPages = 69)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,13 @@ package eu.kanade.presentation.reader.appbars
|
|||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.slideInHorizontally
|
import androidx.compose.animation.slideInHorizontally
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutHorizontally
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -13,10 +16,13 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
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.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -27,7 +33,6 @@ import androidx.compose.ui.platform.LocalLayoutDirection
|
|||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AppBar
|
|
||||||
import eu.kanade.presentation.reader.components.ChapterNavigator
|
import eu.kanade.presentation.reader.components.ChapterNavigator
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
@@ -36,7 +41,8 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
|||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
private val animationSpec = tween<IntOffset>(200)
|
private val readerBarsSlideAnimationSpec = tween<IntOffset>(200)
|
||||||
|
private val readerBarsFadeAnimationSpec = tween<Float>(150)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
enum class NavBarType {
|
enum class NavBarType {
|
||||||
@@ -65,7 +71,6 @@ fun BoxIgnoreLayoutDirection(modifier: Modifier, content: @Composable BoxScope.(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ReaderAppBars(
|
fun ReaderAppBars(
|
||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
fullscreen: Boolean,
|
|
||||||
|
|
||||||
mangaTitle: String?,
|
mangaTitle: String?,
|
||||||
chapterTitle: String?,
|
chapterTitle: String?,
|
||||||
@@ -122,11 +127,7 @@ fun ReaderAppBars(
|
|||||||
.surfaceColorAtElevation(3.dp)
|
.surfaceColorAtElevation(3.dp)
|
||||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||||
|
|
||||||
val modifierWithInsetsPadding = if (fullscreen) {
|
val modifierWithInsetsPadding = Modifier.systemBarsPadding()
|
||||||
Modifier.systemBarsPadding()
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
BoxIgnoreLayoutDirection(
|
BoxIgnoreLayoutDirection(
|
||||||
@@ -136,11 +137,11 @@ fun ReaderAppBars(
|
|||||||
visible = visible && navBarType == NavBarType.VerticalLeft,
|
visible = visible && navBarType == NavBarType.VerticalLeft,
|
||||||
enter = slideInHorizontally(
|
enter = slideInHorizontally(
|
||||||
initialOffsetX = { -it },
|
initialOffsetX = { -it },
|
||||||
animationSpec = animationSpec,
|
animationSpec = readerBarsSlideAnimationSpec,
|
||||||
),
|
),
|
||||||
exit = slideOutHorizontally(
|
exit = slideOutHorizontally(
|
||||||
targetOffsetX = { -it },
|
targetOffsetX = { -it },
|
||||||
animationSpec = animationSpec,
|
animationSpec = readerBarsSlideAnimationSpec,
|
||||||
),
|
),
|
||||||
modifier = modifierWithInsetsPadding
|
modifier = modifierWithInsetsPadding
|
||||||
.padding(bottom = 48.dp, top = 120.dp)
|
.padding(bottom = 48.dp, top = 120.dp)
|
||||||
@@ -164,11 +165,11 @@ fun ReaderAppBars(
|
|||||||
visible = visible && navBarType == NavBarType.VerticalRight,
|
visible = visible && navBarType == NavBarType.VerticalRight,
|
||||||
enter = slideInHorizontally(
|
enter = slideInHorizontally(
|
||||||
initialOffsetX = { it },
|
initialOffsetX = { it },
|
||||||
animationSpec = animationSpec,
|
animationSpec = readerBarsSlideAnimationSpec,
|
||||||
),
|
),
|
||||||
exit = slideOutHorizontally(
|
exit = slideOutHorizontally(
|
||||||
targetOffsetX = { it },
|
targetOffsetX = { it },
|
||||||
animationSpec = animationSpec,
|
animationSpec = readerBarsSlideAnimationSpec,
|
||||||
),
|
),
|
||||||
modifier = modifierWithInsetsPadding
|
modifier = modifierWithInsetsPadding
|
||||||
.padding(bottom = 48.dp, top = 120.dp)
|
.padding(bottom = 48.dp, top = 120.dp)
|
||||||
@@ -196,48 +197,23 @@ fun ReaderAppBars(
|
|||||||
visible = visible,
|
visible = visible,
|
||||||
enter = slideInVertically(
|
enter = slideInVertically(
|
||||||
initialOffsetY = { -it },
|
initialOffsetY = { -it },
|
||||||
animationSpec = animationSpec,
|
animationSpec = readerBarsSlideAnimationSpec,
|
||||||
),
|
) + fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
||||||
exit = slideOutVertically(
|
exit = slideOutVertically(
|
||||||
targetOffsetY = { -it },
|
targetOffsetY = { -it },
|
||||||
animationSpec = animationSpec,
|
animationSpec = readerBarsSlideAnimationSpec,
|
||||||
),
|
) + fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
||||||
) {
|
) {
|
||||||
// SY -->
|
// SY -->
|
||||||
Column(modifierWithInsetsPadding) {
|
Column {
|
||||||
// SY <--
|
// SY <--
|
||||||
AppBar(
|
ReaderTopBar(
|
||||||
modifier = /*SY --> */ Modifier /*SY <-- */
|
modifier = Modifier
|
||||||
|
.background(backgroundColor)
|
||||||
.clickable(onClick = onClickTopAppBar),
|
.clickable(onClick = onClickTopAppBar),
|
||||||
backgroundColor = backgroundColor,
|
mangaTitle = mangaTitle,
|
||||||
title = mangaTitle,
|
chapterTitle = chapterTitle,
|
||||||
subtitle = chapterTitle,
|
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
/* SY --> actions = {
|
|
||||||
AppBarActions(
|
|
||||||
listOfNotNull(
|
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(
|
|
||||||
if (bookmarked) MR.strings.action_remove_bookmark else MR.strings.action_bookmark
|
|
||||||
),
|
|
||||||
icon = if (bookmarked) Icons.Outlined.Bookmark else Icons.Outlined.BookmarkBorder,
|
|
||||||
onClick = onToggleBookmarked,
|
|
||||||
),
|
|
||||||
onOpenInWebView?.let {
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_open_in_web_view),
|
|
||||||
onClick = it,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onShare?.let {
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(MR.strings.action_share),
|
|
||||||
onClick = it,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}, SY <-- */
|
|
||||||
)
|
)
|
||||||
// SY -->
|
// SY -->
|
||||||
ExhUtils(
|
ExhUtils(
|
||||||
@@ -255,8 +231,8 @@ fun ReaderAppBars(
|
|||||||
onClickBoostPage = onClickBoostPage,
|
onClickBoostPage = onClickBoostPage,
|
||||||
onClickBoostPageHelp = onClickBoostPageHelp,
|
onClickBoostPageHelp = onClickBoostPageHelp,
|
||||||
)
|
)
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
@@ -265,18 +241,18 @@ fun ReaderAppBars(
|
|||||||
visible = visible,
|
visible = visible,
|
||||||
enter = slideInVertically(
|
enter = slideInVertically(
|
||||||
initialOffsetY = { it },
|
initialOffsetY = { it },
|
||||||
animationSpec = animationSpec,
|
animationSpec = readerBarsSlideAnimationSpec,
|
||||||
),
|
) + fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
||||||
exit = slideOutVertically(
|
exit = slideOutVertically(
|
||||||
targetOffsetY = { it },
|
targetOffsetY = { it },
|
||||||
animationSpec = animationSpec,
|
animationSpec = readerBarsSlideAnimationSpec,
|
||||||
),
|
) + fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifierWithInsetsPadding,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
if (navBarType == NavBarType.Bottom) {
|
// SY -->
|
||||||
|
if (navBarType == NavBarType.Bottom) { // <-- SY
|
||||||
ChapterNavigator(
|
ChapterNavigator(
|
||||||
isRtl = isRtl,
|
isRtl = isRtl,
|
||||||
onNextChapter = onNextChapter,
|
onNextChapter = onNextChapter,
|
||||||
@@ -291,11 +267,10 @@ fun ReaderAppBars(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BottomReaderBar(
|
ReaderBottomBar(
|
||||||
// SY -->
|
// SY -->
|
||||||
enabledButtons = enabledButtons,
|
enabledButtons = enabledButtons,
|
||||||
// SY <--
|
// SY <--
|
||||||
backgroundColor = backgroundColor,
|
|
||||||
readingMode = readingMode,
|
readingMode = readingMode,
|
||||||
onClickReadingMode = onClickReadingMode,
|
onClickReadingMode = onClickReadingMode,
|
||||||
orientation = orientation,
|
orientation = orientation,
|
||||||
@@ -313,6 +288,12 @@ fun ReaderAppBars(
|
|||||||
onClickShare = onShare,
|
onClickShare = onShare,
|
||||||
onClickPageLayout = onClickPageLayout,
|
onClickPageLayout = onClickPageLayout,
|
||||||
onClickShiftPage = onClickShiftPage,
|
onClickShiftPage = onClickShiftPage,
|
||||||
|
// SY <--
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(backgroundColor)
|
||||||
|
.padding(horizontal = MaterialTheme.padding.small)
|
||||||
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-11
@@ -1,10 +1,7 @@
|
|||||||
package eu.kanade.presentation.reader.appbars
|
package eu.kanade.presentation.reader.appbars
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.FormatListNumbered
|
import androidx.compose.material.icons.outlined.FormatListNumbered
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
@@ -15,9 +12,8 @@ import androidx.compose.material3.IconButton
|
|||||||
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 androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
@@ -28,11 +24,10 @@ import tachiyomi.i18n.sy.SYMR
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomReaderBar(
|
fun ReaderBottomBar(
|
||||||
// SY -->
|
// SY -->
|
||||||
enabledButtons: ImmutableSet<String>,
|
enabledButtons: ImmutableSet<String>,
|
||||||
// SY <--
|
// SY <--
|
||||||
backgroundColor: Color,
|
|
||||||
readingMode: ReadingMode,
|
readingMode: ReadingMode,
|
||||||
onClickReadingMode: () -> Unit,
|
onClickReadingMode: () -> Unit,
|
||||||
orientation: ReaderOrientation,
|
orientation: ReaderOrientation,
|
||||||
@@ -51,12 +46,11 @@ fun BottomReaderBar(
|
|||||||
onClickPageLayout: () -> Unit,
|
onClickPageLayout: () -> Unit,
|
||||||
onClickShiftPage: () -> Unit,
|
onClickShiftPage: () -> Unit,
|
||||||
// SY <--
|
// SY <--
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.pointerInput(Unit) {},
|
||||||
.background(backgroundColor)
|
|
||||||
.padding(8.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package eu.kanade.presentation.reader.appbars
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReaderTopBar(
|
||||||
|
mangaTitle: String?,
|
||||||
|
chapterTitle: String?,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
// bookmarked: Boolean,
|
||||||
|
// onToggleBookmarked: () -> Unit,
|
||||||
|
// onOpenInWebView: (() -> Unit)?,
|
||||||
|
// onOpenInBrowser: (() -> Unit)?,
|
||||||
|
// onShare: (() -> Unit)?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
modifier = modifier,
|
||||||
|
backgroundColor = Color.Transparent,
|
||||||
|
title = mangaTitle,
|
||||||
|
subtitle = chapterTitle,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
/* SY ->
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
|
.apply {
|
||||||
|
add(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(
|
||||||
|
if (bookmarked) {
|
||||||
|
MR.strings.action_remove_bookmark
|
||||||
|
} else {
|
||||||
|
MR.strings.action_bookmark
|
||||||
|
},
|
||||||
|
),
|
||||||
|
icon = if (bookmarked) {
|
||||||
|
Icons.Outlined.Bookmark
|
||||||
|
} else {
|
||||||
|
Icons.Outlined.BookmarkBorder
|
||||||
|
},
|
||||||
|
onClick = onToggleBookmarked,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onOpenInWebView?.let {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_open_in_web_view),
|
||||||
|
onClick = it,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onOpenInBrowser?.let {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_open_in_browser),
|
||||||
|
onClick = it,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onShare?.let {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_share),
|
||||||
|
onClick = it,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
<- SY */
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
pref = screenModel.preferences.customBrightness(),
|
pref = screenModel.preferences.customBrightness(),
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Sets the brightness of the screen. Range is [-75, 100].
|
* Sets the brightness of the screen. Range is [-75, 100].
|
||||||
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
|
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
|
||||||
* From 1 to 100 it sets that value as brightness.
|
* From 1 to 100 it sets that value as brightness.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.reader.settings
|
package eu.kanade.presentation.reader.settings
|
||||||
|
|
||||||
|
import androidx.activity.compose.LocalActivity
|
||||||
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.MaterialTheme
|
||||||
@@ -8,6 +9,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
|
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
@@ -85,10 +87,11 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
pref = screenModel.preferences.fullscreen(),
|
pref = screenModel.preferences.fullscreen(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (screenModel.hasDisplayCutout && screenModel.preferences.fullscreen().get()) {
|
val isFullscreen by screenModel.preferences.fullscreen().collectAsState()
|
||||||
|
if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) {
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_cutout_short),
|
label = stringResource(MR.strings.pref_cutout_short),
|
||||||
pref = screenModel.preferences.cutoutShort(),
|
pref = screenModel.preferences.drawUnderCutout(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +125,7 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||||
valueRange = 1..15,
|
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),
|
valueString = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||||
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
|
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
|
||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
@@ -130,7 +133,7 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
value = flashInterval,
|
value = flashInterval,
|
||||||
valueRange = 1..10,
|
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),
|
valueString = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||||
onChange = {
|
onChange = {
|
||||||
flashIntervalPref.set(it)
|
flashIntervalPref.set(it)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
|
|||||||
value = webtoonSidePadding,
|
value = webtoonSidePadding,
|
||||||
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
|
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
|
||||||
label = stringResource(MR.strings.pref_webtoon_side_padding),
|
label = stringResource(MR.strings.pref_webtoon_side_padding),
|
||||||
valueText = numberFormat.format(webtoonSidePadding / 100f),
|
valueString = numberFormat.format(webtoonSidePadding / 100f),
|
||||||
onChange = {
|
onChange = {
|
||||||
screenModel.preferences.webtoonSidePadding().set(it)
|
screenModel.preferences.webtoonSidePadding().set(it)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package eu.kanade.presentation.theme
|
package eu.kanade.presentation.theme
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialExpressiveTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
import eu.kanade.presentation.theme.colorscheme.BaseColorScheme
|
import eu.kanade.presentation.theme.colorscheme.BaseColorScheme
|
||||||
|
import eu.kanade.presentation.theme.colorscheme.CatppuccinColorScheme
|
||||||
import eu.kanade.presentation.theme.colorscheme.GreenAppleColorScheme
|
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
|
||||||
@@ -52,31 +54,42 @@ private fun BaseTachiyomiTheme(
|
|||||||
isAmoled: Boolean,
|
isAmoled: Boolean,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
MaterialTheme(
|
val context = LocalContext.current
|
||||||
colorScheme = getThemeColorScheme(appTheme, isAmoled),
|
val isDark = isSystemInDarkTheme()
|
||||||
|
MaterialExpressiveTheme(
|
||||||
|
colorScheme = remember(appTheme, isDark, isAmoled) {
|
||||||
|
getThemeColorScheme(
|
||||||
|
context = context,
|
||||||
|
appTheme = appTheme,
|
||||||
|
isDark = isDark,
|
||||||
|
isAmoled = isAmoled,
|
||||||
|
)
|
||||||
|
},
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
@ReadOnlyComposable
|
|
||||||
private fun getThemeColorScheme(
|
private fun getThemeColorScheme(
|
||||||
|
context: Context,
|
||||||
appTheme: AppTheme,
|
appTheme: AppTheme,
|
||||||
|
isDark: Boolean,
|
||||||
isAmoled: Boolean,
|
isAmoled: Boolean,
|
||||||
): ColorScheme {
|
): ColorScheme {
|
||||||
val colorScheme = if (appTheme == AppTheme.MONET) {
|
val colorScheme = if (appTheme == AppTheme.MONET) {
|
||||||
MonetColorScheme(LocalContext.current)
|
MonetColorScheme(context)
|
||||||
} else {
|
} else {
|
||||||
colorSchemes.getOrDefault(appTheme, TachiyomiColorScheme)
|
colorSchemes.getOrDefault(appTheme, TachiyomiColorScheme)
|
||||||
}
|
}
|
||||||
return colorScheme.getColorScheme(
|
return colorScheme.getColorScheme(
|
||||||
isSystemInDarkTheme(),
|
isDark = isDark,
|
||||||
isAmoled,
|
isAmoled = isAmoled,
|
||||||
|
overrideDarkSurfaceContainers = appTheme != AppTheme.MONET,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val colorSchemes: Map<AppTheme, BaseColorScheme> = mapOf(
|
private val colorSchemes: Map<AppTheme, BaseColorScheme> = mapOf(
|
||||||
AppTheme.DEFAULT to TachiyomiColorScheme,
|
AppTheme.DEFAULT to TachiyomiColorScheme,
|
||||||
|
AppTheme.CATPPUCCIN to CatppuccinColorScheme,
|
||||||
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,
|
||||||
|
|||||||
@@ -14,16 +14,25 @@ internal abstract class BaseColorScheme {
|
|||||||
private val surfaceContainerHigh = Color(0xFF131313)
|
private val surfaceContainerHigh = Color(0xFF131313)
|
||||||
private val surfaceContainerHighest = Color(0xFF1B1B1B)
|
private val surfaceContainerHighest = Color(0xFF1B1B1B)
|
||||||
|
|
||||||
fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme {
|
fun getColorScheme(
|
||||||
|
isDark: Boolean,
|
||||||
|
isAmoled: Boolean,
|
||||||
|
overrideDarkSurfaceContainers: Boolean,
|
||||||
|
): ColorScheme {
|
||||||
if (!isDark) return lightScheme
|
if (!isDark) return lightScheme
|
||||||
|
|
||||||
if (!isAmoled) return darkScheme
|
if (!isAmoled) return darkScheme
|
||||||
|
|
||||||
return darkScheme.copy(
|
val amoledScheme = darkScheme.copy(
|
||||||
background = Color.Black,
|
background = Color.Black,
|
||||||
onBackground = Color.White,
|
onBackground = Color.White,
|
||||||
surface = Color.Black,
|
surface = Color.Black,
|
||||||
onSurface = Color.White,
|
onSurface = Color.White,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!overrideDarkSurfaceContainers) return amoledScheme
|
||||||
|
|
||||||
|
return amoledScheme.copy(
|
||||||
surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget)
|
surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget)
|
||||||
surfaceContainerLowest = surfaceContainer,
|
surfaceContainerLowest = surfaceContainer,
|
||||||
surfaceContainerLow = surfaceContainer,
|
surfaceContainerLow = surfaceContainer,
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package eu.kanade.presentation.theme.colorscheme
|
||||||
|
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colors for Catppuccin theme
|
||||||
|
* MIT License
|
||||||
|
* Copyright (c) 2021 Catppuccin
|
||||||
|
* https://catppuccin.com
|
||||||
|
* M3 colors generated by Material Theme Builder (https://goo.gle/material-theme-builder-web)
|
||||||
|
*
|
||||||
|
* Key colors (dark):
|
||||||
|
* Primary #CBA6F4
|
||||||
|
* Secondary #CBA6F4
|
||||||
|
* Tertiary #CBA6F4
|
||||||
|
* Neutral #181825
|
||||||
|
|
||||||
|
* Key colors (light):
|
||||||
|
* Primary #8839EF
|
||||||
|
* Secondary #8839EF
|
||||||
|
* Tertiary #8839EF
|
||||||
|
* Neutral #E6E9EF
|
||||||
|
*/
|
||||||
|
internal object CatppuccinColorScheme : BaseColorScheme() {
|
||||||
|
|
||||||
|
override val darkScheme = darkColorScheme(
|
||||||
|
primary = Color(0xFFCBA6F7),
|
||||||
|
onPrimary = Color(0xFF11111B),
|
||||||
|
primaryContainer = Color(0xFFCBA6F7),
|
||||||
|
onPrimaryContainer = Color(0xFF11111B),
|
||||||
|
secondary = Color(0xFFCBA6F7), // Unread badge
|
||||||
|
onSecondary = Color(0xFF11111B), // Unread badge text
|
||||||
|
secondaryContainer = Color(0xFF313244), // Navigation bar selector pill & progress indicator (remaining)
|
||||||
|
onSecondaryContainer = Color(0xFFCBA6F7), // Navigation bar selector icon
|
||||||
|
tertiary = Color(0xFFCBA6F7), // Volume and brightness bars, Downloaded badge
|
||||||
|
onTertiary = Color(0xFF11111B), // Downloaded badge text
|
||||||
|
tertiaryContainer = Color(0xFF1E1E2E),
|
||||||
|
onTertiaryContainer = Color(0xFFCDD6F4),
|
||||||
|
error = Color(0xFFF38BA8),
|
||||||
|
onError = Color(0xFF11111B),
|
||||||
|
errorContainer = Color(0xFFFF0558),
|
||||||
|
onErrorContainer = Color(0xFFEF9FB4),
|
||||||
|
background = Color(0xFF181825),
|
||||||
|
onBackground = Color(0xFFCDD6F4),
|
||||||
|
surface = Color(0xFF181825),
|
||||||
|
onSurface = Color(0xFFCDD6F4),
|
||||||
|
surfaceVariant = Color(0xFF1E1E2E), // Navigation bar background (ThemePrefWidget)
|
||||||
|
onSurfaceVariant = Color(0xFFCDD6F4), // Button (unselected)
|
||||||
|
outline = Color(0xFFCBA6F7),
|
||||||
|
outlineVariant = Color(0xFF585B70), // Outlines for buttons
|
||||||
|
scrim = Color(0xFF11111B),
|
||||||
|
inverseSurface = Color(0xFFEFF1F5), // Snackbar or whatever they called
|
||||||
|
inverseOnSurface = Color(0xFF4C4F69), // Snackbar text
|
||||||
|
inversePrimary = Color(0xFF8839EF), // Snackbar accent
|
||||||
|
surfaceDim = Color(0xFF181825),
|
||||||
|
surfaceBright = Color(0xFF313244),
|
||||||
|
surfaceContainerLowest = Color(0xFF181825),
|
||||||
|
surfaceContainerLow = Color(0xFF1E1E2E), // Repo cards
|
||||||
|
surfaceContainer = Color(0xFF1E1E2E),
|
||||||
|
surfaceContainerHigh = Color(0xFF1E1E2E), // Filter menu
|
||||||
|
surfaceContainerHighest = Color(0xFF313244), // Untoggleg button bg
|
||||||
|
)
|
||||||
|
|
||||||
|
override val lightScheme = lightColorScheme(
|
||||||
|
primary = Color(0xFF8839EF),
|
||||||
|
onPrimary = Color(0xFFDCE0E8),
|
||||||
|
primaryContainer = Color(0xFF8839EF),
|
||||||
|
onPrimaryContainer = Color(0xFFDCE0E8),
|
||||||
|
secondary = Color(0xFF8839EF), // Unread badge
|
||||||
|
onSecondary = Color(0xFFDCE0E8), // Unread badge text
|
||||||
|
secondaryContainer = Color(0xFFCDD0DA), // Navigation bar selector pill & progress indicator (remaining)
|
||||||
|
onSecondaryContainer = Color(0xFF8839EF), // Navigation bar selector icon
|
||||||
|
tertiary = Color(0xFF8839EF), // Volume and brightness bars, Downloaded badge
|
||||||
|
onTertiary = Color(0xFFDCE0E8), // Downloaded badge text
|
||||||
|
tertiaryContainer = Color(0xFFEFF1F5),
|
||||||
|
onTertiaryContainer = Color(0xFF4C4F69),
|
||||||
|
error = Color(0xFFD20F39),
|
||||||
|
onError = Color(0xFFDCE0E8),
|
||||||
|
errorContainer = Color(0xFF68001C),
|
||||||
|
onErrorContainer = Color(0xFFD61C41),
|
||||||
|
background = Color(0xFFE6E9EF),
|
||||||
|
onBackground = Color(0xFF4C4F69),
|
||||||
|
surface = Color(0xFFE6E9EF),
|
||||||
|
onSurface = Color(0xFF4C4F69),
|
||||||
|
surfaceVariant = Color(0xFFEFF1F5), // Navigation bar background (ThemePrefWidget)
|
||||||
|
onSurfaceVariant = Color(0xFF4C4F69), // Button (unselected)
|
||||||
|
outline = Color(0xFF8839EF),
|
||||||
|
outlineVariant = Color(0xFFACB0BE), // Outlines for buttons
|
||||||
|
scrim = Color(0xFFDCE0E8),
|
||||||
|
inverseSurface = Color(0xFF1E1E2E), // Snackbar
|
||||||
|
inverseOnSurface = Color(0xFFCDD6F4), // Snackbar text
|
||||||
|
inversePrimary = Color(0xFFCBA6F7), // Snackbar accent
|
||||||
|
surfaceDim = Color(0xFFE6E9EF),
|
||||||
|
surfaceBright = Color(0xFFCDD0DA),
|
||||||
|
surfaceContainerLowest = Color(0xFFE6E9EF),
|
||||||
|
surfaceContainerLow = Color(0xFFEFF1F5), // Repo cards
|
||||||
|
surfaceContainer = Color(0xFFEFF1F5), // Navigation bar background
|
||||||
|
surfaceContainerHigh = Color(0xFFEFF1F5), // Filter menu
|
||||||
|
surfaceContainerHighest = Color(0xFFCDD0DA), // Untoggleg bg
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,22 +1,17 @@
|
|||||||
package eu.kanade.presentation.theme.colorscheme
|
package eu.kanade.presentation.theme.colorscheme
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.UiModeManager
|
|
||||||
import android.app.WallpaperManager
|
import android.app.WallpaperManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.dynamicDarkColorScheme
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.core.content.getSystemService
|
import com.materialkolor.PaletteStyle
|
||||||
import com.google.android.material.color.utilities.Hct
|
import com.materialkolor.dynamiccolor.ColorSpec
|
||||||
import com.google.android.material.color.utilities.MaterialDynamicColors
|
import com.materialkolor.ktx.DynamicScheme
|
||||||
import com.google.android.material.color.utilities.QuantizerCelebi
|
import com.materialkolor.toColorScheme
|
||||||
import com.google.android.material.color.utilities.SchemeContent
|
|
||||||
import com.google.android.material.color.utilities.Score
|
|
||||||
|
|
||||||
internal class MonetColorScheme(context: Context) : BaseColorScheme() {
|
internal class MonetColorScheme(context: Context) : BaseColorScheme() {
|
||||||
|
|
||||||
@@ -28,7 +23,7 @@ internal class MonetColorScheme(context: Context) : BaseColorScheme() {
|
|||||||
?.primaryColor
|
?.primaryColor
|
||||||
?.toArgb()
|
?.toArgb()
|
||||||
if (seed != null) {
|
if (seed != null) {
|
||||||
MonetCompatColorScheme(context, seed)
|
MonetCompatColorScheme(Color(seed))
|
||||||
} else {
|
} else {
|
||||||
TachiyomiColorScheme
|
TachiyomiColorScheme
|
||||||
}
|
}
|
||||||
@@ -41,19 +36,6 @@ internal class MonetColorScheme(context: Context) : BaseColorScheme() {
|
|||||||
|
|
||||||
override val lightScheme
|
override val lightScheme
|
||||||
get() = monet.lightScheme
|
get() = monet.lightScheme
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Suppress("Unused")
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
fun extractSeedColorFromImage(bitmap: Bitmap): Int? {
|
|
||||||
val width = bitmap.width
|
|
||||||
val height = bitmap.height
|
|
||||||
val bitmapPixels = IntArray(width * height)
|
|
||||||
bitmap.getPixels(bitmapPixels, 0, width, 0, 0, width, height)
|
|
||||||
return Score.score(QuantizerCelebi.quantize(bitmapPixels, 128), 1, 0)[0]
|
|
||||||
.takeIf { it != 0 } // Don't take fallback color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.S)
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
@@ -62,64 +44,19 @@ private class MonetSystemColorScheme(context: Context) : BaseColorScheme() {
|
|||||||
override val darkScheme = dynamicDarkColorScheme(context)
|
override val darkScheme = dynamicDarkColorScheme(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MonetCompatColorScheme(context: Context, seed: Int) : BaseColorScheme() {
|
internal class MonetCompatColorScheme(seed: Color) : BaseColorScheme() {
|
||||||
|
override val lightScheme = generateColorSchemeFromSeed(seed = seed, dark = false)
|
||||||
override val lightScheme = generateColorSchemeFromSeed(context = context, seed = seed, dark = false)
|
override val darkScheme = generateColorSchemeFromSeed(seed = seed, dark = true)
|
||||||
override val darkScheme = generateColorSchemeFromSeed(context = context, seed = seed, dark = true)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun Int.toComposeColor(): Color = Color(this)
|
fun generateColorSchemeFromSeed(seed: Color, dark: Boolean): ColorScheme {
|
||||||
|
return DynamicScheme(
|
||||||
@SuppressLint("PrivateResource", "RestrictedApi")
|
seedColor = seed,
|
||||||
private fun generateColorSchemeFromSeed(context: Context, seed: Int, dark: Boolean): ColorScheme {
|
isDark = dark,
|
||||||
val scheme = SchemeContent(
|
specVersion = ColorSpec.SpecVersion.SPEC_2025,
|
||||||
Hct.fromInt(seed),
|
style = PaletteStyle.Expressive,
|
||||||
dark,
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
context.getSystemService<UiModeManager>()?.contrast?.toDouble() ?: 0.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
},
|
|
||||||
)
|
|
||||||
val dynamicColors = MaterialDynamicColors()
|
|
||||||
return ColorScheme(
|
|
||||||
primary = dynamicColors.primary().getArgb(scheme).toComposeColor(),
|
|
||||||
onPrimary = dynamicColors.onPrimary().getArgb(scheme).toComposeColor(),
|
|
||||||
primaryContainer = dynamicColors.primaryContainer().getArgb(scheme).toComposeColor(),
|
|
||||||
onPrimaryContainer = dynamicColors.onPrimaryContainer().getArgb(scheme).toComposeColor(),
|
|
||||||
inversePrimary = dynamicColors.inversePrimary().getArgb(scheme).toComposeColor(),
|
|
||||||
secondary = dynamicColors.secondary().getArgb(scheme).toComposeColor(),
|
|
||||||
onSecondary = dynamicColors.onSecondary().getArgb(scheme).toComposeColor(),
|
|
||||||
secondaryContainer = dynamicColors.secondaryContainer().getArgb(scheme).toComposeColor(),
|
|
||||||
onSecondaryContainer = dynamicColors.onSecondaryContainer().getArgb(scheme).toComposeColor(),
|
|
||||||
tertiary = dynamicColors.tertiary().getArgb(scheme).toComposeColor(),
|
|
||||||
onTertiary = dynamicColors.onTertiary().getArgb(scheme).toComposeColor(),
|
|
||||||
tertiaryContainer = dynamicColors.tertiary().getArgb(scheme).toComposeColor(),
|
|
||||||
onTertiaryContainer = dynamicColors.onTertiaryContainer().getArgb(scheme).toComposeColor(),
|
|
||||||
background = dynamicColors.background().getArgb(scheme).toComposeColor(),
|
|
||||||
onBackground = dynamicColors.onBackground().getArgb(scheme).toComposeColor(),
|
|
||||||
surface = dynamicColors.surface().getArgb(scheme).toComposeColor(),
|
|
||||||
onSurface = dynamicColors.onSurface().getArgb(scheme).toComposeColor(),
|
|
||||||
surfaceVariant = dynamicColors.surfaceVariant().getArgb(scheme).toComposeColor(),
|
|
||||||
onSurfaceVariant = dynamicColors.onSurfaceVariant().getArgb(scheme).toComposeColor(),
|
|
||||||
surfaceTint = dynamicColors.surfaceTint().getArgb(scheme).toComposeColor(),
|
|
||||||
inverseSurface = dynamicColors.inverseSurface().getArgb(scheme).toComposeColor(),
|
|
||||||
inverseOnSurface = dynamicColors.inverseOnSurface().getArgb(scheme).toComposeColor(),
|
|
||||||
error = dynamicColors.error().getArgb(scheme).toComposeColor(),
|
|
||||||
onError = dynamicColors.onError().getArgb(scheme).toComposeColor(),
|
|
||||||
errorContainer = dynamicColors.errorContainer().getArgb(scheme).toComposeColor(),
|
|
||||||
onErrorContainer = dynamicColors.onErrorContainer().getArgb(scheme).toComposeColor(),
|
|
||||||
outline = dynamicColors.outline().getArgb(scheme).toComposeColor(),
|
|
||||||
outlineVariant = dynamicColors.outlineVariant().getArgb(scheme).toComposeColor(),
|
|
||||||
scrim = Color.Black,
|
|
||||||
surfaceBright = dynamicColors.surfaceBright().getArgb(scheme).toComposeColor(),
|
|
||||||
surfaceDim = dynamicColors.surfaceDim().getArgb(scheme).toComposeColor(),
|
|
||||||
surfaceContainer = dynamicColors.surfaceContainer().getArgb(scheme).toComposeColor(),
|
|
||||||
surfaceContainerHigh = dynamicColors.surfaceContainerHigh().getArgb(scheme).toComposeColor(),
|
|
||||||
surfaceContainerHighest = dynamicColors.surfaceContainerHighest().getArgb(scheme).toComposeColor(),
|
|
||||||
surfaceContainerLow = dynamicColors.surfaceContainerLow().getArgb(scheme).toComposeColor(),
|
|
||||||
surfaceContainerLowest = dynamicColors.surfaceContainerLowest().getArgb(scheme).toComposeColor(),
|
|
||||||
)
|
)
|
||||||
|
.toColorScheme(isAmoled = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.track
|
package eu.kanade.presentation.track
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@@ -47,6 +48,7 @@ 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.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -55,11 +57,11 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.platform.ClipboardManager
|
import androidx.compose.ui.platform.Clipboard
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboard
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.platform.toClipEntry
|
||||||
import androidx.compose.ui.text.capitalize
|
import androidx.compose.ui.text.capitalize
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.intl.Locale
|
import androidx.compose.ui.text.intl.Locale
|
||||||
@@ -73,6 +75,7 @@ import eu.kanade.presentation.manga.components.MangaCover
|
|||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@@ -240,7 +243,7 @@ private fun SearchResultItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val clipboardManager: ClipboardManager = LocalClipboardManager.current
|
val clipboard: Clipboard = LocalClipboard.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current)
|
val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current)
|
||||||
val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current)
|
val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current)
|
||||||
@@ -248,6 +251,7 @@ private fun SearchResultItem(
|
|||||||
val shape = RoundedCornerShape(16.dp)
|
val shape = RoundedCornerShape(16.dp)
|
||||||
val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent
|
val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent
|
||||||
var dropDownMenuExpanded by remember { mutableStateOf(false) }
|
var dropDownMenuExpanded by remember { mutableStateOf(false) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -295,7 +299,13 @@ private fun SearchResultItem(
|
|||||||
expanded = dropDownMenuExpanded,
|
expanded = dropDownMenuExpanded,
|
||||||
onCollapseMenu = { dropDownMenuExpanded = false },
|
onCollapseMenu = { dropDownMenuExpanded = false },
|
||||||
onCopyName = {
|
onCopyName = {
|
||||||
clipboardManager.setText(AnnotatedString(trackSearch.title))
|
scope.launch {
|
||||||
|
val clipEntry = ClipData.newPlainText(
|
||||||
|
trackSearch.title,
|
||||||
|
trackSearch.title,
|
||||||
|
).toClipEntry()
|
||||||
|
clipboard.setClipEntry(clipEntry)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onOpenInBrowser = {
|
onOpenInBrowser = {
|
||||||
val url = trackSearch.tracking_url
|
val url = trackSearch.tracking_url
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
package eu.kanade.presentation.track.components
|
package eu.kanade.presentation.track.components
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
@@ -30,18 +26,13 @@ fun TrackLogoIcon(
|
|||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Image(
|
||||||
|
painter = painterResource(tracker.getLogo()),
|
||||||
|
contentDescription = tracker.name,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.background(color = Color(tracker.getLogoColor()), shape = MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium),
|
||||||
.padding(4.dp),
|
)
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(tracker.getLogo()),
|
|
||||||
contentDescription = tracker.name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewLightDark
|
@PreviewLightDark
|
||||||
|
|||||||
-4
@@ -1,8 +1,6 @@
|
|||||||
package eu.kanade.presentation.track.components
|
package eu.kanade.presentation.track.components
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import eu.kanade.test.DummyTracker
|
import eu.kanade.test.DummyTracker
|
||||||
|
|
||||||
@@ -13,8 +11,6 @@ internal class TrackLogoIconPreviewProvider : PreviewParameterProvider<Tracker>
|
|||||||
DummyTracker(
|
DummyTracker(
|
||||||
id = 1L,
|
id = 1L,
|
||||||
name = "Dummy Tracker",
|
name = "Dummy Tracker",
|
||||||
valLogoColor = Color.rgb(18, 25, 35),
|
|
||||||
valLogo = R.drawable.ic_tracker_anilist,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package eu.kanade.presentation.updates
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
|
import eu.kanade.tachiyomi.ui.updates.UpdatesSettingsScreenModel
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
|
import tachiyomi.domain.updates.service.UpdatesPreferences
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.SettingsItemsPaddings
|
||||||
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UpdatesFilterDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
screenModel: UpdatesSettingsScreenModel,
|
||||||
|
) {
|
||||||
|
TabbedDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
tabTitles = persistentListOf(
|
||||||
|
stringResource(MR.strings.action_filter),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = TabbedDialogPaddings.Vertical)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
FilterSheet(screenModel = screenModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.FilterSheet(
|
||||||
|
screenModel: UpdatesSettingsScreenModel,
|
||||||
|
) {
|
||||||
|
val filterDownloaded by screenModel.updatesPreferences.filterDownloaded().collectAsState()
|
||||||
|
TriStateItem(
|
||||||
|
label = stringResource(MR.strings.label_downloaded),
|
||||||
|
state = filterDownloaded,
|
||||||
|
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterDownloaded) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterUnread by screenModel.updatesPreferences.filterUnread().collectAsState()
|
||||||
|
TriStateItem(
|
||||||
|
label = stringResource(MR.strings.action_filter_unread),
|
||||||
|
state = filterUnread,
|
||||||
|
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterUnread) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterStarted by screenModel.updatesPreferences.filterStarted().collectAsState()
|
||||||
|
TriStateItem(
|
||||||
|
label = stringResource(MR.strings.label_started),
|
||||||
|
state = filterStarted,
|
||||||
|
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterStarted) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterBookmarked by screenModel.updatesPreferences.filterBookmarked().collectAsState()
|
||||||
|
TriStateItem(
|
||||||
|
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||||
|
state = filterBookmarked,
|
||||||
|
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterBookmarked) },
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(MaterialTheme.padding.small))
|
||||||
|
|
||||||
|
val filterExcludedScanlators by screenModel.updatesPreferences.filterExcludedScanlators().collectAsState()
|
||||||
|
|
||||||
|
fun toggleScanlatorFilter() = screenModel.updatesPreferences.filterExcludedScanlators().getAndSet { !it }
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable { toggleScanlatorFilter() }
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = SettingsItemsPaddings.Horizontal),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.action_filter_excluded_scanlators),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = filterExcludedScanlators,
|
||||||
|
onCheckedChange = { toggleScanlatorFilter() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,12 @@ 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.outlined.CalendarMonth
|
import androidx.compose.material.icons.outlined.CalendarMonth
|
||||||
|
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.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material.icons.outlined.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
@@ -37,6 +40,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
|
import tachiyomi.presentation.core.theme.active
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@@ -57,8 +61,10 @@ fun UpdateScreen(
|
|||||||
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
|
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
|
||||||
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
|
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
|
||||||
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
|
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
|
||||||
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
|
onUpdateSelected: (UpdatesItem, Boolean, Boolean) -> Unit,
|
||||||
onOpenChapter: (UpdatesItem) -> Unit,
|
onOpenChapter: (UpdatesItem) -> Unit,
|
||||||
|
onFilterClicked: () -> Unit,
|
||||||
|
hasActiveFilters: Boolean,
|
||||||
) {
|
) {
|
||||||
BackHandler(enabled = state.selectionMode) {
|
BackHandler(enabled = state.selectionMode) {
|
||||||
onSelectAll(false)
|
onSelectAll(false)
|
||||||
@@ -69,6 +75,8 @@ fun UpdateScreen(
|
|||||||
UpdatesAppBar(
|
UpdatesAppBar(
|
||||||
onCalendarClicked = { onCalendarClicked() },
|
onCalendarClicked = { onCalendarClicked() },
|
||||||
onUpdateLibrary = { onUpdateLibrary() },
|
onUpdateLibrary = { onUpdateLibrary() },
|
||||||
|
onFilterClicked = { onFilterClicked() },
|
||||||
|
hasFilters = hasActiveFilters,
|
||||||
actionModeCounter = state.selected.size,
|
actionModeCounter = state.selected.size,
|
||||||
onSelectAll = { onSelectAll(true) },
|
onSelectAll = { onSelectAll(true) },
|
||||||
onInvertSelection = { onInvertSelection() },
|
onInvertSelection = { onInvertSelection() },
|
||||||
@@ -139,6 +147,8 @@ fun UpdateScreen(
|
|||||||
private fun UpdatesAppBar(
|
private fun UpdatesAppBar(
|
||||||
onCalendarClicked: () -> Unit,
|
onCalendarClicked: () -> Unit,
|
||||||
onUpdateLibrary: () -> Unit,
|
onUpdateLibrary: () -> Unit,
|
||||||
|
onFilterClicked: () -> Unit,
|
||||||
|
hasFilters: Boolean,
|
||||||
// For action mode
|
// For action mode
|
||||||
actionModeCounter: Int,
|
actionModeCounter: Int,
|
||||||
onSelectAll: () -> Unit,
|
onSelectAll: () -> Unit,
|
||||||
@@ -153,6 +163,12 @@ private fun UpdatesAppBar(
|
|||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_filter),
|
||||||
|
icon = Icons.Outlined.FilterList,
|
||||||
|
iconTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current,
|
||||||
|
onClick = onFilterClicked,
|
||||||
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.action_view_upcoming),
|
title = stringResource(MR.strings.action_view_upcoming),
|
||||||
icon = Icons.Outlined.CalendarMonth,
|
icon = Icons.Outlined.CalendarMonth,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ internal fun LazyListScope.updatesUiItems(
|
|||||||
// SY -->
|
// SY -->
|
||||||
preserveReadingPosition: Boolean,
|
preserveReadingPosition: Boolean,
|
||||||
// SY <--
|
// SY <--
|
||||||
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
|
onUpdateSelected: (UpdatesItem, Boolean, Boolean) -> Unit,
|
||||||
onClickCover: (UpdatesItem) -> Unit,
|
onClickCover: (UpdatesItem) -> Unit,
|
||||||
onClickUpdate: (UpdatesItem) -> Unit,
|
onClickUpdate: (UpdatesItem) -> Unit,
|
||||||
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
|
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
|
||||||
@@ -120,11 +120,11 @@ internal fun LazyListScope.updatesUiItems(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
onUpdateSelected(updatesItem, !updatesItem.selected, true, true)
|
onUpdateSelected(updatesItem, !updatesItem.selected, true)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
when {
|
when {
|
||||||
selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false)
|
selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, false)
|
||||||
else -> onClickUpdate(updatesItem)
|
else -> onClickUpdate(updatesItem)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,21 +9,21 @@ import tachiyomi.domain.source.model.SourceNotInstalledException
|
|||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
context(Context)
|
context(context: Context)
|
||||||
val Throwable.formattedMessage: String
|
val Throwable.formattedMessage: String
|
||||||
get() {
|
get() {
|
||||||
when (this) {
|
when (this) {
|
||||||
is HttpException -> return stringResource(MR.strings.exception_http, code)
|
is HttpException -> return context.stringResource(MR.strings.exception_http, code)
|
||||||
is UnknownHostException -> {
|
is UnknownHostException -> {
|
||||||
return if (!isOnline()) {
|
return if (!context.isOnline()) {
|
||||||
stringResource(MR.strings.exception_offline)
|
context.stringResource(MR.strings.exception_offline)
|
||||||
} else {
|
} else {
|
||||||
stringResource(MR.strings.exception_unknown_host, message ?: "")
|
context.stringResource(MR.strings.exception_unknown_host, message ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NoResultsException -> return stringResource(MR.strings.no_results_found)
|
is NoResultsException -> return context.stringResource(MR.strings.no_results_found)
|
||||||
is SourceNotInstalledException -> return stringResource(MR.strings.loader_not_implemented_error)
|
is SourceNotInstalledException -> return context.stringResource(MR.strings.loader_not_implemented_error)
|
||||||
}
|
}
|
||||||
return when (val className = this::class.simpleName) {
|
return when (val className = this::class.simpleName) {
|
||||||
"Exception", "IOException" -> message ?: className
|
"Exception", "IOException" -> message ?: className
|
||||||
|
|||||||
@@ -4,5 +4,7 @@ import androidx.compose.foundation.lazy.LazyItemScope
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
// https://issuetracker.google.com/352584409
|
// https://issuetracker.google.com/352584409
|
||||||
context(LazyItemScope)
|
context(itemScope: LazyItemScope)
|
||||||
fun Modifier.animateItemFastScroll() = this.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
fun Modifier.animateItemFastScroll() = with(itemScope) {
|
||||||
|
this@animateItemFastScroll.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,46 +1,13 @@
|
|||||||
package eu.kanade.presentation.util
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.BackEventCompat
|
|
||||||
import androidx.activity.compose.PredictiveBackHandler
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||||
import androidx.compose.animation.ContentTransform
|
import androidx.compose.animation.ContentTransform
|
||||||
import androidx.compose.animation.EnterTransition
|
|
||||||
import androidx.compose.animation.ExitTransition
|
|
||||||
import androidx.compose.animation.SizeTransform
|
|
||||||
import androidx.compose.animation.core.AnimationSpec
|
|
||||||
import androidx.compose.animation.core.SeekableTransitionState
|
|
||||||
import androidx.compose.animation.core.Spring
|
|
||||||
import androidx.compose.animation.core.animate
|
|
||||||
import androidx.compose.animation.core.rememberTransition
|
|
||||||
import androidx.compose.animation.core.spring
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.Saver
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.snapshotFlow
|
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.util.lerp
|
|
||||||
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.ScreenModelStore
|
import cafe.adriel.voyager.core.model.ScreenModelStore
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
@@ -49,28 +16,18 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
|||||||
import cafe.adriel.voyager.core.stack.StackEvent
|
import cafe.adriel.voyager.core.stack.StackEvent
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.transitions.ScreenTransitionContent
|
import cafe.adriel.voyager.transitions.ScreenTransitionContent
|
||||||
import eu.kanade.tachiyomi.util.view.getWindowRadius
|
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.CoroutineName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.dropWhile
|
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import soup.compose.material.motion.animation.materialSharedAxisXIn
|
import soup.compose.material.motion.animation.materialSharedAxisX
|
||||||
import soup.compose.material.motion.animation.materialSharedAxisXOut
|
|
||||||
import soup.compose.material.motion.animation.rememberSlideDistance
|
import soup.compose.material.motion.animation.rememberSlideDistance
|
||||||
import tachiyomi.presentation.core.util.PredictiveBack
|
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For invoking back press to the parent activity
|
* For invoking back press to the parent activity
|
||||||
*/
|
*/
|
||||||
@SuppressLint("ComposeCompositionLocalUsage")
|
|
||||||
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
||||||
|
|
||||||
interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
|
interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
|
||||||
@@ -103,278 +60,41 @@ interface AssistContentScreen {
|
|||||||
fun onProvideAssistUrl(): String?
|
fun onProvideAssistUrl(): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(InternalVoyagerApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultNavigatorScreenTransition(
|
fun DefaultNavigatorScreenTransition(
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val screenCandidatesToDispose = rememberSaveable(saver = screenCandidatesToDisposeSaver()) {
|
val slideDistance = rememberSlideDistance()
|
||||||
mutableStateOf(emptySet())
|
|
||||||
}
|
|
||||||
val currentScreens = navigator.items
|
|
||||||
DisposableEffect(currentScreens) {
|
|
||||||
onDispose {
|
|
||||||
val newScreenKeys = navigator.items.map { it.key }
|
|
||||||
screenCandidatesToDispose.value += currentScreens.filter { it.key !in newScreenKeys }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val slideDistance = rememberSlideDistance(slideDistance = 30.dp)
|
|
||||||
ScreenTransition(
|
ScreenTransition(
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
|
transition = {
|
||||||
|
materialSharedAxisX(
|
||||||
|
forward = navigator.lastEvent != StackEvent.Pop,
|
||||||
|
slideDistance = slideDistance,
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enterTransition = {
|
|
||||||
if (it == SwipeEdge.Right) {
|
|
||||||
materialSharedAxisXIn(forward = false, slideDistance = slideDistance)
|
|
||||||
} else {
|
|
||||||
materialSharedAxisXIn(forward = true, slideDistance = slideDistance)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
exitTransition = {
|
|
||||||
if (it == SwipeEdge.Right) {
|
|
||||||
materialSharedAxisXOut(forward = false, slideDistance = slideDistance)
|
|
||||||
} else {
|
|
||||||
materialSharedAxisXOut(forward = true, slideDistance = slideDistance)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
popEnterTransition = {
|
|
||||||
if (it == SwipeEdge.Right) {
|
|
||||||
materialSharedAxisXIn(forward = true, slideDistance = slideDistance)
|
|
||||||
} else {
|
|
||||||
materialSharedAxisXIn(forward = false, slideDistance = slideDistance)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
popExitTransition = {
|
|
||||||
if (it == SwipeEdge.Right) {
|
|
||||||
materialSharedAxisXOut(forward = true, slideDistance = slideDistance)
|
|
||||||
} else {
|
|
||||||
materialSharedAxisXOut(forward = false, slideDistance = slideDistance)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
content = { screen ->
|
|
||||||
if (this.transition.targetState == this.transition.currentState) {
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
val newScreens = navigator.items.map { it.key }
|
|
||||||
val screensToDispose = screenCandidatesToDispose.value.filterNot { it.key in newScreens }
|
|
||||||
if (screensToDispose.isNotEmpty()) {
|
|
||||||
screensToDispose.forEach { navigator.dispose(it) }
|
|
||||||
navigator.clearEvent()
|
|
||||||
}
|
|
||||||
screenCandidatesToDispose.value = emptySet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.Content()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class SwipeEdge {
|
|
||||||
Unknown,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum class AnimationType {
|
|
||||||
Pop,
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScreenTransition(
|
fun ScreenTransition(
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
|
transition: AnimatedContentTransitionScope<Screen>.() -> ContentTransform,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enterTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> EnterTransition = { fadeIn() },
|
|
||||||
exitTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> ExitTransition = { fadeOut() },
|
|
||||||
popEnterTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> EnterTransition = enterTransition,
|
|
||||||
popExitTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> ExitTransition = exitTransition,
|
|
||||||
sizeTransform: (AnimatedContentTransitionScope<Screen>.() -> SizeTransform?)? = null,
|
|
||||||
flingAnimationSpec: () -> AnimationSpec<Float> = { spring(stiffness = Spring.StiffnessLow) },
|
|
||||||
content: ScreenTransitionContent = { it.Content() },
|
content: ScreenTransitionContent = { it.Content() },
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
AnimatedContent(
|
||||||
val viewConfig = LocalViewConfiguration.current
|
targetState = navigator.lastItem,
|
||||||
val scope = rememberCoroutineScope()
|
transitionSpec = transition,
|
||||||
val state = remember {
|
|
||||||
ScreenTransitionState(
|
|
||||||
navigator = navigator,
|
|
||||||
scope = scope,
|
|
||||||
flingAnimationSpec = flingAnimationSpec(),
|
|
||||||
windowCornerRadius = view.getWindowRadius().toFloat(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val transitionState = remember { SeekableTransitionState(navigator.lastItem) }
|
|
||||||
val transition = rememberTransition(transitionState = transitionState)
|
|
||||||
|
|
||||||
if (state.isPredictiveBack || state.isAnimating) {
|
|
||||||
LaunchedEffect(state.progress) {
|
|
||||||
if (!state.isPredictiveBack) return@LaunchedEffect
|
|
||||||
val previousEntry = navigator.items.getOrNull(navigator.size - 2)
|
|
||||||
if (previousEntry != null) {
|
|
||||||
transitionState.seekTo(fraction = state.progress, targetState = previousEntry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LaunchedEffect(navigator) {
|
|
||||||
snapshotFlow { navigator.lastItem }
|
|
||||||
.collect {
|
|
||||||
state.cancelCancelAnimation()
|
|
||||||
if (it != transitionState.currentState) {
|
|
||||||
transitionState.animateTo(it)
|
|
||||||
} else {
|
|
||||||
transitionState.snapTo(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PredictiveBackHandler(enabled = navigator.canPop) { backEvent ->
|
|
||||||
state.cancelCancelAnimation()
|
|
||||||
var startOffset: Offset? = null
|
|
||||||
backEvent
|
|
||||||
.dropWhile {
|
|
||||||
if (startOffset == null) startOffset = Offset(it.touchX, it.touchY)
|
|
||||||
if (state.isAnimating) return@dropWhile true
|
|
||||||
// Touch slop check
|
|
||||||
val diff = Offset(it.touchX, it.touchY) - startOffset!!
|
|
||||||
diff.x.absoluteValue < viewConfig.touchSlop && diff.y.absoluteValue < viewConfig.touchSlop
|
|
||||||
}
|
|
||||||
.onCompletion {
|
|
||||||
if (it == null) {
|
|
||||||
state.finish()
|
|
||||||
} else {
|
|
||||||
state.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.collect {
|
|
||||||
state.setPredictiveBackProgress(
|
|
||||||
progress = it.progress,
|
|
||||||
swipeEdge = when (it.swipeEdge) {
|
|
||||||
BackEventCompat.EDGE_LEFT -> SwipeEdge.Left
|
|
||||||
BackEventCompat.EDGE_RIGHT -> SwipeEdge.Right
|
|
||||||
else -> SwipeEdge.Unknown
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.AnimatedContent(
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
transitionSpec = {
|
label = "transition",
|
||||||
val pop = navigator.lastEvent == StackEvent.Pop || state.isPredictiveBack
|
) { screen ->
|
||||||
ContentTransform(
|
navigator.saveableState("transition", screen) {
|
||||||
targetContentEnter = if (pop) {
|
content(screen)
|
||||||
popEnterTransition(state.swipeEdge)
|
|
||||||
} else {
|
|
||||||
enterTransition(state.swipeEdge)
|
|
||||||
},
|
|
||||||
initialContentExit = if (pop) {
|
|
||||||
popExitTransition(state.swipeEdge)
|
|
||||||
} else {
|
|
||||||
exitTransition(state.swipeEdge)
|
|
||||||
},
|
|
||||||
targetContentZIndex = if (pop) 0f else 1f,
|
|
||||||
sizeTransform = sizeTransform?.invoke(this),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
contentKey = { it.key },
|
|
||||||
) {
|
|
||||||
navigator.saveableState("transition", it) {
|
|
||||||
content(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
BackHandler(enabled = navigator.canPop, onBack = navigator::pop)
|
||||||
@Stable
|
|
||||||
private class ScreenTransitionState(
|
|
||||||
private val navigator: Navigator,
|
|
||||||
private val scope: CoroutineScope,
|
|
||||||
private val flingAnimationSpec: AnimationSpec<Float>,
|
|
||||||
windowCornerRadius: Float,
|
|
||||||
) {
|
|
||||||
var isPredictiveBack: Boolean by mutableStateOf(false)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var progress: Float by mutableFloatStateOf(0f)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var swipeEdge: SwipeEdge by mutableStateOf(SwipeEdge.Unknown)
|
|
||||||
private set
|
|
||||||
|
|
||||||
private var animationJob: Pair<Job, AnimationType>? by mutableStateOf(null)
|
|
||||||
|
|
||||||
val isAnimating: Boolean
|
|
||||||
get() = animationJob?.first?.isActive == true
|
|
||||||
|
|
||||||
val windowCornerShape = RoundedCornerShape(windowCornerRadius)
|
|
||||||
|
|
||||||
private fun reset() {
|
|
||||||
this.isPredictiveBack = false
|
|
||||||
this.swipeEdge = SwipeEdge.Unknown
|
|
||||||
this.animationJob = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPredictiveBackProgress(progress: Float, swipeEdge: SwipeEdge) {
|
|
||||||
this.progress = lerp(0f, 0.65f, PredictiveBack.transform(progress))
|
|
||||||
this.swipeEdge = swipeEdge
|
|
||||||
this.isPredictiveBack = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun finish() {
|
|
||||||
if (!isPredictiveBack) {
|
|
||||||
navigator.pop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
animationJob = scope.launch {
|
|
||||||
try {
|
|
||||||
animate(
|
|
||||||
initialValue = progress,
|
|
||||||
targetValue = 1f,
|
|
||||||
animationSpec = flingAnimationSpec,
|
|
||||||
block = { i, _ -> progress = i },
|
|
||||||
)
|
|
||||||
navigator.pop()
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
// Cancelled
|
|
||||||
progress = 0f
|
|
||||||
} finally {
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
} to AnimationType.Pop
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel() {
|
|
||||||
if (!isPredictiveBack) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
animationJob = scope.launch {
|
|
||||||
try {
|
|
||||||
animate(
|
|
||||||
initialValue = progress,
|
|
||||||
targetValue = 0f,
|
|
||||||
animationSpec = flingAnimationSpec,
|
|
||||||
block = { i, _ -> progress = i },
|
|
||||||
)
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
// Cancelled
|
|
||||||
progress = 1f
|
|
||||||
} finally {
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
} to AnimationType.Cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancelCancelAnimation() {
|
|
||||||
if (animationJob?.second == AnimationType.Cancel) {
|
|
||||||
animationJob?.first?.cancel()
|
|
||||||
animationJob = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun screenCandidatesToDisposeSaver(): Saver<MutableState<Set<Screen>>, List<Screen>> {
|
|
||||||
return Saver(
|
|
||||||
save = { it.value.toList() },
|
|
||||||
restore = { mutableStateOf(it.toSet()) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ fun EhLoginWebViewScreen(
|
|||||||
)
|
)
|
||||||
is LoadingState.Loading -> {
|
is LoadingState.Loading -> {
|
||||||
val animatedProgress by animateFloatAsState(
|
val animatedProgress by animateFloatAsState(
|
||||||
(loadingState as? LoadingState.Loading)?.progress ?: 1f,
|
loadingState.progress,
|
||||||
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||||
label = "webview_loading",
|
label = "webview_loading",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ 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.os.Message
|
||||||
import android.webkit.WebResourceRequest
|
import android.webkit.WebResourceRequest
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -19,6 +21,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -26,17 +29,23 @@ 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.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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.core.stack.mutableStateStackOf
|
||||||
|
import com.kevinnzou.web.AccompanistWebChromeClient
|
||||||
import com.kevinnzou.web.AccompanistWebViewClient
|
import com.kevinnzou.web.AccompanistWebViewClient
|
||||||
import com.kevinnzou.web.LoadingState
|
import com.kevinnzou.web.LoadingState
|
||||||
|
import com.kevinnzou.web.WebContent
|
||||||
import com.kevinnzou.web.WebView
|
import com.kevinnzou.web.WebView
|
||||||
import com.kevinnzou.web.rememberWebViewNavigator
|
import com.kevinnzou.web.WebViewNavigator
|
||||||
import com.kevinnzou.web.rememberWebViewState
|
import com.kevinnzou.web.WebViewState
|
||||||
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.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
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
|
||||||
@@ -44,6 +53,18 @@ import kotlinx.coroutines.launch
|
|||||||
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
|
||||||
|
|
||||||
|
class WebViewWindow(webContent: WebContent, val navigator: WebViewNavigator) {
|
||||||
|
var state by mutableStateOf(WebViewState(webContent))
|
||||||
|
var popupMessage: Message? = null
|
||||||
|
private set
|
||||||
|
var webView: WebView? = null
|
||||||
|
|
||||||
|
constructor(popupMessage: Message, navigator: WebViewNavigator) : this(WebContent.NavigatorOnly, navigator) {
|
||||||
|
this.popupMessage = popupMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WebViewScreenContent(
|
fun WebViewScreenContent(
|
||||||
onNavigateUp: () -> Unit,
|
onNavigateUp: () -> Unit,
|
||||||
@@ -55,8 +76,20 @@ fun WebViewScreenContent(
|
|||||||
headers: Map<String, String> = emptyMap(),
|
headers: Map<String, String> = emptyMap(),
|
||||||
onUrlChange: (String) -> Unit = {},
|
onUrlChange: (String) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val navigator = rememberWebViewNavigator()
|
|
||||||
|
val windowStack = remember {
|
||||||
|
mutableStateStackOf(
|
||||||
|
WebViewWindow(
|
||||||
|
WebContent.Url(url = url, additionalHttpHeaders = headers),
|
||||||
|
WebViewNavigator(coroutineScope),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentWindow = windowStack.lastItemOrNull!!
|
||||||
|
val navigator = currentWindow.navigator
|
||||||
|
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -97,31 +130,67 @@ fun WebViewScreenContent(
|
|||||||
view: WebView?,
|
view: WebView?,
|
||||||
request: WebResourceRequest?,
|
request: WebResourceRequest?,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
request?.let {
|
val url = request?.url?.toString() ?: return false
|
||||||
// Don't attempt to open blobs as webpages
|
|
||||||
if (it.url.toString().startsWith("blob:http")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore intents urls
|
// Ignore intents urls
|
||||||
if (it.url.toString().startsWith("intent://")) {
|
if (url.startsWith("intent://")) return true
|
||||||
|
|
||||||
|
// Only open valid web urls
|
||||||
|
if (url.startsWith("http") || url.startsWith("https")) {
|
||||||
|
if (url != view?.url) {
|
||||||
|
view?.loadUrl(url, headers)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue with request, but with custom headers
|
|
||||||
view?.loadUrl(it.url.toString(), headers)
|
|
||||||
}
|
}
|
||||||
return super.shouldOverrideUrlLoading(view, request)
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val webChromeClient = remember {
|
||||||
|
object : AccompanistWebChromeClient() {
|
||||||
|
override fun onCreateWindow(
|
||||||
|
view: WebView,
|
||||||
|
isDialog: Boolean,
|
||||||
|
isUserGesture: Boolean,
|
||||||
|
resultMsg: Message,
|
||||||
|
): Boolean {
|
||||||
|
// if it wasn't initiated by a user gesture, we should ignore it like a normal browser would
|
||||||
|
if (isUserGesture) {
|
||||||
|
windowStack.push(WebViewWindow(resultMsg, WebViewNavigator(coroutineScope)))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initializePopup(webView: WebView, message: Message): WebView {
|
||||||
|
val transport = message.obj as WebView.WebViewTransport
|
||||||
|
transport.webView = webView
|
||||||
|
message.sendToTarget()
|
||||||
|
return webView
|
||||||
|
}
|
||||||
|
|
||||||
|
val popState = remember<() -> Unit> {
|
||||||
|
{
|
||||||
|
if (windowStack.size == 1) {
|
||||||
|
onNavigateUp()
|
||||||
|
} else {
|
||||||
|
windowStack.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(windowStack.size > 1, popState)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Box {
|
Box {
|
||||||
Column {
|
Column {
|
||||||
AppBar(
|
AppBar(
|
||||||
title = state.pageTitle ?: initialTitle,
|
title = currentWindow.state.pageTitle ?: initialTitle,
|
||||||
subtitle = currentUrl,
|
subtitle = currentUrl,
|
||||||
navigateUp = onNavigateUp,
|
navigateUp = onNavigateUp,
|
||||||
navigationIcon = Icons.Outlined.Close,
|
navigationIcon = Icons.Outlined.Close,
|
||||||
@@ -164,7 +233,18 @@ fun WebViewScreenContent(
|
|||||||
title = stringResource(MR.strings.pref_clear_cookies),
|
title = stringResource(MR.strings.pref_clear_cookies),
|
||||||
onClick = { onClearCookies(currentUrl) },
|
onClick = { onClearCookies(currentUrl) },
|
||||||
),
|
),
|
||||||
),
|
).builder().apply {
|
||||||
|
if (windowStack.size > 1) {
|
||||||
|
add(
|
||||||
|
0,
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_webview_close_tab),
|
||||||
|
icon = ImageVector.vectorResource(R.drawable.ic_tab_close_24px),
|
||||||
|
onClick = popState,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.build(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -186,14 +266,14 @@ fun WebViewScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (val loadingState = state.loadingState) {
|
when (val loadingState = currentWindow.state.loadingState) {
|
||||||
is LoadingState.Initializing -> LinearProgressIndicator(
|
is LoadingState.Initializing -> LinearProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.BottomCenter),
|
.align(Alignment.BottomCenter),
|
||||||
)
|
)
|
||||||
is LoadingState.Loading -> LinearProgressIndicator(
|
is LoadingState.Loading -> LinearProgressIndicator(
|
||||||
progress = { (loadingState as? LoadingState.Loading)?.progress ?: 1f },
|
progress = { loadingState.progress },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.BottomCenter),
|
.align(Alignment.BottomCenter),
|
||||||
@@ -203,27 +283,55 @@ fun WebViewScreenContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
WebView(
|
// We need to key the WebView composable to the window object since simply updating the WebView composable will
|
||||||
state = state,
|
// not cause it to re-invoke the WebView factory and render the new current window's WebView. This lets us
|
||||||
modifier = Modifier
|
// completely reset the WebView composable when the current window switches.
|
||||||
.fillMaxSize()
|
key(currentWindow) {
|
||||||
.padding(contentPadding),
|
WebView(
|
||||||
navigator = navigator,
|
state = currentWindow.state,
|
||||||
onCreated = { webView ->
|
modifier = Modifier
|
||||||
webView.setDefaultSettings()
|
.fillMaxSize()
|
||||||
|
.padding(contentPadding),
|
||||||
|
navigator = navigator,
|
||||||
|
onCreated = { webView ->
|
||||||
|
webView.setDefaultSettings()
|
||||||
|
|
||||||
// Debug mode (chrome://inspect/#devices)
|
// Debug mode (chrome://inspect/#devices)
|
||||||
if (BuildConfig.DEBUG &&
|
if (BuildConfig.DEBUG &&
|
||||||
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
|
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
|
||||||
) {
|
) {
|
||||||
WebView.setWebContentsDebuggingEnabled(true)
|
WebView.setWebContentsDebuggingEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers["user-agent"]?.let {
|
headers["user-agent"]?.let {
|
||||||
webView.settings.userAgentString = it
|
webView.settings.userAgentString = it
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
client = webClient,
|
onDispose = { webView ->
|
||||||
)
|
val window = windowStack.items.find { it.webView == webView }
|
||||||
|
if (window == null) {
|
||||||
|
// If we couldn't find any window on the stack that owns this WebView, it means that we can
|
||||||
|
// safely dispose of it because the window containing it has been closed.
|
||||||
|
webView.destroy()
|
||||||
|
} else {
|
||||||
|
// The composable is being disposed but the WebView object is not.
|
||||||
|
// When the WebView element is recomposed, we will want the WebView to resume from its state
|
||||||
|
// before it was unmounted, we won't want it to reset back to its original target.
|
||||||
|
window.state.content = WebContent.NavigatorOnly
|
||||||
|
}
|
||||||
|
},
|
||||||
|
client = webClient,
|
||||||
|
chromeClient = webChromeClient,
|
||||||
|
factory = { context ->
|
||||||
|
currentWindow.webView
|
||||||
|
?: WebView(context).also { webView ->
|
||||||
|
currentWindow.webView = webView
|
||||||
|
currentWindow.popupMessage?.let {
|
||||||
|
initializePopup(webView, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.work.Configuration
|
|||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import coil3.ImageLoader
|
import coil3.ImageLoader
|
||||||
import coil3.SingletonImageLoader
|
import coil3.SingletonImageLoader
|
||||||
|
import coil3.memory.MemoryCache
|
||||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||||
import coil3.request.allowRgb565
|
import coil3.request.allowRgb565
|
||||||
import coil3.request.crossfade
|
import coil3.request.crossfade
|
||||||
@@ -130,7 +131,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
setupExhLogging() // EXH logging
|
setupExhLogging() // EXH logging
|
||||||
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog
|
LogcatLogger.install()
|
||||||
|
LogcatLogger.loggers += XLogLogcatLogger() // SY Redirect Logcat to XLog
|
||||||
|
|
||||||
setupNotificationChannels()
|
setupNotificationChannels()
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
val pendingIntent = PendingIntent.getBroadcast(
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
this@App,
|
this@App,
|
||||||
0,
|
0,
|
||||||
Intent(ACTION_DISABLE_INCOGNITO_MODE),
|
Intent(ACTION_DISABLE_INCOGNITO_MODE).setPackage(BuildConfig.APPLICATION_ID),
|
||||||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
|
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
|
||||||
)
|
)
|
||||||
setContentIntent(pendingIntent)
|
setContentIntent(pendingIntent)
|
||||||
@@ -246,6 +248,12 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memoryCache(
|
||||||
|
MemoryCache.Builder()
|
||||||
|
.maxSizePercent(context)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
|
||||||
crossfade((300 * this@App.animatorDurationScale).toInt())
|
crossfade((300 * this@App.animatorDurationScale).toInt())
|
||||||
allowRgb565(DeviceUtil.isLowRamDevice(this@App))
|
allowRgb565(DeviceUtil.isLowRamDevice(this@App))
|
||||||
if (networkPreferences.verboseLogging().get()) logger(DebugLogger())
|
if (networkPreferences.verboseLogging().get()) logger(DebugLogger())
|
||||||
@@ -278,8 +286,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
// 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 isChromiumCall = stackTrace.any { trace ->
|
val isChromiumCall = stackTrace.any { trace ->
|
||||||
trace.className.equals("org.chromium.base.BuildInfo", ignoreCase = true) &&
|
trace.className.lowercase() in setOf("org.chromium.base.buildinfo", "org.chromium.base.apkinfo") &&
|
||||||
setOf("getAll", "getPackageName", "<init>").any { trace.methodName.equals(it, ignoreCase = true) }
|
trace.methodName.lowercase() in setOf("getall", "getpackagename", "<init>")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isChromiumCall) return WebViewUtil.spoofedPackageName(applicationContext)
|
if (isChromiumCall) return WebViewUtil.spoofedPackageName(applicationContext)
|
||||||
|
|||||||
+1
@@ -138,6 +138,7 @@ private fun Manga.toBackupManga(/* SY --> */customMangaInfo: CustomMangaInfo?/*
|
|||||||
favoriteModifiedAt = this.favoriteModifiedAt,
|
favoriteModifiedAt = this.favoriteModifiedAt,
|
||||||
version = this.version,
|
version = this.version,
|
||||||
notes = this.notes,
|
notes = this.notes,
|
||||||
|
initialized = this.initialized,
|
||||||
// SY -->
|
// SY -->
|
||||||
).also { backupManga ->
|
).also { backupManga ->
|
||||||
customMangaInfo?.let {
|
customMangaInfo?.let {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ data class BackupManga(
|
|||||||
@ProtoNumber(108) var excludedScanlators: List<String> = emptyList(),
|
@ProtoNumber(108) var excludedScanlators: List<String> = emptyList(),
|
||||||
@ProtoNumber(109) var version: Long = 0,
|
@ProtoNumber(109) var version: Long = 0,
|
||||||
@ProtoNumber(110) var notes: String = "",
|
@ProtoNumber(110) var notes: String = "",
|
||||||
|
@ProtoNumber(111) var initialized: Boolean = false,
|
||||||
|
|
||||||
// SY specific values
|
// SY specific values
|
||||||
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
|
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
|
||||||
@@ -80,6 +81,7 @@ data class BackupManga(
|
|||||||
favoriteModifiedAt = this@BackupManga.favoriteModifiedAt,
|
favoriteModifiedAt = this@BackupManga.favoriteModifiedAt,
|
||||||
version = this@BackupManga.version,
|
version = this@BackupManga.version,
|
||||||
notes = this@BackupManga.notes,
|
notes = this@BackupManga.notes,
|
||||||
|
initialized = this@BackupManga.initialized,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,9 +171,7 @@ class MangaRestorer(
|
|||||||
manga: Manga,
|
manga: Manga,
|
||||||
): Manga {
|
): Manga {
|
||||||
return manga.copy(
|
return manga.copy(
|
||||||
initialized = manga.description != null,
|
|
||||||
id = insertManga(manga),
|
id = insertManga(manga),
|
||||||
version = manga.version,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@@ -148,7 +147,7 @@ class ChapterCache(
|
|||||||
fun isImageInCache(imageUrl: String): Boolean {
|
fun isImageInCache(imageUrl: String): Boolean {
|
||||||
return try {
|
return try {
|
||||||
diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)).use { it != null }
|
diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)).use { it != null }
|
||||||
} catch (e: IOException) {
|
} catch (_: IOException) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +179,7 @@ class ChapterCache(
|
|||||||
try {
|
try {
|
||||||
// Get editor from md5 key.
|
// Get editor from md5 key.
|
||||||
val key = DiskUtil.hashKeyForDisk(imageUrl)
|
val key = DiskUtil.hashKeyForDisk(imageUrl)
|
||||||
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
|
editor = diskCache.edit(key) ?: return
|
||||||
|
|
||||||
// Get OutputStream and write image with Okio.
|
// Get OutputStream and write image with Okio.
|
||||||
response.body.source().saveTo(editor.newOutputStream(0))
|
response.body.source().saveTo(editor.newOutputStream(0))
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.jakewharton.disklrucache.DiskLruCache
|
|||||||
import eu.kanade.tachiyomi.source.PagePreviewPage
|
import eu.kanade.tachiyomi.source.PagePreviewPage
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import okio.Source
|
import okio.Source
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.cache.PagePreviewCache
|
|||||||
import eu.kanade.tachiyomi.network.await
|
import eu.kanade.tachiyomi.network.await
|
||||||
import eu.kanade.tachiyomi.source.PagePreviewSource
|
import eu.kanade.tachiyomi.source.PagePreviewSource
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import exh.source.getMainSource
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
@@ -249,7 +250,7 @@ class PagePreviewFetcher(
|
|||||||
isInCache = { pagePreviewCache.isImageInCache(data.imageUrl) },
|
isInCache = { pagePreviewCache.isImageInCache(data.imageUrl) },
|
||||||
writeToCache = { pagePreviewCache.putImageToCache(data.imageUrl, it) },
|
writeToCache = { pagePreviewCache.putImageToCache(data.imageUrl, it) },
|
||||||
diskCacheKeyLazy = lazy { imageLoader.components.key(data, options)!! },
|
diskCacheKeyLazy = lazy { imageLoader.components.key(data, options)!! },
|
||||||
sourceLazy = lazy { sourceManager.get(data.source) as? PagePreviewSource },
|
sourceLazy = lazy { sourceManager.get(data.source)?.getMainSource<PagePreviewSource>() },
|
||||||
callFactoryLazy = callFactoryLazy,
|
callFactoryLazy = callFactoryLazy,
|
||||||
imageLoader = imageLoader,
|
imageLoader = imageLoader,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ class DownloadCache(
|
|||||||
*
|
*
|
||||||
* @param chapterName the name of the chapter to query.
|
* @param chapterName the name of the chapter to query.
|
||||||
* @param chapterScanlator scanlator of the chapter to query
|
* @param chapterScanlator scanlator of the chapter to query
|
||||||
|
* @param chapterUrl the url of the chapter to query
|
||||||
* @param mangaTitle the title of the manga to query.
|
* @param mangaTitle the title of the manga to query.
|
||||||
* @param sourceId the id of the source of the chapter.
|
* @param sourceId the id of the source of the chapter.
|
||||||
* @param skipCache whether to skip the directory cache and check in the filesystem.
|
* @param skipCache whether to skip the directory cache and check in the filesystem.
|
||||||
@@ -135,13 +136,14 @@ class DownloadCache(
|
|||||||
fun isChapterDownloaded(
|
fun isChapterDownloaded(
|
||||||
chapterName: String,
|
chapterName: String,
|
||||||
chapterScanlator: String?,
|
chapterScanlator: String?,
|
||||||
|
chapterUrl: String,
|
||||||
mangaTitle: String,
|
mangaTitle: String,
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
skipCache: Boolean,
|
skipCache: Boolean,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (skipCache) {
|
if (skipCache) {
|
||||||
val source = sourceManager.getOrStub(sourceId)
|
val source = sourceManager.getOrStub(sourceId)
|
||||||
return provider.findChapterDir(chapterName, chapterScanlator, mangaTitle, source) != null
|
return provider.findChapterDir(chapterName, chapterScanlator, chapterUrl, mangaTitle, source) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
renewCache()
|
renewCache()
|
||||||
@@ -153,6 +155,7 @@ class DownloadCache(
|
|||||||
return provider.getValidChapterDirNames(
|
return provider.getValidChapterDirNames(
|
||||||
chapterName,
|
chapterName,
|
||||||
chapterScanlator,
|
chapterScanlator,
|
||||||
|
chapterUrl,
|
||||||
).any { it in mangaDir.chapterDirs }
|
).any { it in mangaDir.chapterDirs }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +242,7 @@ class DownloadCache(
|
|||||||
/* SY --> */ manga.ogTitle, /* SY <-- */
|
/* SY --> */ manga.ogTitle, /* SY <-- */
|
||||||
),
|
),
|
||||||
] ?: return
|
] ?: return
|
||||||
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
|
provider.getValidChapterDirNames(chapter.name, chapter.scanlator, chapter.url).forEach {
|
||||||
if (it in mangaDir.chapterDirs) {
|
if (it in mangaDir.chapterDirs) {
|
||||||
mangaDir.chapterDirs -= it
|
mangaDir.chapterDirs -= it
|
||||||
}
|
}
|
||||||
@@ -279,7 +282,7 @@ class DownloadCache(
|
|||||||
),
|
),
|
||||||
] ?: return
|
] ?: return
|
||||||
chapters.forEach { chapter ->
|
chapters.forEach { chapter ->
|
||||||
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
|
provider.getValidChapterDirNames(chapter.name, chapter.scanlator, chapter.url).forEach {
|
||||||
if (it in mangaDir.chapterDirs) {
|
if (it in mangaDir.chapterDirs) {
|
||||||
mangaDir.chapterDirs -= it
|
mangaDir.chapterDirs -= it
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ class DownloadManager(
|
|||||||
val chapterDir = provider.findChapterDir(
|
val chapterDir = provider.findChapterDir(
|
||||||
chapter.name,
|
chapter.name,
|
||||||
chapter.scanlator,
|
chapter.scanlator,
|
||||||
|
chapter.url,
|
||||||
/* SY --> */ manga.ogTitle /* SY <-- */,
|
/* SY --> */ manga.ogTitle /* SY <-- */,
|
||||||
source,
|
source,
|
||||||
)
|
)
|
||||||
@@ -195,11 +196,12 @@ class DownloadManager(
|
|||||||
fun isChapterDownloaded(
|
fun isChapterDownloaded(
|
||||||
chapterName: String,
|
chapterName: String,
|
||||||
chapterScanlator: String?,
|
chapterScanlator: String?,
|
||||||
|
chapterUrl: String,
|
||||||
mangaTitle: String,
|
mangaTitle: String,
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
skipCache: Boolean = false,
|
skipCache: Boolean = false,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return cache.isChapterDownloaded(chapterName, chapterScanlator, mangaTitle, sourceId, skipCache)
|
return cache.isChapterDownloaded(chapterName, chapterScanlator, chapterUrl, mangaTitle, sourceId, skipCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -440,7 +442,7 @@ class DownloadManager(
|
|||||||
* @param newChapter the target chapter with the new name.
|
* @param newChapter the target chapter with the new name.
|
||||||
*/
|
*/
|
||||||
suspend fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
|
suspend fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
|
||||||
val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator)
|
val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator, oldChapter.url)
|
||||||
val mangaDir = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source).getOrElse { e ->
|
val mangaDir = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source).getOrElse { e ->
|
||||||
logcat(LogPriority.ERROR, e) { "Manga download folder doesn't exist. Skipping renaming after source sync" }
|
logcat(LogPriority.ERROR, e) { "Manga download folder doesn't exist. Skipping renaming after source sync" }
|
||||||
return
|
return
|
||||||
@@ -451,7 +453,7 @@ class DownloadManager(
|
|||||||
.mapNotNull { mangaDir.findFile(it) }
|
.mapNotNull { mangaDir.findFile(it) }
|
||||||
.firstOrNull() ?: return
|
.firstOrNull() ?: return
|
||||||
|
|
||||||
var newName = provider.getChapterDirName(newChapter.name, newChapter.scanlator)
|
var newName = provider.getChapterDirName(newChapter.name, newChapter.scanlator, newChapter.url)
|
||||||
if (oldDownload.isFile && oldDownload.extension == "cbz") {
|
if (oldDownload.isFile && oldDownload.extension == "cbz") {
|
||||||
newName += ".cbz"
|
newName += ".cbz"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.download
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ package eu.kanade.tachiyomi.data.download
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.util.lang.Hash.md5
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.core.common.storage.displayablePath
|
import tachiyomi.core.common.storage.displayablePath
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.storage.service.StorageManager
|
import tachiyomi.domain.storage.service.StorageManager
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -25,6 +28,8 @@ import java.io.IOException
|
|||||||
class DownloadProvider(
|
class DownloadProvider(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val storageManager: StorageManager = Injekt.get(),
|
private val storageManager: StorageManager = Injekt.get(),
|
||||||
|
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
|
private val downloadPreferences: DownloadPreferences = Injekt.get(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val downloadsDir: UniFile?
|
private val downloadsDir: UniFile?
|
||||||
@@ -96,9 +101,15 @@ class DownloadProvider(
|
|||||||
* @param mangaTitle the title of the manga to query.
|
* @param mangaTitle the title of the manga to query.
|
||||||
* @param source the source of the chapter.
|
* @param source the source of the chapter.
|
||||||
*/
|
*/
|
||||||
fun findChapterDir(chapterName: String, chapterScanlator: String?, mangaTitle: String, source: Source): UniFile? {
|
fun findChapterDir(
|
||||||
|
chapterName: String,
|
||||||
|
chapterScanlator: String?,
|
||||||
|
chapterUrl: String,
|
||||||
|
mangaTitle: String,
|
||||||
|
source: Source,
|
||||||
|
): UniFile? {
|
||||||
val mangaDir = findMangaDir(mangaTitle, source)
|
val mangaDir = findMangaDir(mangaTitle, source)
|
||||||
return getValidChapterDirNames(chapterName, chapterScanlator).asSequence()
|
return getValidChapterDirNames(chapterName, chapterScanlator, chapterUrl).asSequence()
|
||||||
.mapNotNull { mangaDir?.findFile(it) }
|
.mapNotNull { mangaDir?.findFile(it) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
}
|
}
|
||||||
@@ -113,7 +124,7 @@ class DownloadProvider(
|
|||||||
fun findChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): Pair<UniFile?, List<UniFile>> {
|
fun findChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): Pair<UniFile?, List<UniFile>> {
|
||||||
val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return null to emptyList()
|
val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return null to emptyList()
|
||||||
return mangaDir to chapters.mapNotNull { chapter ->
|
return mangaDir to chapters.mapNotNull { chapter ->
|
||||||
getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence()
|
getValidChapterDirNames(chapter.name, chapter.scanlator, chapter.url).asSequence()
|
||||||
.mapNotNull { mangaDir.findFile(it) }
|
.mapNotNull { mangaDir.findFile(it) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
}
|
}
|
||||||
@@ -136,7 +147,7 @@ class DownloadProvider(
|
|||||||
val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return emptyList()
|
val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return emptyList()
|
||||||
return mangaDir.listFiles().orEmpty().asList().filter {
|
return mangaDir.listFiles().orEmpty().asList().filter {
|
||||||
chapters.find { chp ->
|
chapters.find { chp ->
|
||||||
getValidChapterDirNames(chp.name, chp.scanlator).any { dir ->
|
getValidChapterDirNames(chp.name, chp.scanlator, chp.url).any { dir ->
|
||||||
mangaDir.findFile(dir) != null
|
mangaDir.findFile(dir) != null
|
||||||
}
|
}
|
||||||
} == null ||
|
} == null ||
|
||||||
@@ -151,7 +162,10 @@ class DownloadProvider(
|
|||||||
* @param source the source to query.
|
* @param source the source to query.
|
||||||
*/
|
*/
|
||||||
fun getSourceDirName(source: Source): String {
|
fun getSourceDirName(source: Source): String {
|
||||||
return DiskUtil.buildValidFilename(source.toString())
|
return DiskUtil.buildValidFilename(
|
||||||
|
source.toString(),
|
||||||
|
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,23 +174,77 @@ class DownloadProvider(
|
|||||||
* @param mangaTitle the title of the manga to query.
|
* @param mangaTitle the title of the manga to query.
|
||||||
*/
|
*/
|
||||||
fun getMangaDirName(mangaTitle: String): String {
|
fun getMangaDirName(mangaTitle: String): String {
|
||||||
return DiskUtil.buildValidFilename(mangaTitle)
|
return DiskUtil.buildValidFilename(
|
||||||
|
mangaTitle,
|
||||||
|
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the chapter directory name for a chapter.
|
* Returns the chapter directory name for a chapter.
|
||||||
*
|
*
|
||||||
* @param chapterName the name of the chapter to query.
|
* @param chapterName the name of the chapter to query.
|
||||||
* @param chapterScanlator scanlator of the chapter to query
|
* @param chapterScanlator scanlator of the chapter to query.
|
||||||
|
* @param chapterUrl url of the chapter to query.
|
||||||
*/
|
*/
|
||||||
fun getChapterDirName(chapterName: String, chapterScanlator: String?): String {
|
fun getChapterDirName(
|
||||||
val newChapterName = sanitizeChapterName(chapterName)
|
chapterName: String,
|
||||||
return DiskUtil.buildValidFilename(
|
chapterScanlator: String?,
|
||||||
|
chapterUrl: String,
|
||||||
|
disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames().get(),
|
||||||
|
includeChapterUrlHash: Boolean = downloadPreferences.includeChapterUrlHash().get(),
|
||||||
|
): String {
|
||||||
|
var dirName = sanitizeChapterName(chapterName)
|
||||||
|
if (!chapterScanlator.isNullOrBlank()) {
|
||||||
|
dirName = chapterScanlator + "_" + dirName
|
||||||
|
}
|
||||||
|
// Subtract 7 bytes for hash and underscore, 4 bytes for .cbz
|
||||||
|
dirName = DiskUtil.buildValidFilename(dirName, DiskUtil.MAX_FILE_NAME_BYTES - 11, disallowNonAsciiFilenames)
|
||||||
|
if (includeChapterUrlHash) dirName += "_" + md5(chapterUrl).take(6)
|
||||||
|
return dirName
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of names that might have been previously used as
|
||||||
|
* the directory name for a chapter.
|
||||||
|
* Add to this list if naming pattern ever changes.
|
||||||
|
*
|
||||||
|
* @param chapterName the name of the chapter to query.
|
||||||
|
* @param chapterScanlator scanlator of the chapter to query.
|
||||||
|
* @param chapterUrl url of the chapter to query.
|
||||||
|
*/
|
||||||
|
private fun getLegacyChapterDirNames(
|
||||||
|
chapterName: String,
|
||||||
|
chapterScanlator: String?,
|
||||||
|
chapterUrl: String,
|
||||||
|
): List<String> {
|
||||||
|
val sanitizedChapterName = sanitizeChapterName(chapterName)
|
||||||
|
val chapterNameV1 = DiskUtil.buildValidFilename(
|
||||||
when {
|
when {
|
||||||
!chapterScanlator.isNullOrBlank() -> "${chapterScanlator}_$newChapterName"
|
!chapterScanlator.isNullOrBlank() -> "${chapterScanlator}_$sanitizedChapterName"
|
||||||
else -> newChapterName
|
else -> sanitizedChapterName
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Get the filename that would be generated if the user were
|
||||||
|
// using the other value for the disallow non-ASCII
|
||||||
|
// filenames setting. This ensures that chapters downloaded
|
||||||
|
// before the user changed the setting can still be found.
|
||||||
|
val otherChapterDirName =
|
||||||
|
getChapterDirName(
|
||||||
|
chapterName,
|
||||||
|
chapterScanlator,
|
||||||
|
chapterUrl,
|
||||||
|
!libraryPreferences.disallowNonAsciiFilenames().get(),
|
||||||
|
!downloadPreferences.includeChapterUrlHash().get(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return buildList(2) {
|
||||||
|
// Chapter name without hash (unable to handle duplicate
|
||||||
|
// chapter names)
|
||||||
|
add(chapterNameV1)
|
||||||
|
add(otherChapterDirName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,22 +259,22 @@ class DownloadProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isChapterDirNameChanged(oldChapter: Chapter, newChapter: Chapter): Boolean {
|
fun isChapterDirNameChanged(oldChapter: Chapter, newChapter: Chapter): Boolean {
|
||||||
return oldChapter.name != newChapter.name ||
|
return getChapterDirName(oldChapter.name, oldChapter.scanlator, oldChapter.url) !=
|
||||||
oldChapter.scanlator?.takeIf { it.isNotBlank() } != newChapter.scanlator?.takeIf { it.isNotBlank() }
|
getChapterDirName(newChapter.name, newChapter.scanlator, newChapter.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns valid downloaded chapter directory names.
|
* Returns valid downloaded chapter directory names.
|
||||||
*
|
*
|
||||||
* @param chapterName the name of the chapter to query.
|
* @param chapter the domain chapter object.
|
||||||
* @param chapterScanlator scanlator of the chapter to query
|
|
||||||
*/
|
*/
|
||||||
fun getValidChapterDirNames(chapterName: String, chapterScanlator: String?): List<String> {
|
fun getValidChapterDirNames(chapterName: String, chapterScanlator: String?, chapterUrl: String): List<String> {
|
||||||
val chapterDirName = getChapterDirName(chapterName, chapterScanlator)
|
val chapterDirName = getChapterDirName(chapterName, chapterScanlator, chapterUrl)
|
||||||
return buildList(4) {
|
val legacyChapterDirNames = getLegacyChapterDirNames(chapterName, chapterScanlator, chapterUrl)
|
||||||
|
|
||||||
|
return buildList {
|
||||||
// Folder of images
|
// Folder of images
|
||||||
add(chapterDirName)
|
add(chapterDirName)
|
||||||
|
|
||||||
// Archived chapters
|
// Archived chapters
|
||||||
add("$chapterDirName.cbz")
|
add("$chapterDirName.cbz")
|
||||||
|
|
||||||
@@ -218,6 +286,12 @@ class DownloadProvider(
|
|||||||
// Legacy chapter directory name used in v0.9.2 and before
|
// Legacy chapter directory name used in v0.9.2 and before
|
||||||
add(DiskUtil.buildValidFilename(chapterName))
|
add(DiskUtil.buildValidFilename(chapterName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// any legacy names
|
||||||
|
legacyChapterDirNames.forEach {
|
||||||
|
add(it)
|
||||||
|
add("$it.cbz")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user