mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-17 06:02:28 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0648e2eaaf | ||
|
|
7d4e1476ba | ||
|
+9 |
46a2471abb | ||
|
|
f63dc799f0 | ||
|
|
c114028a33 | ||
|
|
4296d056b5 | ||
|
|
7aaa891aef | ||
|
|
2ccb0ca163 | ||
|
|
44cd9a55d1 | ||
|
|
2a92f0b46a | ||
|
|
f37e81991e | ||
|
|
d15a05602a | ||
|
|
fbf5ae82f7 | ||
|
|
fdb223d657 | ||
|
|
54ae69eaa2 | ||
|
|
45b1e781eb | ||
|
|
069e5c91ac | ||
|
|
4ac9f3474b | ||
|
|
9c3cc1ca5f | ||
|
|
47d7c0f2ea | ||
|
|
0b5e9bb815 | ||
|
|
d72ae365cb | ||
|
|
e7ec672c8b | ||
|
|
628cb58862 | ||
|
|
e454cf8e9d | ||
|
|
baa0fb7ecb | ||
|
|
2d2cf0879f | ||
|
|
6981185f4b | ||
|
|
47aa9190d4 | ||
|
|
98705910e7 | ||
|
|
5ce7d00eb8 | ||
|
|
03229a3807 | ||
|
|
493140c7ff | ||
|
|
98bb731b4d | ||
|
|
c3b99aea07 | ||
|
|
ef1c529672 | ||
|
|
633eea4ced | ||
|
|
31a4d6ba39 | ||
|
|
e9b7c760a1 | ||
|
|
ef1e7ba907 | ||
|
|
526d79a975 | ||
|
|
b9ac9a0422 | ||
|
|
d171768cff | ||
|
|
cb2617dee0 | ||
|
|
b4635c41a8 | ||
|
|
217b20123c | ||
|
|
b2ca6bc5f3 | ||
|
|
a8d3e39779 | ||
|
|
3d3bd49d1c | ||
|
|
4a66b8b5df | ||
|
|
52f827c6ff | ||
|
|
aa434285b2 | ||
|
|
91e967128d | ||
|
|
abef1cb8c5 | ||
|
|
1d8e5d7eb1 | ||
|
|
75f506836c | ||
|
|
44780e74b5 | ||
|
|
8407439b8a | ||
|
|
ab58a9952e | ||
|
|
0ff654205f | ||
|
|
1d8789d800 | ||
|
|
a313c747f3 | ||
|
|
66b390c62a | ||
|
|
26abad0be0 | ||
|
|
68f293be45 | ||
|
|
41f4579c74 | ||
|
|
0787678c12 | ||
|
|
d0c79399b9 | ||
|
|
17aed02e56 | ||
|
|
88a9d44bb7 | ||
|
|
aa46039e6c | ||
|
|
3b078331a0 | ||
|
|
b8e5f22c01 | ||
|
|
c3bf7a78c0 | ||
|
|
94b3b5eaa2 | ||
|
|
27284a40ab | ||
|
|
76cc04c8f4 | ||
|
|
77580cd55f | ||
|
|
db095c87aa | ||
|
|
f3c6789dfb | ||
|
|
0772f72021 | ||
|
|
1623c188df | ||
|
|
dfed97edc8 | ||
|
|
f6632940fa | ||
|
|
ca2e8cf4de | ||
|
|
000e2d091b | ||
|
|
40f782e10c | ||
|
|
f306e65b25 | ||
|
|
d0f373df22 | ||
|
|
3fd76e4f93 | ||
|
|
d8c3440d37 | ||
|
|
a82ccea6f5 | ||
|
|
19f1d00fcb | ||
|
|
8c099aca90 | ||
|
|
6c6a07c0c0 | ||
|
|
8e323e30de | ||
|
|
91ccc0ca40 | ||
|
|
735cea35f9 | ||
|
|
41265cce32 | ||
|
|
176a5409b2 | ||
|
|
f23170bfaa | ||
|
|
bc7f7e70a1 | ||
|
|
a1b5ea811a | ||
|
+21 |
9d8d70dc61 | ||
|
|
06497622f6 | ||
|
|
c397d657d1 | ||
|
|
6f99c7b970 | ||
|
|
1cb38a1c33 | ||
|
|
b3e190c627 | ||
|
|
3bb734dde7 | ||
|
|
5f5a034112 | ||
|
|
eb5f57a48c | ||
|
|
903add50c9 | ||
|
|
c7e782c179 | ||
|
|
53724fd561 | ||
|
|
5f80403c5d | ||
|
|
d4ad76d576 | ||
|
|
46b5cce007 | ||
|
|
80541831bb | ||
|
|
ee6a9876ec | ||
|
|
df37d81dfb | ||
|
|
e99a50f612 | ||
|
|
50d0e8ae0a | ||
|
|
6552ffe315 | ||
|
|
381eb07ec9 | ||
|
|
3eafa16c64 | ||
|
|
672ff416c5 | ||
|
|
16376f509c | ||
|
|
56fde24532 | ||
|
|
518d2ab7df | ||
|
|
082bd66673 | ||
|
|
5880f4b0bd | ||
|
|
d156496cfa | ||
|
|
eb80ab410e | ||
|
|
5896d2c939 | ||
|
|
e3610cbe2c | ||
|
|
94cab02c1e | ||
|
|
f00cac265f | ||
|
|
aa6742bf3e | ||
|
|
4c37f4c764 | ||
|
|
430b13bb81 | ||
|
|
c15f7bee85 | ||
|
|
1a030420b1 | ||
|
|
a6f0cec083 | ||
|
|
6ae79d255d | ||
|
|
4b9974e20a | ||
|
|
509eee5dfb | ||
|
|
63a71fa447 | ||
|
|
d4327c8d48 | ||
|
|
5f1d76a73a | ||
|
|
35f6a607b8 | ||
|
|
378518c0fd | ||
|
|
69fb33887f | ||
|
|
f87c4d55e3 | ||
|
|
c612a6ca8d | ||
|
|
a1ad33baef | ||
|
|
8c27bc07fc | ||
|
|
95bc599ac0 | ||
|
|
cd27b10750 | ||
|
|
7f8851de6a | ||
|
|
a330258f6a | ||
|
|
d28c605a89 | ||
|
|
87c1675882 | ||
|
|
b446d2a6b1 | ||
|
|
748211c19b | ||
|
|
1fd02d0c82 | ||
|
|
4951f0bddd | ||
|
|
d391566832 | ||
|
|
0b564f1f20 | ||
|
|
b639e44a03 | ||
|
|
f68c1bcc9e | ||
|
|
4fabe9b405 | ||
|
|
ae3aa7926a | ||
|
|
82338d7d64 | ||
|
|
3b94df6108 | ||
|
|
5bb92a75c7 | ||
|
|
48179083f1 | ||
|
|
b4a3ec0b32 | ||
|
|
cb2a0ea570 | ||
|
|
9c3519046f | ||
|
|
da9b5d9b9d | ||
|
|
09d3e16183 | ||
|
|
cfefa51296 | ||
|
|
b65825b3ca | ||
|
|
981b16094a | ||
|
|
bb877e4b26 | ||
|
|
5b8adfbdb0 | ||
|
|
9678b8fd79 | ||
|
|
8e0c911f93 | ||
|
|
a2f3ec94aa | ||
|
|
38ccd7d5fc | ||
|
|
4faf89fc6f | ||
|
|
5b32a990a2 | ||
|
|
cb6daa5e4c | ||
|
|
d68347f5fb | ||
|
|
e676c8e6e1 | ||
|
|
34fa9138d9 | ||
|
|
7a917968e3 | ||
|
|
5f548243e1 | ||
|
|
34d22e9495 | ||
|
|
bc4fc765b2 | ||
|
|
253a560b20 | ||
|
|
eb3d2c6676 | ||
|
|
b1c6994977 | ||
|
|
2990762df9 | ||
|
|
8e6af6492b | ||
|
|
c4a606112a | ||
|
+6 |
230dc6fd05 | ||
|
|
e4b7002909 | ||
|
|
02c6427823 | ||
|
|
50b0ccb928 | ||
|
|
1712b9cdc5 | ||
|
|
b201ed163e | ||
|
|
3521ccfb4f | ||
|
|
b7cdfb1b30 | ||
|
|
4b1ae0526b | ||
|
|
378e734eed | ||
|
|
ed3b5305f1 | ||
|
|
cfc69d8d0b | ||
|
|
2a1859d7d7 | ||
|
|
8a2a2b1f36 | ||
|
|
af04276bed | ||
|
|
25d4bf5e2f | ||
|
|
d75db63258 | ||
|
|
52fc38e965 | ||
|
|
580863e891 | ||
|
|
85eb539c4d | ||
|
|
09ee38bb7a | ||
|
|
af4f17efc5 | ||
|
|
fa197a4ab2 | ||
|
|
c3243882b1 | ||
|
|
a938708e1f | ||
|
|
2e635ff2ce | ||
|
|
209805359e | ||
|
|
eb5b762515 | ||
|
|
7c5034275f | ||
|
|
d75a125b26 | ||
|
|
54d2c7b9e0 | ||
|
|
8b9d0a89c8 | ||
|
|
34afdabcfe | ||
|
|
bc32308327 | ||
|
|
c95269afd7 | ||
|
|
4af8f1eab0 | ||
|
+2 |
2366fbb4b3 | ||
|
|
e930f38608 |
@@ -30,7 +30,7 @@ body:
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.19.6](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.20.2](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
|
||||
@@ -52,7 +52,7 @@ body:
|
||||
label: Mihon version
|
||||
description: You can find your Mihon version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "0.19.6"
|
||||
Example: "0.20.2"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -95,7 +95,7 @@ body:
|
||||
required: true
|
||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.19.6](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.20.2](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
||||
required: true
|
||||
|
||||
+17
-13
@@ -4,6 +4,7 @@ on:
|
||||
paths:
|
||||
- '**'
|
||||
- '!**.md'
|
||||
- '!fastlane/**'
|
||||
- '!i18n/src/commonMain/moko-resources/**/strings.xml'
|
||||
- '!i18n/src/commonMain/moko-resources/**/plurals.xml'
|
||||
- 'i18n/src/commonMain/moko-resources/base/strings.xml'
|
||||
@@ -26,46 +27,49 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Dependency Review
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
|
||||
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
||||
with:
|
||||
java-version-file: .github/.java-version
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
|
||||
- name: Check code format
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
|
||||
|
||||
- name: Run unit tests
|
||||
id: unit_tests
|
||||
run: ./gradlew testReleaseUnitTest
|
||||
run: ./gradlew testDebugUnitTest
|
||||
|
||||
- name: Verify SQLDelight migrations
|
||||
run: ./gradlew verifySqlDelightMigration
|
||||
|
||||
- name: Upload test report
|
||||
if: steps.unit_tests.outcome == 'failure'
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: test-report-${{ github.sha }}
|
||||
path: app/build/reports/tests/testReleaseUnitTest
|
||||
path: app/build/reports/tests/testDebugUnitTest
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
|
||||
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
|
||||
|
||||
- name: Upload mapping
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/release
|
||||
|
||||
@@ -4,6 +4,9 @@ on:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
MIHON_GITHUB_RELEASE: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -29,43 +32,37 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
||||
with:
|
||||
java-version-file: .github/.java-version
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
|
||||
|
||||
- name: Sign APK
|
||||
uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: '35.0.1'
|
||||
storeFileBase64: ${{ secrets.SIGNING_KEY }}
|
||||
storePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyAlias: ${{ secrets.ALIAS }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Rename APK
|
||||
run: |
|
||||
set -e
|
||||
|
||||
mv app/build/outputs/apk/release/app-universal-release-unsigned-signed.apk mihon-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-arm64-v8a-release-unsigned-signed.apk mihon-arm64-v8a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned-signed.apk mihon-armeabi-v7a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-x86-release-unsigned-signed.apk mihon-x86-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-x86_64-release-unsigned-signed.apk mihon-x86_64-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-universal-release.apk mihon-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-arm64-v8a-release.apk mihon-arm64-v8a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-armeabi-v7a-release.apk mihon-armeabi-v7a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-x86-release.apk mihon-x86-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-x86_64-release.apk mihon-x86_64-${{ needs.get_tag.outputs.tag }}.apk
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: mihon
|
||||
path: |
|
||||
@@ -83,41 +80,35 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
||||
with:
|
||||
java-version-file: .github/.java-version
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-disabled: true
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew assembleFoss -Penable-updater
|
||||
|
||||
- name: Sign APK
|
||||
uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/foss
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: '35.0.1'
|
||||
storeFileBase64: ${{ secrets.SIGNING_KEY }}
|
||||
storePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyAlias: ${{ secrets.ALIAS }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Rename APK
|
||||
run: |
|
||||
set -e
|
||||
|
||||
mv app/build/outputs/apk/foss/app-universal-foss-unsigned-signed.apk mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
|
||||
mv app/build/outputs/apk/foss/app-universal-foss.apk mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: mihon-foss
|
||||
path: mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
|
||||
@@ -130,12 +121,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Delete all artifacts
|
||||
uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # v5.1.0
|
||||
uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6.0.0
|
||||
with:
|
||||
failOnError: false
|
||||
name: |
|
||||
@@ -143,12 +134,14 @@ jobs:
|
||||
mihon-foss
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
|
||||
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
|
||||
with:
|
||||
tag_name: ${{ needs.get_tag.outputs.tag }}
|
||||
name: Mihon ${{ needs.get_tag.outputs.tag }}
|
||||
body: |
|
||||
Check out the [past release notes](https://github.com/mihonapp/mihon/releases) if you're upgrading from an earlier version. Consider [becoming a patron](https://www.patreon.com/mihon/membership) to help keep Mihon improving!
|
||||
Consider supporting the development on [Patreon](https://www.patreon.com/mihon/membership) or [OpenCollective](https://opencollective.com/mihon/contribute) to keep Mihon improving!
|
||||
|
||||
Check out the [past release notes](https://github.com/mihonapp/mihon/releases) if you're upgrading from an earlier version.
|
||||
|
||||
<!-->
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ build
|
||||
/captures
|
||||
|
||||
# Configuration files
|
||||
keystore.properties
|
||||
local.properties
|
||||
|
||||
# macOS specific files
|
||||
|
||||
+106
-1
@@ -12,6 +12,105 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.20.2] - 2026-08-01
|
||||
### Added
|
||||
- Support resumable image downloads if supported by source ([@xMohnad](https://github.com/xMohnad)) ([#3167](https://github.com/mihonapp/mihon/pull/3167))
|
||||
- Fix HTTP Error 416 not being handled properly ([@AntsyLich](https://github.com/AntsyLich)) ([#3563](https://github.com/mihonapp/mihon/pull/3563))
|
||||
- Fix incorrect progress when resuming downloads ([@xMohnad](https://github.com/xMohnad)) ([#3616](https://github.com/mihonapp/mihon/pull/3616))
|
||||
- Add Tokyo Night Theme ([@xMohnad](https://github.com/xMohnad)) ([#3502](https://github.com/mihonapp/mihon/pull/3502))
|
||||
|
||||
### Changed
|
||||
- Detect Shizuku with permission check ([@Small-Ku](https://github.com/Small-Ku)) ([#3565](https://github.com/mihonapp/mihon/pull/3565))
|
||||
- Check for app and extension update on every cold start ([@AntsyLich](https://github.com/AntsyLich)) ([#3658](https://github.com/mihonapp/mihon/pull/3658))
|
||||
|
||||
### Improved
|
||||
- Library search ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#3554](https://github.com/mihonapp/mihon/pull/3554))
|
||||
- Support logical operators (`&&`, `||`, `-`), implicit `AND` separators, and nested expressions with `()`
|
||||
- Add comparison operators (`>`, `>=`, `<`, `<=`, `=`) for `id`, `added`, `fetchinterval` / `fi`, `nextupdate` / `nu`, `unread`, `read`, and `total` (Uses `yyyy-MM-dd` dates for `added` and `nextupdate`)
|
||||
- Support field-specific prefixes (`title`, `author`, `artist`, `description` / `desc`, `genre` / `tag`, `source` / `src`, `source_id` / `src_id`, `notes` / `note`, `language` / `lang`) and empty field matching (`""`, `''`)
|
||||
- Always use Private installer to update privately installed extensions ([@MajorTanya](https://github.com/MajorTanya)) ([#3614](https://github.com/mihonapp/mihon/pull/3614))
|
||||
- App update prompt flow experience ([@AntsyLich](https://github.com/AntsyLich)) ([#3669](https://github.com/mihonapp/mihon/pull/3669))
|
||||
|
||||
### Fixed
|
||||
- Fixed app bars remaining visible after changing pages by tapping in the paged reader after using the chapter navigator slider ([@AntsyLich](https://github.com/AntsyLich)) ([#3567](https://github.com/mihonapp/mihon/pull/3567))
|
||||
- Fixed MangaBaka User Agent string ([@MajorTanya](https://github.com/MajorTanya)) ([#3578](https://github.com/mihonapp/mihon/pull/3578))
|
||||
- Fixed extension installation with shizuku installer ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#3630](https://github.com/mihonapp/mihon/pull/3630))
|
||||
- Fixed backup restore dropping library entries when the backup contains duplicate chapters ([@na-ji](https://github.com/na-ji)) ([#3667](https://github.com/mihonapp/mihon/pull/3667))
|
||||
|
||||
## [v0.20.1] - 2026-07-09
|
||||
### Added
|
||||
- [Hikka](https://hikka.io/) tracker support ([@Lorg0n](https://github.com/Lorg0n)) ([#1386](https://github.com/mihonapp/mihon/pull/1386))
|
||||
- Fix Hikka not defaulting to "Plan to Read" for unread titles ([@MajorTanya](https://github.com/MajorTanya)) ([#3534](https://github.com/mihonapp/mihon/pull/3534))
|
||||
- Fix Hikka search throwing error due to unclosed response ([@MajorTanya](https://github.com/MajorTanya)) ([#3548](https://github.com/mihonapp/mihon/pull/3548))
|
||||
- Add support for [MangaBaka](https://mangabaka.org) tracker ([@MajorTanya](https://github.com/MajorTanya)) ([#3047](https://github.com/mihonapp/mihon/pull/3047))
|
||||
- Display authors and description in Shikimori search results ([@MajorTanya](https://github.com/MajorTanya)) ([#3499](https://github.com/mihonapp/mihon/pull/3499))
|
||||
- Invalidate download cache after backup restore ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3096](https://github.com/mihonapp/mihon/pull/3096))
|
||||
- Add setting to control vertical chapter navigator height ([@AntsyLich](https://github.com/AntsyLich)) ([#3528](https://github.com/mihonapp/mihon/pull/3528))
|
||||
- Add option to set which readers use vertical chapter navigator ([@AntsyLich](https://github.com/AntsyLich)) ([#3528](https://github.com/mihonapp/mihon/pull/3528))
|
||||
|
||||
### Changed
|
||||
- Don't use GMS to detect if GMS is available ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3525](https://github.com/mihonapp/mihon/pull/3525))
|
||||
|
||||
### Improved
|
||||
- Show usernames in Tracking settings ([@MajorTanya](https://github.com/MajorTanya)) ([#3533](https://github.com/mihonapp/mihon/pull/3533))
|
||||
|
||||
### Fixed
|
||||
- Fix Shikimori tracking not working ([@MajorTanya](https://github.com/MajorTanya)) ([#3497](https://github.com/mihonapp/mihon/pull/3497))
|
||||
- Fix crash trying to select text in notes screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3516](https://github.com/mihonapp/mihon/pull/3516))
|
||||
- Fix crash when putting app in background ([@AntsyLich](https://github.com/AntsyLich)) ([#3523](https://github.com/mihonapp/mihon/pull/3523))
|
||||
- Fix support for non-system SAF providers ([@AntsyLich](https://github.com/AntsyLich)) ([#3530](https://github.com/mihonapp/mihon/pull/3530))
|
||||
- Fix chapter's `memo` field not being updated for existing chapters ([@cuong-tran](https://github.com/cuong-tran)) ([#3538](https://github.com/mihonapp/mihon/pull/3538))
|
||||
- Fix app not reading `tachiyomix.extensionLib` extension metadata ([@AntsyLich](https://github.com/AntsyLich)) ([#3545](https://github.com/mihonapp/mihon/pull/3545), [#3559](https://github.com/mihonapp/mihon/pull/3559))
|
||||
- Fix reader navigator slider steps not updating after changing chapter ([@AntsyLich](https://github.com/AntsyLich)) ([#3549](https://github.com/mihonapp/mihon/pull/3549))
|
||||
|
||||
## [v0.20.0] - 2026-06-27
|
||||
### Added
|
||||
- Add support for `tachiyomix` 1.6 extensions ([@AntsyLich](https://github.com/AntsyLich))
|
||||
- Add support for `tachiyomix` extension index format ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
|
||||
- Add vertical chapter navigator for long strip mode ([@AntsyLich](https://github.com/AntsyLich)) ([#3377](https://github.com/mihonapp/mihon/pull/3377))
|
||||
- Add option to open app settings from system settings ([@JFronny](https://github.com/JFronny)) ([#3180](https://github.com/mihonapp/mihon/pull/3180))
|
||||
|
||||
### Changed
|
||||
- Change the term "extension repo" to "extension store" ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
|
||||
- Change the term "Obsolete" to "Orphaned" for extensions ([@AntsyLich](https://github.com/AntsyLich)) ([#3383](https://github.com/mihonapp/mihon/pull/3383))
|
||||
- Remove text limit of manga notes ([@AntsyLich](https://github.com/AntsyLich)) ([#3410](https://github.com/mihonapp/mihon/pull/3410))
|
||||
- Tweak Catppuccin color scheme for clarity ([@xenitane](https://github.com/xenitane)) ([#3436](https://github.com/mihonapp/mihon/pull/3436))
|
||||
- Update app shortcuts to use app icon color scheme ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2835](https://github.com/mihonapp/mihon/pull/2835))
|
||||
|
||||
### Improved
|
||||
- Batch database operations during backup restore for improved performance ([@Lolle2000la](https://github.com/Lolle2000la)) ([#3267](https://github.com/mihonapp/mihon/pull/3267))
|
||||
|
||||
### Fixed
|
||||
- Add missing `outlineVariant` color to Nord theme ([@CompileConnected](https://github.com/CompileConnected)) ([#3184](https://github.com/mihonapp/mihon/pull/3184))
|
||||
- Continue reading button missing when unread filter is off ([@AntsyLich](https://github.com/AntsyLich)) ([#3382](https://github.com/mihonapp/mihon/pull/3382))
|
||||
- Fix random `FileNotFoundException` when reading chapters ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3154](https://github.com/mihonapp/mihon/pull/3154))
|
||||
|
||||
## [v0.19.9] - 2026-04-11
|
||||
### Fixed
|
||||
- Regression with installing/updating extension ([@AntsyLich](https://github.com/AntsyLich))
|
||||
|
||||
## [v0.19.8] - 2026-04-11
|
||||
### Added
|
||||
- Add dedicated "Support Us" screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3200](https://github.com/mihonapp/mihon/pull/3200))
|
||||
- Add a one time popup asking for donation from long time users ([@AntsyLich](https://github.com/AntsyLich)) ([#3203](https://github.com/mihonapp/mihon/pull/3203))
|
||||
|
||||
### Changed
|
||||
- Don't prompt users to enable Google Play services if disabled or unavailable ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3152](https://github.com/mihonapp/mihon/pull/3152))
|
||||
- Make "Support Us" more prominent in More screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3196](https://github.com/mihonapp/mihon/pull/3196))
|
||||
|
||||
### Improved
|
||||
- Show informative error when trying to add unapproved titles to list on MAL ([@MajorTanya](https://github.com/MajorTanya)) ([#3155](https://github.com/mihonapp/mihon/pull/3155))
|
||||
- Show AniList publishing type based on country of origin ([@zweimach](https://github.com/zweimach)) ([#3100](https://github.com/mihonapp/mihon/pull/3100))
|
||||
|
||||
### Fixed
|
||||
- Fix app trying to split long strip when not needed ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3121](https://github.com/mihonapp/mihon/pull/3121))
|
||||
|
||||
### Removed
|
||||
- Remove the empty "Updated to X.Y.Z" popup after app update ([@AntsyLich](https://github.com/AntsyLich)) ([#3203](https://github.com/mihonapp/mihon/pull/3203))
|
||||
|
||||
## [v0.19.7] - 2026-03-23
|
||||
Same as v0.19.6
|
||||
|
||||
## [v0.19.6] - 2026-03-23
|
||||
### Fixed
|
||||
- Fix app crashing when trying to add extension repo with existing signature ([@AntsyLich](https://github.com/AntsyLich)) ([`cb0e329`](https://github.com/mihonapp/mihon/commit/cb0e329))
|
||||
@@ -502,7 +601,13 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
||||
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
|
||||
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
|
||||
|
||||
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.19.6...main
|
||||
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.20.2...main
|
||||
[v0.20.2]: https://github.com/mihonapp/mihon/compare/v0.20.1...v0.20.2
|
||||
[v0.20.1]: https://github.com/mihonapp/mihon/compare/v0.20.0...v0.20.1
|
||||
[v0.20.0]: https://github.com/mihonapp/mihon/compare/v0.19.9...v0.20.0
|
||||
[v0.19.9]: https://github.com/mihonapp/mihon/compare/v0.19.8...v0.19.9
|
||||
[v0.19.8]: https://github.com/mihonapp/mihon/compare/v0.19.7...v0.19.8
|
||||
[v0.19.7]: https://github.com/mihonapp/mihon/compare/v0.19.6...v0.19.7
|
||||
[v0.19.6]: https://github.com/mihonapp/mihon/compare/v0.19.5...v0.19.6
|
||||
[v0.19.5]: https://github.com/mihonapp/mihon/compare/v0.19.4...v0.19.5
|
||||
[v0.19.4]: https://github.com/mihonapp/mihon/compare/v0.19.3...v0.19.4
|
||||
|
||||
@@ -29,7 +29,7 @@ Discover and read manga, webtoons, comics, and more – easier than ever on your
|
||||
|
||||
* Local reading of content.
|
||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support.
|
||||
* Tracker support: [MangaBaka](https://mangabaka.org), [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), [Bangumi](https://bgm.tv/), and [Hikka](https://hikka.io/) support.
|
||||
* Categories to organize your library.
|
||||
* Light and dark themes.
|
||||
* Schedule updating your library for new chapters.
|
||||
|
||||
+125
-84
@@ -1,14 +1,20 @@
|
||||
import mihon.buildlogic.Config
|
||||
import mihon.buildlogic.getBuildTime
|
||||
import mihon.buildlogic.getCommitCount
|
||||
import mihon.buildlogic.getGitSha
|
||||
import mihon.gradle.Config
|
||||
import mihon.gradle.getBuildTime
|
||||
import mihon.gradle.getLatestCommitCount
|
||||
import mihon.gradle.getLatestCommitSha
|
||||
import mihon.gradle.tasks.ReplaceShortcutsPlaceholderTask
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
import kotlin.io.encoding.Base64
|
||||
|
||||
plugins {
|
||||
id("mihon.android.application")
|
||||
id("mihon.android.application.compose")
|
||||
id("com.github.zellius.shortcut-helper")
|
||||
kotlin("plugin.serialization")
|
||||
alias(mihonx.plugins.android.application)
|
||||
alias(mihonx.plugins.compose)
|
||||
alias(mihonx.plugins.spotless)
|
||||
|
||||
alias(libs.plugins.aboutLibraries)
|
||||
alias(libs.plugins.androidx.baselineProfile)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
if (Config.includeTelemetry) {
|
||||
@@ -18,7 +24,7 @@ if (Config.includeTelemetry) {
|
||||
}
|
||||
}
|
||||
|
||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||
val keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
|
||||
android {
|
||||
namespace = "eu.kanade.tachiyomi"
|
||||
@@ -26,31 +32,62 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "app.mihon"
|
||||
|
||||
versionCode = 19
|
||||
versionName = "0.19.5"
|
||||
versionCode = 27
|
||||
versionName = "0.20.2"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getLatestCommitSha()}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
|
||||
buildConfigField("boolean", "TELEMETRY_INCLUDED", "${Config.includeTelemetry}")
|
||||
buildConfigField("boolean", "UPDATER_ENABLED", "${Config.enableUpdater}")
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
if (System.getenv("MIHON_GITHUB_RELEASE").toBoolean()) {
|
||||
val tempStoreFile = file(System.getenv("RUNNER_TEMP")).resolve("antsy.keystore")
|
||||
|
||||
val storeFileBytes = System.getenv("storeFileBase64").let(Base64::decode)
|
||||
tempStoreFile.outputStream().use { it.write(storeFileBytes) }
|
||||
|
||||
signingConfigs {
|
||||
named("debug") {
|
||||
storeFile = tempStoreFile
|
||||
storePassword = System.getenv("storePassword")
|
||||
keyAlias = System.getenv("keyAlias")
|
||||
keyPassword = System.getenv("keyPassword")
|
||||
}
|
||||
}
|
||||
} else if (keystorePropertiesFile.exists()) {
|
||||
val keystoreProperties = FileInputStream(keystorePropertiesFile).use { Properties().apply { load(it) } }
|
||||
|
||||
signingConfigs {
|
||||
named("debug") {
|
||||
storeFile = file(keystoreProperties.getProperty("storeFile"))
|
||||
storePassword = keystoreProperties.getProperty("storePassword")
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
val debug by getting {
|
||||
val debug = getByName("debug") {
|
||||
applicationIdSuffix = ".dev"
|
||||
versionNameSuffix = "-${getCommitCount()}"
|
||||
versionNameSuffix = "-${getLatestCommitCount()}"
|
||||
isPseudoLocalesEnabled = true
|
||||
}
|
||||
val release by getting {
|
||||
isMinifyEnabled = Config.enableCodeShrink
|
||||
isShrinkResources = Config.enableCodeShrink
|
||||
val release = getByName("release") {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
|
||||
signingConfig = debug.signingConfig
|
||||
|
||||
isProfileable = true
|
||||
|
||||
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
|
||||
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = true)}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = true)}\"")
|
||||
}
|
||||
|
||||
val commonMatchingFallbacks = listOf(release.name)
|
||||
@@ -68,29 +105,24 @@ android {
|
||||
applicationIdSuffix = ".debug"
|
||||
|
||||
versionNameSuffix = debug.versionNameSuffix
|
||||
signingConfig = debug.signingConfig
|
||||
|
||||
matchingFallbacks.addAll(commonMatchingFallbacks)
|
||||
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
|
||||
}
|
||||
create("benchmark") {
|
||||
initWith(release)
|
||||
|
||||
isDebuggable = false
|
||||
isProfileable = true
|
||||
versionNameSuffix = "-benchmark"
|
||||
applicationIdSuffix = ".benchmark"
|
||||
|
||||
signingConfig = debug.signingConfig
|
||||
|
||||
matchingFallbacks.addAll(commonMatchingFallbacks)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("preview").res.srcDirs("src/debug/res")
|
||||
getByName("benchmark").res.srcDirs("src/debug/res")
|
||||
getByName("preview").res.directories.add("src/debug/res")
|
||||
getByName("benchmark").res.directories.add("src/debug/res")
|
||||
}
|
||||
|
||||
splits {
|
||||
@@ -139,10 +171,6 @@ android {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
aidl = true
|
||||
|
||||
// Disable some unused things
|
||||
renderScript = false
|
||||
shaders = false
|
||||
}
|
||||
|
||||
lint {
|
||||
@@ -166,15 +194,22 @@ kotlin {
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||
"-Xannotation-default-target=param-property",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
baselineProfile {
|
||||
baselineProfileOutputDir = "baselineProfiles"
|
||||
mergeIntoMain = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
baselineProfile(projects.baselineProfile)
|
||||
|
||||
implementation(projects.i18n)
|
||||
implementation(projects.core.archive)
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.viewmodel)
|
||||
implementation(projects.coreMetadata)
|
||||
implementation(projects.sourceApi)
|
||||
implementation(projects.sourceLocal)
|
||||
@@ -185,91 +220,90 @@ dependencies {
|
||||
implementation(projects.telemetry)
|
||||
|
||||
// Compose
|
||||
implementation(compose.activity)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3.core)
|
||||
implementation(compose.material.icons)
|
||||
implementation(compose.animation)
|
||||
implementation(compose.animation.graphics)
|
||||
debugImplementation(compose.ui.tooling)
|
||||
implementation(compose.ui.tooling.preview)
|
||||
implementation(compose.ui.util)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.compose.foundation)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.materialIcons)
|
||||
implementation(libs.androidx.compose.animation)
|
||||
implementation(libs.androidx.compose.animationGraphics)
|
||||
debugImplementation(libs.androidx.compose.uiTooling)
|
||||
implementation(libs.androidx.compose.uiToolingPreview)
|
||||
implementation(libs.androidx.compose.uiUtil)
|
||||
|
||||
implementation(androidx.interpolator)
|
||||
implementation(libs.androidx.interpolator)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
implementation(libs.androidx.paging.runtime)
|
||||
implementation(libs.androidx.paging.compose)
|
||||
|
||||
implementation(androidx.sqlite.bundled)
|
||||
implementation(libs.androidx.sqlite.bundled)
|
||||
|
||||
implementation(kotlinx.reflect)
|
||||
implementation(kotlinx.immutables)
|
||||
implementation(libs.kotlin.reflect)
|
||||
|
||||
implementation(platform(kotlinx.coroutines.bom))
|
||||
implementation(kotlinx.bundles.coroutines)
|
||||
implementation(libs.bundles.kotlinx.coroutines)
|
||||
|
||||
implementation(libs.sqldelight.async)
|
||||
|
||||
// AndroidX libraries
|
||||
implementation(androidx.annotation)
|
||||
implementation(androidx.appcompat)
|
||||
implementation(androidx.biometricktx)
|
||||
implementation(androidx.constraintlayout)
|
||||
implementation(androidx.corektx)
|
||||
implementation(androidx.splashscreen)
|
||||
implementation(androidx.recyclerview)
|
||||
implementation(androidx.viewpager)
|
||||
implementation(androidx.profileinstaller)
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.appCompat)
|
||||
implementation(libs.androidx.biometric)
|
||||
implementation(libs.androidx.constraintLayout)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.coreSplashScreen)
|
||||
implementation(libs.androidx.recyclerView)
|
||||
implementation(libs.androidx.viewPager)
|
||||
implementation(libs.androidx.profileInstaller)
|
||||
|
||||
implementation(androidx.bundles.lifecycle)
|
||||
implementation(libs.bundles.androidx.lifecycle)
|
||||
|
||||
// Job scheduling
|
||||
implementation(androidx.workmanager)
|
||||
implementation(libs.androidx.work)
|
||||
|
||||
// RxJava
|
||||
implementation(libs.rxjava)
|
||||
implementation(libs.rxJava)
|
||||
|
||||
// Networking
|
||||
implementation(libs.bundles.okhttp)
|
||||
implementation(libs.okio)
|
||||
implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10
|
||||
implementation(libs.conscrypt) // TLS 1.3 support for Android < 10
|
||||
|
||||
// Data serialization (JSON, protobuf, xml)
|
||||
implementation(kotlinx.bundles.serialization)
|
||||
implementation(libs.bundles.serialization)
|
||||
|
||||
// HTML parser
|
||||
implementation(libs.jsoup)
|
||||
|
||||
// Disk
|
||||
implementation(libs.disklrucache)
|
||||
implementation(libs.diskLruCache)
|
||||
implementation(libs.unifile)
|
||||
|
||||
// Preferences
|
||||
implementation(libs.preferencektx)
|
||||
implementation(libs.androidx.preference)
|
||||
|
||||
// Dependency injection
|
||||
implementation(libs.injekt)
|
||||
|
||||
// Image loading
|
||||
implementation(platform(libs.coil.bom))
|
||||
implementation(libs.bundles.coil)
|
||||
implementation(libs.subsamplingscaleimageview) {
|
||||
implementation(libs.subsamplingScaleImageView) {
|
||||
exclude(module = "image-decoder")
|
||||
}
|
||||
implementation(libs.image.decoder)
|
||||
|
||||
// UI libraries
|
||||
implementation(libs.material)
|
||||
implementation(libs.flexible.adapter.core)
|
||||
implementation(libs.photoview)
|
||||
implementation(libs.directionalviewpager) {
|
||||
implementation(libs.flexibleAdapter)
|
||||
implementation(libs.photoView)
|
||||
implementation(libs.directionalViewPager) {
|
||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||
}
|
||||
implementation(libs.richeditor.compose)
|
||||
implementation(libs.composeRichEditor)
|
||||
implementation(libs.aboutLibraries.compose)
|
||||
implementation(libs.bundles.voyager)
|
||||
implementation(libs.compose.materialmotion)
|
||||
implementation(libs.composeMaterialMotion)
|
||||
implementation(libs.swipe)
|
||||
implementation(libs.compose.webview)
|
||||
implementation(libs.compose.grid)
|
||||
implementation(libs.composeWebview)
|
||||
implementation(libs.composeGrid)
|
||||
implementation(libs.reorderable)
|
||||
implementation(libs.bundles.markdown)
|
||||
implementation(libs.materialKolor)
|
||||
@@ -288,22 +322,29 @@ dependencies {
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// debugImplementation(libs.leakcanary.android)
|
||||
implementation(libs.leakcanary.plumber)
|
||||
// debugImplementation(libs.leakCanary.android)
|
||||
implementation(libs.leakCanary.plumber)
|
||||
|
||||
testImplementation(kotlinx.coroutines.test)
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
onVariants { variant ->
|
||||
val resSource = variant.sources.res ?: return@onVariants
|
||||
|
||||
val variantName = variant.name.replaceFirstChar { it.uppercase() }
|
||||
val replaceShortcutsPlaceholderTask = tasks.register<ReplaceShortcutsPlaceholderTask>(
|
||||
"replace${variantName}ShortcutPlaceholder",
|
||||
) {
|
||||
applicationId.set(variant.applicationId)
|
||||
shortcutsFile.set(projectDir.resolve("src/main/shortcuts.xml"))
|
||||
}
|
||||
resSource.addGeneratedSourceDirectory(replaceShortcutsPlaceholderTask) { it.outputDir }
|
||||
}
|
||||
|
||||
onVariants(selector().withFlavor("default" to "standard")) {
|
||||
// Only excluding in standard flavor because this breaks
|
||||
// Layout Inspector's Compose tree
|
||||
it.packaging.resources.excludes.add("META-INF/*.version")
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath(kotlinx.gradle)
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -16,6 +16,7 @@
|
||||
-keep,allowoptimization class rx.** { public protected *; }
|
||||
-keep,allowoptimization class app.cash.quickjs.** { public protected *; }
|
||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
||||
-keep,allowoptimization class com.squareup.zstd.** { public protected *; }
|
||||
|
||||
# From extensions-lib
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptorKt { public protected *; }
|
||||
@@ -25,6 +26,11 @@
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.RequestsKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.AppInfo { public protected *; }
|
||||
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
||||
|
||||
##---------------Begin: proguard configuration for RxJava 1.x ----------
|
||||
-dontwarn sun.misc.**
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="accent_blue">#7EBBED</color>
|
||||
<color name="icon_background">#2E3943</color>
|
||||
</resources>
|
||||
@@ -60,8 +60,8 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Deep link to add repos -->
|
||||
<intent-filter android:label="@string/action_add_repo">
|
||||
<!-- Legacy deeplink to add extension store -->
|
||||
<intent-filter android:label="@string/action_addExtensionStore">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -71,6 +71,17 @@
|
||||
<data android:host="add-repo" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Deeplink to add extension store -->
|
||||
<intent-filter android:label="@string/action_addExtensionStore">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="mihon" />
|
||||
<data android:host="extension-store" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Open backup files -->
|
||||
<intent-filter android:label="@string/pref_restore_backup">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
@@ -130,6 +141,10 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/app_settings">
|
||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
@@ -178,8 +193,10 @@
|
||||
|
||||
<data android:host="anilist-auth" />
|
||||
<data android:host="bangumi-auth" />
|
||||
<data android:host="mangabaka-auth" />
|
||||
<data android:host="myanimelist-auth" />
|
||||
<data android:host="shikimori-auth" />
|
||||
<data android:host="hikka-auth" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package mihon.app.shizuku;
|
||||
|
||||
interface IShellInterface {
|
||||
void install(in AssetFileDescriptor apk) = 1;
|
||||
void install(in AssetFileDescriptor apk, in IntentSender intentSender) = 1;
|
||||
|
||||
void destroy() = 16777114;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -25,17 +25,17 @@ import eu.kanade.domain.track.interactor.AddTracks
|
||||
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||
import eu.kanade.domain.track.interactor.TrackChapter
|
||||
import mihon.data.repository.ExtensionRepoRepositoryImpl
|
||||
import mihon.data.extension.repository.ExtensionStoreRepositoryImpl
|
||||
import mihon.data.extension.service.ExtensionStoreService
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
|
||||
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
||||
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||
import mihon.domain.extensionrepo.service.ExtensionRepoService
|
||||
import mihon.domain.extension.interactor.AddExtensionStore
|
||||
import mihon.domain.extension.interactor.GetExtensionStoreCountAsFlow
|
||||
import mihon.domain.extension.interactor.GetExtensionStores
|
||||
import mihon.domain.extension.interactor.RemoveExtensionStore
|
||||
import mihon.domain.extension.interactor.UpdateExtensionStores
|
||||
import mihon.domain.extension.repository.ExtensionStoreRepository
|
||||
import mihon.domain.migration.usecases.MigrateMangaUseCase
|
||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||
@@ -143,7 +143,7 @@ class DomainModule : InjektModule {
|
||||
}
|
||||
|
||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||
addFactory { GetApplicationRelease(get(), get()) }
|
||||
addFactory { GetApplicationRelease(get()) }
|
||||
|
||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||
addFactory { TrackChapter(get(), get(), get(), get()) }
|
||||
@@ -195,15 +195,17 @@ class DomainModule : InjektModule {
|
||||
addFactory { ToggleSourcePin(get()) }
|
||||
addFactory { TrustExtension(get(), get()) }
|
||||
|
||||
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
|
||||
addFactory { ExtensionRepoService(get(), get()) }
|
||||
addFactory { GetExtensionRepo(get()) }
|
||||
addFactory { GetExtensionRepoCount(get()) }
|
||||
addFactory { CreateExtensionRepo(get(), get()) }
|
||||
addFactory { DeleteExtensionRepo(get()) }
|
||||
addFactory { ReplaceExtensionRepo(get()) }
|
||||
addFactory { UpdateExtensionRepo(get(), get()) }
|
||||
addSingletonFactory { ExtensionStoreService(get(), get(), get()) }
|
||||
addSingletonFactory<ExtensionStoreRepository> { ExtensionStoreRepositoryImpl(get(), get()) }
|
||||
addFactory { AddExtensionStore(get()) }
|
||||
addFactory { GetExtensionStoreCountAsFlow(get()) }
|
||||
addFactory { GetExtensionStores(get()) }
|
||||
addFactory { RemoveExtensionStore(get()) }
|
||||
addFactory { UpdateExtensionStores(get()) }
|
||||
|
||||
addFactory { ToggleIncognito(get()) }
|
||||
addFactory { GetIncognitoState(get(), get(), get()) }
|
||||
|
||||
addFactory { UpdateMangaFromRemote(get(), get(), get(), get(), get(), get(), get()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,9 @@ class BasePreferences(
|
||||
)
|
||||
|
||||
val installationId: Preference<String> = preferenceStore.getString(Preference.appStateKey("installation_id"), "")
|
||||
|
||||
val donationCampaignShown: Preference<Boolean> = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("donation_campaign_shown"),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ class SyncChaptersWithSource(
|
||||
// Update metadata from source if necessary.
|
||||
if (source is HttpSource) {
|
||||
val sChapter = chapter.toSChapter()
|
||||
@Suppress("DEPRECATION")
|
||||
source.prepareNewChapter(sChapter, manga.toSManga())
|
||||
chapter = chapter.copyFromSChapter(sChapter)
|
||||
}
|
||||
@@ -128,6 +129,7 @@ class SyncChaptersWithSource(
|
||||
chapterNumber = chapter.chapterNumber,
|
||||
scanlator = chapter.scanlator,
|
||||
sourceOrder = chapter.sourceOrder,
|
||||
memo = chapter.memo,
|
||||
)
|
||||
|
||||
if (chapter.dateUpload != 0L) {
|
||||
|
||||
@@ -13,6 +13,7 @@ fun Chapter.toSChapter(): SChapter {
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber.toFloat()
|
||||
it.scanlator = scanlator
|
||||
it.memo = memo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +24,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
||||
dateUpload = sChapter.date_upload,
|
||||
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||
memo = sChapter.memo,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,4 +41,5 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber.toFloat()
|
||||
it.source_order = sourceOrder.toInt()
|
||||
it.memo = memo
|
||||
}
|
||||
|
||||
@@ -17,11 +17,7 @@ class GetExtensionLanguages(
|
||||
) { enabledLanguage, availableExtensions ->
|
||||
availableExtensions
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
listOf(ext.lang)
|
||||
} else {
|
||||
ext.sources.map { it.lang }
|
||||
}
|
||||
ext.sources.map { it.lang }
|
||||
}
|
||||
.distinct()
|
||||
.sortedWith(
|
||||
|
||||
@@ -39,9 +39,6 @@ class GetExtensionsByType(
|
||||
(showNsfwSources || !extension.isNsfw)
|
||||
}
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
|
||||
}
|
||||
ext.sources.filter { it.lang in enabledLanguages }
|
||||
.map {
|
||||
ext.copy(
|
||||
|
||||
@@ -3,16 +3,16 @@ package eu.kanade.domain.extension.interactor
|
||||
import android.content.pm.PackageInfo
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||
import mihon.domain.extension.repository.ExtensionStoreRepository
|
||||
import tachiyomi.core.common.preference.getAndSet
|
||||
|
||||
class TrustExtension(
|
||||
private val extensionRepoRepository: ExtensionRepoRepository,
|
||||
private val repository: ExtensionStoreRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
|
||||
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
|
||||
val trustedFingerprints = repository.getAll().map { it.signingKey }.toHashSet()
|
||||
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
|
||||
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions.get()
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import app.cash.sqldelight.async.coroutines.awaitAsList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
import tachiyomi.data.Database
|
||||
import tachiyomi.data.subscribeToList
|
||||
|
||||
class GetExcludedScanlators(
|
||||
private val handler: DatabaseHandler,
|
||||
private val database: Database,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long): Set<String> {
|
||||
return handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}
|
||||
return database.excluded_scanlatorsQueries
|
||||
.getExcludedScanlatorsByMangaId(mangaId)
|
||||
.awaitAsList()
|
||||
.toSet()
|
||||
}
|
||||
|
||||
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||
return handler.subscribeToList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}
|
||||
return database.excluded_scanlatorsQueries
|
||||
.getExcludedScanlatorsByMangaId(mangaId)
|
||||
.subscribeToList()
|
||||
.map { it.toSet() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
import app.cash.sqldelight.async.coroutines.awaitAsList
|
||||
import tachiyomi.data.Database
|
||||
|
||||
class SetExcludedScanlators(
|
||||
private val handler: DatabaseHandler,
|
||||
private val database: Database,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
|
||||
handler.await(inTransaction = true) {
|
||||
val currentExcluded = handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}.toSet()
|
||||
database.transaction {
|
||||
val currentExcluded = database.excluded_scanlatorsQueries
|
||||
.getExcludedScanlatorsByMangaId(mangaId)
|
||||
.awaitAsList()
|
||||
.toSet()
|
||||
val toAdd = excludedScanlators.minus(currentExcluded)
|
||||
for (scanlator in toAdd) {
|
||||
excluded_scanlatorsQueries.insert(mangaId, scanlator)
|
||||
database.excluded_scanlatorsQueries.insert(mangaId, scanlator)
|
||||
}
|
||||
val toRemove = currentExcluded.minus(excludedScanlators)
|
||||
excluded_scanlatorsQueries.remove(mangaId, toRemove)
|
||||
database.excluded_scanlatorsQueries.remove(mangaId, toRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.manga.model.hasCustomCover
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
@@ -28,67 +20,6 @@ class UpdateManga(
|
||||
return mangaRepository.updateAll(mangaUpdates)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFromSource(
|
||||
localManga: Manga,
|
||||
remoteManga: SManga,
|
||||
manualFetch: Boolean,
|
||||
coverCache: CoverCache = Injekt.get(),
|
||||
libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
downloadManager: DownloadManager = Injekt.get(),
|
||||
): Boolean {
|
||||
val remoteTitle = try {
|
||||
remoteManga.title
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
""
|
||||
}
|
||||
|
||||
// if the manga isn't a favorite (or 'update titles' preference is enabled), set its title from source and update in db
|
||||
val title =
|
||||
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles.get())) {
|
||||
remoteTitle
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val coverLastModified =
|
||||
when {
|
||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||
localManga.isLocal() -> Instant.now().toEpochMilli()
|
||||
localManga.hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
Instant.now().toEpochMilli()
|
||||
}
|
||||
}
|
||||
|
||||
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
||||
|
||||
val success = mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = localManga.id,
|
||||
title = title,
|
||||
coverLastModified = coverLastModified,
|
||||
author = remoteManga.author,
|
||||
artist = remoteManga.artist,
|
||||
description = remoteManga.description,
|
||||
genre = remoteManga.getGenres(),
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
status = remoteManga.status.toLong(),
|
||||
updateStrategy = remoteManga.update_strategy,
|
||||
initialized = true,
|
||||
),
|
||||
)
|
||||
if (success && title != null) {
|
||||
downloadManager.renameManga(localManga, title)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFetchInterval(
|
||||
manga: Manga,
|
||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
|
||||
@@ -45,6 +45,7 @@ fun Manga.toSManga(): SManga = SManga.create().also {
|
||||
it.status = status.toInt()
|
||||
it.thumbnail_url = thumbnailUrl
|
||||
it.initialized = initialized
|
||||
it.memo = memo
|
||||
}
|
||||
|
||||
fun Manga.copyFrom(other: SManga): Manga {
|
||||
@@ -66,6 +67,7 @@ fun Manga.copyFrom(other: SManga): Manga {
|
||||
status = other.status.toLong(),
|
||||
updateStrategy = other.update_strategy,
|
||||
initialized = other.initialized && initialized,
|
||||
memo = other.memo,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package eu.kanade.domain.track.service
|
||||
import eu.kanade.domain.track.model.AutoTrackState
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.data.track.mangabaka.MangaBaka
|
||||
import tachiyomi.core.common.preference.Preference
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.core.common.preference.getEnum
|
||||
@@ -16,6 +17,11 @@ class TrackPreferences(
|
||||
"",
|
||||
)
|
||||
|
||||
fun trackDisplayUsername(tracker: Tracker) = preferenceStore.getString(
|
||||
Preference.privateKey("pref_mangasync_displayname_${tracker.id}"),
|
||||
"",
|
||||
)
|
||||
|
||||
fun trackPassword(tracker: Tracker) = preferenceStore.getString(
|
||||
Preference.privateKey("pref_mangasync_password_${tracker.id}"),
|
||||
"",
|
||||
@@ -36,6 +42,8 @@ class TrackPreferences(
|
||||
|
||||
val anilistScoreType: Preference<String> = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||
|
||||
val mangabakaScoreType: Preference<String> = preferenceStore.getString("mangabaka_score_type", MangaBaka.STEP_1)
|
||||
|
||||
val autoUpdateTrack: Preference<Boolean> = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||
|
||||
val autoUpdateTrackOnMarkRead: Preference<AutoTrackState> = preferenceStore.getEnum(
|
||||
|
||||
@@ -17,6 +17,7 @@ enum class AppTheme(val titleRes: StringResource?) {
|
||||
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
||||
YINYANG(MR.strings.theme_yinyang),
|
||||
YOTSUBA(MR.strings.theme_yotsuba),
|
||||
TOKYONIGHT(MR.strings.theme_tokyonight),
|
||||
MONOCHROME(MR.strings.theme_monochrome),
|
||||
|
||||
// Deprecated
|
||||
|
||||
@@ -22,7 +22,6 @@ import eu.kanade.presentation.browse.components.BrowseSourceList
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.util.formattedMessage
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
@@ -86,7 +85,7 @@ fun BrowseSourceContent(
|
||||
else -> stringResource(MR.strings.no_results_found)
|
||||
},
|
||||
actions = if (source is LocalSource) {
|
||||
persistentListOf(
|
||||
listOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.local_source_help_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
@@ -94,7 +93,7 @@ fun BrowseSourceContent(
|
||||
),
|
||||
)
|
||||
} else {
|
||||
persistentListOf(
|
||||
listOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.action_retry,
|
||||
icon = Icons.Outlined.Refresh,
|
||||
|
||||
@@ -53,11 +53,9 @@ import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsViewModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
@@ -68,7 +66,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
@Composable
|
||||
fun ExtensionDetailsScreen(
|
||||
navigateUp: () -> Unit,
|
||||
state: ExtensionDetailsScreenModel.State,
|
||||
state: ExtensionDetailsViewModel.State,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickEnableAll: () -> Unit,
|
||||
onClickDisableAll: () -> Unit,
|
||||
@@ -80,12 +78,12 @@ fun ExtensionDetailsScreen(
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val url = remember(state.extension) {
|
||||
val regex = """https://raw.githubusercontent.com/(.+?)/(.+?)/.+""".toRegex()
|
||||
regex.find(state.extension?.repoUrl.orEmpty())
|
||||
regex.find(state.extension?.store?.indexUrl.orEmpty())
|
||||
?.let {
|
||||
val (user, repo) = it.destructured
|
||||
"https://github.com/$user/$repo"
|
||||
}
|
||||
?: state.extension?.repoUrl
|
||||
?: state.extension?.store?.indexUrl
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -95,37 +93,35 @@ fun ExtensionDetailsScreen(
|
||||
navigateUp = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
if (url != null) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_open_repo),
|
||||
icon = Icons.AutoMirrored.Outlined.Launch,
|
||||
onClick = {
|
||||
uriHandler.openUri(url)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
addAll(
|
||||
listOf(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_enable_all),
|
||||
onClick = onClickEnableAll,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_disable_all),
|
||||
onClick = onClickDisableAll,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.pref_clear_cookies),
|
||||
onClick = onClickClearCookies,
|
||||
),
|
||||
actions = buildList {
|
||||
if (url != null) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_open_repo),
|
||||
icon = Icons.AutoMirrored.Outlined.Launch,
|
||||
onClick = {
|
||||
uriHandler.openUri(url)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
.build(),
|
||||
addAll(
|
||||
listOf(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_enable_all),
|
||||
onClick = onClickEnableAll,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_disable_all),
|
||||
onClick = onClickDisableAll,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.pref_clear_cookies),
|
||||
onClick = onClickClearCookies,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
@@ -157,7 +153,7 @@ fun ExtensionDetailsScreen(
|
||||
private fun ExtensionDetails(
|
||||
contentPadding: PaddingValues,
|
||||
extension: Extension.Installed,
|
||||
sources: ImmutableList<ExtensionSourceItem>,
|
||||
sources: List<ExtensionSourceItem>,
|
||||
incognitoMode: Boolean,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickUninstall: () -> Unit,
|
||||
@@ -248,14 +244,17 @@ private fun DetailsHeader(
|
||||
|
||||
if (extension is Extension.Installed) {
|
||||
append("\n\n")
|
||||
append(
|
||||
appendLine(
|
||||
"""
|
||||
Update available: ${extension.hasUpdate}
|
||||
Obsolete: ${extension.isObsolete}
|
||||
Orphaned: ${extension.isObsolete}
|
||||
Shared: ${extension.isShared}
|
||||
Repository: ${extension.repoUrl}
|
||||
""".trimIndent(),
|
||||
)
|
||||
val store = extension.store
|
||||
if (store != null) {
|
||||
append("Repository: ${store.indexUrl}")
|
||||
}
|
||||
}
|
||||
}
|
||||
context.copyToClipboard("Extension Debug information", extDebugInfo)
|
||||
|
||||
@@ -47,16 +47,14 @@ import eu.kanade.presentation.browse.components.BaseBrowseItem
|
||||
import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||
import eu.kanade.presentation.components.WarningBanner
|
||||
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen
|
||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsViewModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
@@ -72,7 +70,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
|
||||
@Composable
|
||||
fun ExtensionScreen(
|
||||
state: ExtensionsScreenModel.State,
|
||||
state: ExtensionsViewModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
searchQuery: String?,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
@@ -104,11 +102,11 @@ fun ExtensionScreen(
|
||||
EmptyScreen(
|
||||
stringRes = msg,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
actions = persistentListOf(
|
||||
actions = listOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.label_extension_repos,
|
||||
stringRes = MR.strings.extensionStores,
|
||||
icon = Icons.Outlined.Settings,
|
||||
onClick = { navigator.push(ExtensionReposScreen()) },
|
||||
onClick = { navigator.push(ExtensionStoresScreen()) },
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -134,7 +132,7 @@ fun ExtensionScreen(
|
||||
|
||||
@Composable
|
||||
private fun ExtensionContent(
|
||||
state: ExtensionsScreenModel.State,
|
||||
state: ExtensionsViewModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
onClickItemCancel: (Extension) -> Unit,
|
||||
@@ -188,14 +186,14 @@ private fun ExtensionContent(
|
||||
}
|
||||
ExtensionHeader(
|
||||
textRes = header.textRes,
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
modifier = Modifier.animateItem(),
|
||||
action = action,
|
||||
)
|
||||
}
|
||||
is ExtensionUiModel.Header.Text -> {
|
||||
ExtensionHeader(
|
||||
text = header.text,
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
modifier = Modifier.animateItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -213,7 +211,7 @@ private fun ExtensionContent(
|
||||
},
|
||||
) { item ->
|
||||
ExtensionItem(
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
modifier = Modifier.animateItem(),
|
||||
item = item,
|
||||
onClickItem = {
|
||||
when (it) {
|
||||
|
||||
@@ -10,9 +10,9 @@ import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
@@ -20,14 +20,14 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchScreen(
|
||||
state: SearchScreenModel.State,
|
||||
state: SearchViewModel.State,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
) {
|
||||
@@ -62,10 +62,10 @@ fun GlobalSearchScreen(
|
||||
|
||||
@Composable
|
||||
internal fun GlobalSearchContent(
|
||||
items: Map<CatalogueSource, SearchItemResult>,
|
||||
items: Map<Source, SearchItemResult>,
|
||||
contentPadding: PaddingValues,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
fromSourceId: Long? = null,
|
||||
|
||||
@@ -3,15 +3,15 @@ package eu.kanade.presentation.browse
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
||||
@Composable
|
||||
fun MigrateSearchScreen(
|
||||
state: SearchScreenModel.State,
|
||||
state: SearchViewModel.State,
|
||||
fromSourceId: Long?,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
@@ -19,7 +19,7 @@ fun MigrateSearchScreen(
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
) {
|
||||
|
||||
@@ -24,9 +24,8 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||
import eu.kanade.presentation.browse.components.SourceIcon
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceViewModel
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.Badge
|
||||
@@ -44,7 +43,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
|
||||
@Composable
|
||||
fun MigrateSourceScreen(
|
||||
state: MigrateSourceScreenModel.State,
|
||||
state: MigrateSourceViewModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
onClickItem: (Source) -> Unit,
|
||||
onToggleSortingDirection: () -> Unit,
|
||||
@@ -76,7 +75,7 @@ fun MigrateSourceScreen(
|
||||
|
||||
@Composable
|
||||
private fun MigrateSourceList(
|
||||
list: ImmutableList<Pair<Source, Long>>,
|
||||
list: List<Pair<Source, Long>>,
|
||||
contentPadding: PaddingValues,
|
||||
onClickItem: (Source) -> Unit,
|
||||
onLongClickItem: (Source) -> Unit,
|
||||
|
||||
@@ -10,8 +10,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterViewModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -23,7 +22,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
@Composable
|
||||
fun SourcesFilterScreen(
|
||||
navigateUp: () -> Unit,
|
||||
state: SourcesFilterScreenModel.State.Success,
|
||||
state: SourcesFilterViewModel.State.Success,
|
||||
onClickLanguage: (String) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
) {
|
||||
@@ -55,7 +54,7 @@ fun SourcesFilterScreen(
|
||||
@Composable
|
||||
private fun SourcesFilterContent(
|
||||
contentPadding: PaddingValues,
|
||||
state: SourcesFilterScreenModel.State.Success,
|
||||
state: SourcesFilterViewModel.State.Success,
|
||||
onClickLanguage: (String) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
) {
|
||||
@@ -69,7 +68,7 @@ private fun SourcesFilterContent(
|
||||
contentType = "source-filter-header",
|
||||
) {
|
||||
SourcesFilterHeader(
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
modifier = Modifier.animateItem(),
|
||||
language = language,
|
||||
enabled = enabled,
|
||||
onClickItem = onClickLanguage,
|
||||
@@ -82,7 +81,7 @@ private fun SourcesFilterContent(
|
||||
contentType = { "source-filter-item" },
|
||||
) { source ->
|
||||
SourcesFilterItem(
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
modifier = Modifier.animateItem(),
|
||||
source = source,
|
||||
enabled = "${source.id}" !in state.disabledSources,
|
||||
onClickItem = onClickSource,
|
||||
|
||||
@@ -21,8 +21,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesViewModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceViewModel.Listing
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.source.model.Pin
|
||||
import tachiyomi.domain.source.model.Source
|
||||
@@ -40,7 +40,7 @@ import tachiyomi.source.local.isLocal
|
||||
|
||||
@Composable
|
||||
fun SourcesScreen(
|
||||
state: SourcesScreenModel.State,
|
||||
state: SourcesViewModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
onClickItem: (Source, Listing) -> Unit,
|
||||
onClickPin: (Source) -> Unit,
|
||||
|
||||
@@ -18,7 +18,6 @@ import eu.kanade.presentation.components.RadioMenuItem
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -54,44 +53,42 @@ fun BrowseSourceToolbar(
|
||||
onClickCloseSearch = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
actions = buildList {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_display_mode),
|
||||
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||
Icons.AutoMirrored.Filled.ViewList
|
||||
} else {
|
||||
Icons.Filled.ViewModule
|
||||
},
|
||||
onClick = { selectingDisplayMode = true },
|
||||
),
|
||||
)
|
||||
if (isLocalSource) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_display_mode),
|
||||
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||
Icons.AutoMirrored.Filled.ViewList
|
||||
} else {
|
||||
Icons.Filled.ViewModule
|
||||
},
|
||||
onClick = { selectingDisplayMode = true },
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.label_help),
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_open_in_web_view),
|
||||
onClick = onWebViewClick,
|
||||
),
|
||||
)
|
||||
if (isLocalSource) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.label_help),
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_open_in_web_view),
|
||||
onClick = onWebViewClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
if (isConfigurableSource) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_settings),
|
||||
onClick = onSettingsClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
.build(),
|
||||
if (isConfigurableSource) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_settings),
|
||||
onClick = onSettingsClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
DropdownMenu(
|
||||
|
||||
@@ -27,21 +27,17 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import eu.kanade.core.preference.asToggleableState
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import tachiyomi.core.common.preference.CheckboxState
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
fun CategoryCreateDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: (String) -> Unit,
|
||||
categories: ImmutableList<String>,
|
||||
categories: List<String>,
|
||||
) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
|
||||
@@ -93,8 +89,6 @@ fun CategoryCreateDialog(
|
||||
)
|
||||
|
||||
LaunchedEffect(focusRequester) {
|
||||
// TODO: https://issuetracker.google.com/issues/204502668
|
||||
delay(0.1.seconds)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
@@ -103,7 +97,7 @@ fun CategoryCreateDialog(
|
||||
fun CategoryRenameDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onRename: (String) -> Unit,
|
||||
categories: ImmutableList<String>,
|
||||
categories: List<String>,
|
||||
category: String,
|
||||
) {
|
||||
var name by remember { mutableStateOf(category) }
|
||||
@@ -157,8 +151,6 @@ fun CategoryRenameDialog(
|
||||
)
|
||||
|
||||
LaunchedEffect(focusRequester) {
|
||||
// TODO: https://issuetracker.google.com/issues/204502668
|
||||
delay(0.1.seconds)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
@@ -195,7 +187,7 @@ fun CategoryDeleteDialog(
|
||||
|
||||
@Composable
|
||||
fun ChangeCategoryDialog(
|
||||
initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||
initialSelection: List<CheckboxState<Category>>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onEditCategories: () -> Unit,
|
||||
onConfirm: (List<Long>, List<Long>) -> Unit,
|
||||
@@ -267,7 +259,7 @@ fun ChangeCategoryDialog(
|
||||
if (index != -1) {
|
||||
val mutableList = selection.toMutableList()
|
||||
mutableList[index] = it.next()
|
||||
selection = mutableList.toList().toImmutableList()
|
||||
selection = mutableList.toList()
|
||||
}
|
||||
}
|
||||
Row(
|
||||
|
||||
@@ -28,7 +28,7 @@ fun NavigatorAdaptiveSheet(
|
||||
content = { sheetNavigator ->
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
||||
enableImplicitDismiss = enableSwipeDismiss(sheetNavigator),
|
||||
) {
|
||||
ScreenTransition(
|
||||
navigator = sheetNavigator,
|
||||
@@ -63,7 +63,7 @@ fun NavigatorAdaptiveSheet(
|
||||
fun AdaptiveSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enableSwipeDismiss: Boolean = true,
|
||||
enableImplicitDismiss: Boolean = true,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val isTabletUi = isTabletUi()
|
||||
@@ -74,7 +74,7 @@ fun AdaptiveSheet(
|
||||
) {
|
||||
AdaptiveSheetImpl(
|
||||
isTabletUi = isTabletUi,
|
||||
enableSwipeDismiss = enableSwipeDismiss,
|
||||
enableImplicitDismiss = enableImplicitDismiss,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
) {
|
||||
|
||||
@@ -51,7 +51,6 @@ import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide
|
||||
@@ -59,8 +58,6 @@ import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import tachiyomi.presentation.core.util.showSoftKeyboard
|
||||
|
||||
const val SEARCH_DEBOUNCE_MILLIS = 250L
|
||||
|
||||
@Composable
|
||||
fun AppBar(
|
||||
title: String?,
|
||||
@@ -190,7 +187,7 @@ fun AppBarTitle(
|
||||
|
||||
@Composable
|
||||
fun AppBarActions(
|
||||
actions: ImmutableList<AppBar.AppBarAction>,
|
||||
actions: List<AppBar.AppBarAction>,
|
||||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -52,7 +51,7 @@ private fun DownloadDropdownMenuItems(
|
||||
onDismissRequest: () -> Unit,
|
||||
onDownloadClicked: (DownloadAction) -> Unit,
|
||||
) {
|
||||
val options = persistentListOf(
|
||||
val options = listOf(
|
||||
DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1),
|
||||
DownloadAction.NEXT_5_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 5, 5),
|
||||
DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10),
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||
@@ -31,7 +30,7 @@ private fun WithActionPreview() {
|
||||
Surface {
|
||||
EmptyScreen(
|
||||
stringRes = MR.strings.empty_screen,
|
||||
actions = persistentListOf(
|
||||
actions = listOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.action_retry,
|
||||
icon = Icons.Outlined.Refresh,
|
||||
|
||||
@@ -27,7 +27,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.TabText
|
||||
@@ -41,7 +40,7 @@ object TabbedDialogPaddings {
|
||||
@Composable
|
||||
fun TabbedDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
tabTitles: ImmutableList<String>,
|
||||
tabTitles: List<String>,
|
||||
modifier: Modifier = Modifier,
|
||||
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
|
||||
pagerState: PagerState = rememberPagerState { tabTitles.size },
|
||||
@@ -59,7 +58,6 @@ fun TabbedDialog(
|
||||
modifier = Modifier.weight(1f),
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
divider = {},
|
||||
) {
|
||||
tabTitles.fastForEachIndexed { index, tab ->
|
||||
Tab(
|
||||
@@ -73,7 +71,6 @@ fun TabbedDialog(
|
||||
|
||||
tabOverflowMenuContent?.let { MoreMenu(it) }
|
||||
}
|
||||
HorizontalDivider()
|
||||
|
||||
HorizontalPager(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
|
||||
@@ -22,8 +22,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.zIndex
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.TabText
|
||||
@@ -32,7 +30,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
@Composable
|
||||
fun TabbedScreen(
|
||||
titleRes: StringResource,
|
||||
tabs: ImmutableList<TabContent>,
|
||||
tabs: List<TabContent>,
|
||||
state: PagerState = rememberPagerState { tabs.size },
|
||||
searchQuery: String? = null,
|
||||
onChangeSearchQuery: (String?) -> Unit = {},
|
||||
@@ -94,6 +92,6 @@ data class TabContent(
|
||||
val titleRes: StringResource,
|
||||
val badgeNumber: Int? = null,
|
||||
val searchEnabled: Boolean = false,
|
||||
val actions: ImmutableList<AppBar.AppBarAction> = persistentListOf(),
|
||||
val actions: List<AppBar.AppBarAction> = listOf(),
|
||||
val content: @Composable (contentPadding: PaddingValues, snackbarHostState: SnackbarHostState) -> Unit,
|
||||
)
|
||||
|
||||
@@ -18,9 +18,7 @@ import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.presentation.components.relativeDateText
|
||||
import eu.kanade.presentation.history.components.HistoryItem
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
@@ -33,13 +31,13 @@ import java.time.LocalDate
|
||||
|
||||
@Composable
|
||||
fun HistoryScreen(
|
||||
state: HistoryScreenModel.State,
|
||||
state: HistoryViewModel.State,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onSearchQueryChange: (String?) -> Unit,
|
||||
onClickCover: (mangaId: Long) -> Unit,
|
||||
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
||||
onClickFavorite: (mangaId: Long) -> Unit,
|
||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
||||
onDialogChange: (HistoryViewModel.Dialog?) -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
@@ -49,12 +47,12 @@ fun HistoryScreen(
|
||||
onChangeSearchQuery = onSearchQueryChange,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.pref_clear_history),
|
||||
icon = Icons.Outlined.DeleteSweep,
|
||||
onClick = {
|
||||
onDialogChange(HistoryScreenModel.Dialog.DeleteAll)
|
||||
onDialogChange(HistoryViewModel.Dialog.DeleteAll)
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -84,7 +82,7 @@ fun HistoryScreen(
|
||||
contentPadding = contentPadding,
|
||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
||||
onClickDelete = { item -> onDialogChange(HistoryViewModel.Dialog.Delete(item)) },
|
||||
onClickFavorite = { history -> onClickFavorite(history.mangaId) },
|
||||
)
|
||||
}
|
||||
@@ -117,14 +115,14 @@ private fun HistoryScreenContent(
|
||||
when (item) {
|
||||
is HistoryUiModel.Header -> {
|
||||
ListGroupHeader(
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
modifier = Modifier.animateItem(),
|
||||
text = relativeDateText(item.date),
|
||||
)
|
||||
}
|
||||
is HistoryUiModel.Item -> {
|
||||
val value = item.item
|
||||
HistoryItem(
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
modifier = Modifier.animateItem(),
|
||||
history = value,
|
||||
onClickCover = { onClickCover(value) },
|
||||
onClickResume = { onClickResume(value) },
|
||||
@@ -145,8 +143,8 @@ sealed interface HistoryUiModel {
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
internal fun HistoryScreenPreviews(
|
||||
@PreviewParameter(HistoryScreenModelStateProvider::class)
|
||||
historyState: HistoryScreenModel.State,
|
||||
@PreviewParameter(HistoryviewModelStateProvider::class)
|
||||
historyState: HistoryViewModel.State,
|
||||
) {
|
||||
TachiyomiPreviewTheme {
|
||||
HistoryScreen(
|
||||
|
||||
+9
-9
@@ -1,7 +1,7 @@
|
||||
package eu.kanade.presentation.history
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||
import tachiyomi.domain.manga.model.MangaCover
|
||||
import java.time.Instant
|
||||
@@ -10,9 +10,9 @@ import java.time.temporal.ChronoUnit
|
||||
import java.util.Date
|
||||
import kotlin.random.Random
|
||||
|
||||
class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenModel.State> {
|
||||
class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.State> {
|
||||
|
||||
private val multiPage = HistoryScreenModel.State(
|
||||
private val multiPage = HistoryViewModel.State(
|
||||
searchQuery = null,
|
||||
list =
|
||||
listOf(HistoryUiModelExamples.headerToday)
|
||||
@@ -26,7 +26,7 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
private val shortRecent = HistoryScreenModel.State(
|
||||
private val shortRecent = HistoryViewModel.State(
|
||||
searchQuery = null,
|
||||
list = listOf(
|
||||
HistoryUiModelExamples.headerToday,
|
||||
@@ -35,7 +35,7 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
private val shortFuture = HistoryScreenModel.State(
|
||||
private val shortFuture = HistoryViewModel.State(
|
||||
searchQuery = null,
|
||||
list = listOf(
|
||||
HistoryUiModelExamples.headerTomorrow,
|
||||
@@ -44,23 +44,23 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
private val empty = HistoryScreenModel.State(
|
||||
private val empty = HistoryViewModel.State(
|
||||
searchQuery = null,
|
||||
list = listOf(),
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
private val loadingWithSearchQuery = HistoryScreenModel.State(
|
||||
private val loadingWithSearchQuery = HistoryViewModel.State(
|
||||
searchQuery = "Example Search Query",
|
||||
)
|
||||
|
||||
private val loading = HistoryScreenModel.State(
|
||||
private val loading = HistoryViewModel.State(
|
||||
searchQuery = null,
|
||||
list = null,
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
override val values: Sequence<HistoryScreenModel.State> = sequenceOf(
|
||||
override val values: Sequence<HistoryViewModel.State> = sequenceOf(
|
||||
multiPage,
|
||||
shortRecent,
|
||||
shortFuture,
|
||||
@@ -19,9 +19,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.library.LibrarySettingsViewModel
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.core.common.preference.TriState
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
@@ -42,12 +41,12 @@ import tachiyomi.presentation.core.util.collectAsState
|
||||
@Composable
|
||||
fun LibrarySettingsDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
viewModel: LibrarySettingsViewModel,
|
||||
category: Category?,
|
||||
) {
|
||||
TabbedDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
tabTitles = persistentListOf(
|
||||
tabTitles = listOf(
|
||||
stringResource(MR.strings.action_filter),
|
||||
stringResource(MR.strings.action_sort),
|
||||
stringResource(MR.strings.action_display),
|
||||
@@ -60,14 +59,14 @@ fun LibrarySettingsDialog(
|
||||
) {
|
||||
when (page) {
|
||||
0 -> FilterPage(
|
||||
screenModel = screenModel,
|
||||
viewModel = viewModel,
|
||||
)
|
||||
1 -> SortPage(
|
||||
category = category,
|
||||
screenModel = screenModel,
|
||||
viewModel = viewModel,
|
||||
)
|
||||
2 -> DisplayPage(
|
||||
screenModel = screenModel,
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,11 +75,11 @@ fun LibrarySettingsDialog(
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.FilterPage(
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
viewModel: LibrarySettingsViewModel,
|
||||
) {
|
||||
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded.collectAsState()
|
||||
val downloadedOnly by screenModel.preferences.downloadedOnly.collectAsState()
|
||||
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions.collectAsState()
|
||||
val filterDownloaded by viewModel.libraryPreferences.filterDownloaded.collectAsState()
|
||||
val downloadedOnly by viewModel.preferences.downloadedOnly.collectAsState()
|
||||
val autoUpdateMangaRestrictions by viewModel.libraryPreferences.autoUpdateMangaRestrictions.collectAsState()
|
||||
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.label_downloaded),
|
||||
@@ -90,64 +89,64 @@ private fun ColumnScope.FilterPage(
|
||||
filterDownloaded
|
||||
},
|
||||
enabled = !downloadedOnly,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterDownloaded) },
|
||||
onClick = { viewModel.toggleFilter(LibraryPreferences::filterDownloaded) },
|
||||
)
|
||||
val filterUnread by screenModel.libraryPreferences.filterUnread.collectAsState()
|
||||
val filterUnread by viewModel.libraryPreferences.filterUnread.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.action_filter_unread),
|
||||
state = filterUnread,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
|
||||
onClick = { viewModel.toggleFilter(LibraryPreferences::filterUnread) },
|
||||
)
|
||||
val filterStarted by screenModel.libraryPreferences.filterStarted.collectAsState()
|
||||
val filterStarted by viewModel.libraryPreferences.filterStarted.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.label_started),
|
||||
state = filterStarted,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
|
||||
onClick = { viewModel.toggleFilter(LibraryPreferences::filterStarted) },
|
||||
)
|
||||
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked.collectAsState()
|
||||
val filterBookmarked by viewModel.libraryPreferences.filterBookmarked.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||
state = filterBookmarked,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
|
||||
onClick = { viewModel.toggleFilter(LibraryPreferences::filterBookmarked) },
|
||||
)
|
||||
val filterCompleted by screenModel.libraryPreferences.filterCompleted.collectAsState()
|
||||
val filterCompleted by viewModel.libraryPreferences.filterCompleted.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.completed),
|
||||
state = filterCompleted,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) },
|
||||
onClick = { viewModel.toggleFilter(LibraryPreferences::filterCompleted) },
|
||||
)
|
||||
// TODO: re-enable when custom intervals are ready for stable
|
||||
if ((!isReleaseBuildType) && LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions) {
|
||||
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom.collectAsState()
|
||||
val filterIntervalCustom by viewModel.libraryPreferences.filterIntervalCustom.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.action_filter_interval_custom),
|
||||
state = filterIntervalCustom,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterIntervalCustom) },
|
||||
onClick = { viewModel.toggleFilter(LibraryPreferences::filterIntervalCustom) },
|
||||
)
|
||||
}
|
||||
|
||||
val trackers by screenModel.trackersFlow.collectAsState()
|
||||
val trackers by viewModel.trackersFlow.collectAsState()
|
||||
when (trackers.size) {
|
||||
0 -> {
|
||||
// No trackers
|
||||
}
|
||||
1 -> {
|
||||
val service = trackers[0]
|
||||
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||
val filterTracker by viewModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.action_filter_tracked),
|
||||
state = filterTracker,
|
||||
onClick = { screenModel.toggleTracker(service.id.toInt()) },
|
||||
onClick = { viewModel.toggleTracker(service.id.toInt()) },
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
HeadingItem(MR.strings.action_filter_tracked)
|
||||
trackers.map { service ->
|
||||
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||
val filterTracker by viewModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||
TriStateItem(
|
||||
label = service.name,
|
||||
state = filterTracker,
|
||||
onClick = { screenModel.toggleTracker(service.id.toInt()) },
|
||||
onClick = { viewModel.toggleTracker(service.id.toInt()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -157,9 +156,9 @@ private fun ColumnScope.FilterPage(
|
||||
@Composable
|
||||
private fun ColumnScope.SortPage(
|
||||
category: Category?,
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
viewModel: LibrarySettingsViewModel,
|
||||
) {
|
||||
val trackers by screenModel.trackersFlow.collectAsState()
|
||||
val trackers by viewModel.trackersFlow.collectAsState()
|
||||
val sortingMode = category.sort.type
|
||||
val sortDescending = !category.sort.isAscending
|
||||
|
||||
@@ -190,7 +189,7 @@ private fun ColumnScope.SortPage(
|
||||
icon = Icons.Default.Refresh
|
||||
.takeIf { sortingMode == LibrarySort.Type.Random },
|
||||
onClick = {
|
||||
screenModel.setSort(category, mode, LibrarySort.Direction.Ascending)
|
||||
viewModel.setSort(category, mode, LibrarySort.Direction.Ascending)
|
||||
},
|
||||
)
|
||||
return@map
|
||||
@@ -212,7 +211,7 @@ private fun ColumnScope.SortPage(
|
||||
LibrarySort.Direction.Ascending
|
||||
}
|
||||
}
|
||||
screenModel.setSort(category, mode, direction)
|
||||
viewModel.setSort(category, mode, direction)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -227,14 +226,14 @@ private val displayModes = listOf(
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.DisplayPage(
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
viewModel: LibrarySettingsViewModel,
|
||||
) {
|
||||
val displayMode by screenModel.libraryPreferences.displayMode.collectAsState()
|
||||
val displayMode by viewModel.libraryPreferences.displayMode.collectAsState()
|
||||
SettingsChipRow(MR.strings.action_display_mode) {
|
||||
displayModes.map { (titleRes, mode) ->
|
||||
FilterChip(
|
||||
selected = displayMode == mode,
|
||||
onClick = { screenModel.setDisplayMode(mode) },
|
||||
onClick = { viewModel.setDisplayMode(mode) },
|
||||
label = { Text(stringResource(titleRes)) },
|
||||
)
|
||||
}
|
||||
@@ -244,9 +243,9 @@ private fun ColumnScope.DisplayPage(
|
||||
val configuration = LocalConfiguration.current
|
||||
val columnPreference = remember {
|
||||
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
screenModel.libraryPreferences.landscapeColumns
|
||||
viewModel.libraryPreferences.landscapeColumns
|
||||
} else {
|
||||
screenModel.libraryPreferences.portraitColumns
|
||||
viewModel.libraryPreferences.portraitColumns
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,32 +267,32 @@ private fun ColumnScope.DisplayPage(
|
||||
HeadingItem(MR.strings.overlay_header)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_download_badge),
|
||||
pref = screenModel.libraryPreferences.downloadBadge,
|
||||
pref = viewModel.libraryPreferences.downloadBadge,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_unread_badge),
|
||||
pref = screenModel.libraryPreferences.unreadBadge,
|
||||
pref = viewModel.libraryPreferences.unreadBadge,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_local_badge),
|
||||
pref = screenModel.libraryPreferences.localBadge,
|
||||
pref = viewModel.libraryPreferences.localBadge,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_language_badge),
|
||||
pref = screenModel.libraryPreferences.languageBadge,
|
||||
pref = viewModel.libraryPreferences.languageBadge,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_show_continue_reading_button),
|
||||
pref = screenModel.libraryPreferences.showContinueReadingButton,
|
||||
pref = viewModel.libraryPreferences.showContinueReadingButton,
|
||||
)
|
||||
|
||||
HeadingItem(MR.strings.tabs_header)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_show_tabs),
|
||||
pref = screenModel.libraryPreferences.categoryTabs,
|
||||
pref = viewModel.libraryPreferences.categoryTabs,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_show_number_of_items),
|
||||
pref = screenModel.libraryPreferences.categoryNumberOfItems,
|
||||
pref = viewModel.libraryPreferences.categoryNumberOfItems,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import tachiyomi.presentation.core.components.Badge
|
||||
|
||||
@Composable
|
||||
internal fun DownloadsBadge(count: Long) {
|
||||
internal fun DownloadsBadge(count: Int) {
|
||||
if (count > 0) {
|
||||
Badge(
|
||||
text = "$count",
|
||||
|
||||
+4
-4
@@ -44,13 +44,13 @@ internal fun LibraryComfortableGrid(
|
||||
lastModified = manga.coverLastModified,
|
||||
),
|
||||
coverBadgeStart = {
|
||||
DownloadsBadge(count = libraryItem.downloadCount)
|
||||
UnreadBadge(count = libraryItem.unreadCount)
|
||||
DownloadsBadge(count = libraryItem.badges.downloadCount)
|
||||
UnreadBadge(count = libraryItem.badges.unreadCount)
|
||||
},
|
||||
coverBadgeEnd = {
|
||||
LanguageBadge(
|
||||
isLocal = libraryItem.isLocal,
|
||||
sourceLanguage = libraryItem.sourceLanguage,
|
||||
isLocal = libraryItem.badges.isLocal,
|
||||
sourceLanguage = libraryItem.badges.sourceLanguage,
|
||||
)
|
||||
},
|
||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||
|
||||
@@ -45,13 +45,13 @@ internal fun LibraryCompactGrid(
|
||||
lastModified = manga.coverLastModified,
|
||||
),
|
||||
coverBadgeStart = {
|
||||
DownloadsBadge(count = libraryItem.downloadCount)
|
||||
UnreadBadge(count = libraryItem.unreadCount)
|
||||
DownloadsBadge(count = libraryItem.badges.downloadCount)
|
||||
UnreadBadge(count = libraryItem.badges.unreadCount)
|
||||
},
|
||||
coverBadgeEnd = {
|
||||
LanguageBadge(
|
||||
isLocal = libraryItem.isLocal,
|
||||
sourceLanguage = libraryItem.sourceLanguage,
|
||||
isLocal = libraryItem.badges.isLocal,
|
||||
sourceLanguage = libraryItem.badges.sourceLanguage,
|
||||
)
|
||||
},
|
||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||
|
||||
@@ -54,11 +54,11 @@ internal fun LibraryList(
|
||||
lastModified = manga.coverLastModified,
|
||||
),
|
||||
badge = {
|
||||
DownloadsBadge(count = libraryItem.downloadCount)
|
||||
UnreadBadge(count = libraryItem.unreadCount)
|
||||
DownloadsBadge(count = libraryItem.badges.downloadCount)
|
||||
UnreadBadge(count = libraryItem.badges.unreadCount)
|
||||
LanguageBadge(
|
||||
isLocal = libraryItem.isLocal,
|
||||
sourceLanguage = libraryItem.sourceLanguage,
|
||||
isLocal = libraryItem.badges.isLocal,
|
||||
sourceLanguage = libraryItem.badges.sourceLanguage,
|
||||
)
|
||||
},
|
||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package eu.kanade.presentation.library.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.presentation.core.components.material.TabText
|
||||
@@ -22,29 +18,22 @@ internal fun LibraryTabs(
|
||||
onTabItemClick: (Int) -> Unit,
|
||||
) {
|
||||
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
||||
Column(modifier = Modifier.zIndex(2f)) {
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = currentPageIndex,
|
||||
edgePadding = 0.dp,
|
||||
// TODO: use default when width is fixed upstream
|
||||
// https://issuetracker.google.com/issues/242879624
|
||||
divider = {},
|
||||
) {
|
||||
categories.forEachIndexed { index, category ->
|
||||
Tab(
|
||||
selected = currentPageIndex == index,
|
||||
onClick = { onTabItemClick(index) },
|
||||
text = {
|
||||
TabText(
|
||||
text = category.visualName,
|
||||
badgeCount = getItemCountForCategory(category),
|
||||
)
|
||||
},
|
||||
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = currentPageIndex,
|
||||
edgePadding = 0.dp,
|
||||
) {
|
||||
categories.forEachIndexed { index, category ->
|
||||
Tab(
|
||||
selected = currentPageIndex == index,
|
||||
onClick = { onTabItemClick(index) },
|
||||
text = {
|
||||
TabText(
|
||||
text = category.visualName,
|
||||
badgeCount = getItemCountForCategory(category),
|
||||
)
|
||||
},
|
||||
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.Pill
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -96,7 +95,7 @@ private fun LibraryRegularToolbar(
|
||||
actions = {
|
||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_filter),
|
||||
icon = Icons.Outlined.FilterList,
|
||||
@@ -133,7 +132,7 @@ private fun LibrarySelectionToolbar(
|
||||
titleContent = { Text(text = "$selectedCount") },
|
||||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_select_all),
|
||||
icon = Icons.Outlined.SelectAll,
|
||||
|
||||
@@ -31,7 +31,6 @@ import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.manga.model.downloadedFilter
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.core.common.preference.TriState
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -70,7 +69,7 @@ fun ChapterSettingsDialog(
|
||||
|
||||
TabbedDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
tabTitles = persistentListOf(
|
||||
tabTitles = listOf(
|
||||
stringResource(MR.strings.action_filter),
|
||||
stringResource(MR.strings.action_sort),
|
||||
stringResource(MR.strings.action_display),
|
||||
|
||||
@@ -60,7 +60,7 @@ import eu.kanade.presentation.util.formatChapterNumber
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaViewModel
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.service.missingChaptersCount
|
||||
@@ -79,7 +79,7 @@ import java.time.Instant
|
||||
|
||||
@Composable
|
||||
fun MangaScreen(
|
||||
state: MangaScreenModel.State.Success,
|
||||
state: MangaViewModel.State.Success,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
nextUpdate: Instant?,
|
||||
isTabletUi: Boolean,
|
||||
@@ -210,7 +210,7 @@ fun MangaScreen(
|
||||
|
||||
@Composable
|
||||
private fun MangaScreenSmallImpl(
|
||||
state: MangaScreenModel.State.Success,
|
||||
state: MangaViewModel.State.Success,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
nextUpdate: Instant?,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
@@ -452,7 +452,7 @@ private fun MangaScreenSmallImpl(
|
||||
|
||||
@Composable
|
||||
fun MangaScreenLargeImpl(
|
||||
state: MangaScreenModel.State.Success,
|
||||
state: MangaViewModel.State.Success,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
nextUpdate: Instant?,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
|
||||
@@ -48,7 +48,6 @@ import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.presentation.manga.EditCoverAction
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
@@ -69,7 +68,7 @@ fun MangaCoverDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
decorFitsSystemWindows = false, // Doesn't work https://issuetracker.google.com/issues/246909281
|
||||
decorFitsSystemWindows = false,
|
||||
),
|
||||
) {
|
||||
Scaffold(
|
||||
@@ -93,7 +92,7 @@ fun MangaCoverDialog(
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
ActionsPill {
|
||||
AppBarActions(
|
||||
actions = persistentListOf(
|
||||
actions = listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_share),
|
||||
icon = Icons.Outlined.Share,
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||
@@ -124,7 +123,7 @@ fun SetIntervalDialog(
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
|
||||
WheelTextPicker(
|
||||
items = items,
|
||||
size = size,
|
||||
|
||||
@@ -55,9 +55,6 @@ import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
private const val MAX_LENGTH = 250
|
||||
private const val MAX_LENGTH_WARN = MAX_LENGTH * 0.9
|
||||
|
||||
@Composable
|
||||
fun MangaNotesTextArea(
|
||||
state: MangaNotesScreen.State,
|
||||
@@ -102,7 +99,6 @@ fun MangaNotesTextArea(
|
||||
RichTextEditor(
|
||||
state = richTextState,
|
||||
textStyle = MaterialTheme.typography.bodyLarge,
|
||||
maxLength = MAX_LENGTH,
|
||||
placeholder = {
|
||||
Text(text = stringResource(MR.strings.notes_placeholder))
|
||||
},
|
||||
@@ -182,12 +178,7 @@ fun MangaNotesTextArea(
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = (MAX_LENGTH - textLength).toString(),
|
||||
color = if (textLength > MAX_LENGTH_WARN) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
Color.Unspecified
|
||||
},
|
||||
text = textLength.toString(),
|
||||
modifier = Modifier.padding(MaterialTheme.padding.extraSmall),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.theme.active
|
||||
@@ -76,7 +75,7 @@ fun MangaToolbar(
|
||||
|
||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||
AppBarActions(
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder().apply {
|
||||
actions = buildList {
|
||||
if (isActionMode) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
@@ -92,7 +91,7 @@ fun MangaToolbar(
|
||||
onClick = onInvertSelection,
|
||||
),
|
||||
)
|
||||
return@apply
|
||||
return@buildList
|
||||
}
|
||||
if (onClickDownload != null) {
|
||||
add(
|
||||
@@ -147,8 +146,7 @@ fun MangaToolbar(
|
||||
onClick = onClickEditNotes,
|
||||
),
|
||||
)
|
||||
}
|
||||
.build(),
|
||||
},
|
||||
)
|
||||
},
|
||||
isActionMode = isActionMode,
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.FirstBaseline
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
@@ -223,6 +222,7 @@ private val markdownComponents = markdownComponents(
|
||||
if (type in DISALLOWED_MARKDOWN_TYPES) {
|
||||
MarkdownText(
|
||||
content = model.content.substring(model.node.startOffset, model.node.endOffset),
|
||||
node = model.node,
|
||||
style = model.typography.text,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.presentation.more
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -15,7 +16,9 @@ import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
@Composable
|
||||
fun LogoHeader() {
|
||||
fun LogoHeader(
|
||||
iconPadding: PaddingValues = PaddingValues(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
@@ -25,7 +28,7 @@ fun LogoHeader() {
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier
|
||||
.padding(vertical = 56.dp)
|
||||
.padding(iconPadding)
|
||||
.size(64.dp),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package eu.kanade.presentation.more
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||
import androidx.compose.material.icons.outlined.AttachMoney
|
||||
import androidx.compose.material.icons.filled.VolunteerActivism
|
||||
import androidx.compose.material.icons.outlined.CloudOff
|
||||
import androidx.compose.material.icons.outlined.GetApp
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
@@ -13,10 +13,10 @@ import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.Storage
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.tachiyomi.R
|
||||
@@ -40,16 +40,17 @@ fun MoreScreen(
|
||||
onClickStats: () -> Unit,
|
||||
onClickDataAndStorage: () -> Unit,
|
||||
onClickSettings: () -> Unit,
|
||||
onClickSupport: () -> Unit,
|
||||
onClickAbout: () -> Unit,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
Scaffold { contentPadding ->
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
) {
|
||||
ScrollbarLazyColumn(contentPadding = contentPadding) {
|
||||
item {
|
||||
LogoHeader()
|
||||
LogoHeader(
|
||||
iconPadding = PaddingValues(vertical = 32.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
SwitchPreferenceWidget(
|
||||
@@ -132,6 +133,13 @@ fun MoreScreen(
|
||||
onPreferenceClick = onClickSettings,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.label_support_us),
|
||||
icon = Icons.Default.VolunteerActivism,
|
||||
onPreferenceClick = onClickSupport,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.pref_category_about),
|
||||
@@ -146,13 +154,6 @@ fun MoreScreen(
|
||||
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.label_donate),
|
||||
icon = Icons.Outlined.AttachMoney,
|
||||
onPreferenceClick = { uriHandler.openUri(Constants.URL_DONATE) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import eu.kanade.presentation.manga.components.MarkdownRender
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.tachiyomi.ui.more.NewUpdateScreenModel
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
@@ -27,16 +29,27 @@ import tachiyomi.presentation.core.screens.InfoScreen
|
||||
fun NewUpdateScreen(
|
||||
versionName: String,
|
||||
changelogInfo: String,
|
||||
stage: NewUpdateScreenModel.Stage,
|
||||
downloadProgress: () -> Int,
|
||||
onOpenInBrowser: () -> Unit,
|
||||
onRejectUpdate: () -> Unit,
|
||||
onAcceptUpdate: () -> Unit,
|
||||
onRejectUpdate: () -> Unit,
|
||||
) {
|
||||
InfoScreen(
|
||||
icon = Icons.Outlined.NewReleases,
|
||||
headingText = stringResource(MR.strings.update_check_notification_update_available),
|
||||
subtitleText = versionName,
|
||||
acceptText = stringResource(MR.strings.update_check_confirm),
|
||||
acceptText = when (stage) {
|
||||
NewUpdateScreenModel.Stage.Available -> stringResource(MR.strings.update_check_confirm)
|
||||
NewUpdateScreenModel.Stage.Downloading -> stringResource(
|
||||
MR.strings.downloading_with_progress,
|
||||
downloadProgress(),
|
||||
)
|
||||
NewUpdateScreenModel.Stage.Downloaded -> stringResource(MR.strings.action_install)
|
||||
NewUpdateScreenModel.Stage.Failed -> stringResource(MR.strings.action_retry)
|
||||
},
|
||||
onAcceptClick = onAcceptUpdate,
|
||||
canAccept = stage != NewUpdateScreenModel.Stage.Downloading,
|
||||
rejectText = stringResource(MR.strings.action_not_now),
|
||||
onRejectClick = onRejectUpdate,
|
||||
) {
|
||||
@@ -47,7 +60,7 @@ fun NewUpdateScreen(
|
||||
) {
|
||||
MarkdownRender(
|
||||
content = changelogInfo,
|
||||
flavour = GFMFlavourDescriptor(),
|
||||
flavour = remember { GFMFlavourDescriptor() },
|
||||
)
|
||||
|
||||
TextButton(
|
||||
@@ -76,9 +89,11 @@ private fun NewUpdateScreenPreview() {
|
||||
- Hello
|
||||
- World
|
||||
""".trimIndent(),
|
||||
stage = NewUpdateScreenModel.Stage.Available,
|
||||
downloadProgress = { 0 },
|
||||
onOpenInBrowser = {},
|
||||
onRejectUpdate = {},
|
||||
onAcceptUpdate = {},
|
||||
onRejectUpdate = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,8 +169,6 @@ internal class PermissionStep : OnboardingStep {
|
||||
) {
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
headlineContent = { Text(text = title) },
|
||||
supportingContent = { Text(text = subtitle) },
|
||||
trailingContent = {
|
||||
OutlinedButton(
|
||||
enabled = !granted,
|
||||
@@ -187,7 +185,9 @@ internal class PermissionStep : OnboardingStep {
|
||||
}
|
||||
}
|
||||
},
|
||||
supportingContent = { Text(text = subtitle) },
|
||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||
content = { Text(text = title) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -201,15 +201,15 @@ internal class PermissionStep : OnboardingStep {
|
||||
) {
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
headlineContent = { Text(text = title) },
|
||||
supportingContent = { Text(text = subtitle) },
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = granted,
|
||||
onCheckedChange = onToggleChange,
|
||||
)
|
||||
},
|
||||
supportingContent = { Text(text = subtitle) },
|
||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||
content = { Text(text = title) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.core.common.preference.Preference as PreferenceData
|
||||
@@ -69,10 +67,10 @@ sealed class Preference {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
data class ListPreference<T>(
|
||||
val preference: PreferenceData<T>,
|
||||
val entries: ImmutableMap<T, String>,
|
||||
val entries: Map<T, String>,
|
||||
override val title: String,
|
||||
override val subtitle: String? = "%s",
|
||||
val subtitleProvider: @Composable (value: T, entries: ImmutableMap<T, String>) -> String? =
|
||||
val subtitleProvider: @Composable (value: T, entries: Map<T, String>) -> String? =
|
||||
{ v, e -> subtitle?.format(e[v]) },
|
||||
override val icon: ImageVector? = null,
|
||||
override val enabled: Boolean = true,
|
||||
@@ -82,8 +80,8 @@ sealed class Preference {
|
||||
internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T)
|
||||
|
||||
@Composable
|
||||
internal fun internalSubtitleProvider(value: Any?, entries: ImmutableMap<out Any?, String>) =
|
||||
subtitleProvider(value as T, entries as ImmutableMap<T, String>)
|
||||
internal fun internalSubtitleProvider(value: Any?, entries: Map<out Any?, String>) =
|
||||
subtitleProvider(value as T, entries as Map<T, String>)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,10 +89,10 @@ sealed class Preference {
|
||||
*/
|
||||
data class BasicListPreference(
|
||||
val value: String,
|
||||
val entries: ImmutableMap<String, String>,
|
||||
val entries: Map<String, String>,
|
||||
override val title: String,
|
||||
override val subtitle: String? = "%s",
|
||||
val subtitleProvider: @Composable (value: String, entries: ImmutableMap<String, String>) -> String? =
|
||||
val subtitleProvider: @Composable (value: String, entries: Map<String, String>) -> String? =
|
||||
{ v, e -> subtitle?.format(e[v]) },
|
||||
override val icon: ImageVector? = null,
|
||||
override val enabled: Boolean = true,
|
||||
@@ -105,12 +103,13 @@ sealed class Preference {
|
||||
* A [PreferenceItem] that displays a list of entries as a dialog.
|
||||
* Multiple entries can be selected at the same time.
|
||||
*/
|
||||
data class MultiSelectListPreference(
|
||||
val preference: PreferenceData<Set<String>>,
|
||||
val entries: ImmutableMap<String, String>,
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
data class MultiSelectListPreference<T>(
|
||||
val preference: PreferenceData<Set<T>>,
|
||||
val entries: Map<T, String>,
|
||||
override val title: String,
|
||||
override val subtitle: String? = "%s",
|
||||
val subtitleProvider: @Composable (value: Set<String>, entries: ImmutableMap<String, String>) -> String? =
|
||||
val subtitleProvider: @Composable (value: Set<T>, entries: Map<T, String>) -> String? =
|
||||
{ v, e ->
|
||||
val combined = remember(v, e) {
|
||||
v.mapNotNull { e[it] }
|
||||
@@ -122,8 +121,15 @@ sealed class Preference {
|
||||
},
|
||||
override val icon: ImageVector? = null,
|
||||
override val enabled: Boolean = true,
|
||||
override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true },
|
||||
) : PreferenceItem<Set<String>, Boolean>()
|
||||
override val onValueChanged: suspend (value: Set<T>) -> Boolean = { true },
|
||||
) : PreferenceItem<Set<T>, Boolean>() {
|
||||
internal fun internalSet(value: Set<Any?>) = preference.set(value as Set<T>)
|
||||
internal suspend fun internalOnValueChanged(value: Set<Any?>) = onValueChanged(value as Set<T>)
|
||||
|
||||
@Composable
|
||||
internal fun internalSubtitleProvider(value: Set<Any?>, entries: Map<out Any?, String>) =
|
||||
subtitleProvider(value as Set<T>, entries as Map<T, String>)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [PreferenceItem] that shows a EditText in the dialog.
|
||||
@@ -177,6 +183,6 @@ sealed class Preference {
|
||||
override val title: String,
|
||||
override val enabled: Boolean = true,
|
||||
|
||||
val preferenceItems: ImmutableList<PreferenceItem<out Any, out Any>>,
|
||||
val preferenceItems: List<PreferenceItem<out Any, out Any>>,
|
||||
) : Preference()
|
||||
}
|
||||
|
||||
@@ -128,15 +128,18 @@ internal fun PreferenceItem(
|
||||
onValueChange = { scope.launch { item.onValueChanged(it) } },
|
||||
)
|
||||
}
|
||||
is Preference.PreferenceItem.MultiSelectListPreference -> {
|
||||
is Preference.PreferenceItem.MultiSelectListPreference<*> -> {
|
||||
val values by item.preference.collectAsState()
|
||||
MultiSelectListPreferenceWidget(
|
||||
preference = item,
|
||||
values = values,
|
||||
title = item.title,
|
||||
subtitle = item.internalSubtitleProvider(values, item.entries),
|
||||
icon = item.icon,
|
||||
entries = item.entries,
|
||||
onValuesChange = { newValues ->
|
||||
scope.launch {
|
||||
if (item.onValueChanged(newValues)) {
|
||||
item.preference.set(newValues.toMutableSet())
|
||||
if (item.internalOnValueChanged(newValues)) {
|
||||
item.internalSet(newValues)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -171,7 +174,7 @@ internal fun PreferenceItem(
|
||||
}
|
||||
TrackingPreferenceWidget(
|
||||
tracker = item.tracker,
|
||||
checked = isLoggedIn,
|
||||
isLoggedIn = isLoggedIn,
|
||||
onClick = { if (isLoggedIn) item.logout() else item.login() },
|
||||
)
|
||||
}
|
||||
|
||||
+9
-14
@@ -53,9 +53,6 @@ import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||
import eu.kanade.tachiyomi.util.system.powerManager
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import okhttp3.Headers
|
||||
@@ -140,7 +137,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_background_activity),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_disable_battery_optimization),
|
||||
subtitle = stringResource(MR.strings.pref_disable_battery_optimization_summary),
|
||||
@@ -179,7 +176,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_data),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_invalidate_download_cache),
|
||||
subtitle = stringResource(MR.strings.pref_invalidate_download_cache_summary),
|
||||
@@ -209,7 +206,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_network),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_clear_cookies),
|
||||
onClick = {
|
||||
@@ -239,7 +236,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = networkPreferences.dohProvider,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
-1 to stringResource(MR.strings.disabled),
|
||||
PREF_DOH_CLOUDFLARE to "Cloudflare",
|
||||
PREF_DOH_GOOGLE to "Google",
|
||||
@@ -296,7 +293,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_library),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_refresh_library_covers),
|
||||
onClick = { MetadataUpdateJob.startNow(context) },
|
||||
@@ -348,7 +345,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
}
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_reader),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = basePreferences.hardwareBitmapThreshold,
|
||||
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
|
||||
@@ -360,8 +357,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
}
|
||||
option to display
|
||||
}
|
||||
.toMap()
|
||||
.toImmutableMap(),
|
||||
.toMap(),
|
||||
title = stringResource(MR.strings.pref_hardware_bitmap_threshold),
|
||||
subtitleProvider = { value, options ->
|
||||
stringResource(MR.strings.pref_hardware_bitmap_threshold_summary, options[value].orEmpty())
|
||||
@@ -420,7 +416,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
}
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_extensions),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = extensionInstallerPref,
|
||||
entries = extensionInstallerPref.entries
|
||||
@@ -432,8 +428,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
true
|
||||
}
|
||||
}
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
.associateWith { stringResource(it.titleRes) },
|
||||
title = stringResource(MR.strings.ext_installer_pref),
|
||||
onValueChanged = {
|
||||
if (it == BasePreferences.ExtensionInstaller.SHIZUKU &&
|
||||
|
||||
+4
-8
@@ -19,8 +19,6 @@ import eu.kanade.presentation.more.settings.screen.appearance.AppLanguageScreen
|
||||
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
@@ -61,7 +59,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_theme),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.CustomPreference(
|
||||
title = stringResource(MR.strings.pref_app_theme),
|
||||
) {
|
||||
@@ -110,7 +108,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_display),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_app_language),
|
||||
onClick = { navigator.push(AppLanguageScreen()) },
|
||||
@@ -118,8 +116,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = uiPreferences.tabletUiMode,
|
||||
entries = TabletUiMode.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
.associateWith { stringResource(it.titleRes) },
|
||||
title = stringResource(MR.strings.pref_tablet_ui_mode),
|
||||
onValueChanged = {
|
||||
context.toast(MR.strings.requires_app_restart)
|
||||
@@ -132,8 +129,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
.associateWith {
|
||||
val formattedDate = UiPreferences.dateFormat(it).format(now)
|
||||
"${it.ifEmpty { stringResource(MR.strings.label_default) }} ($formattedDate)"
|
||||
}
|
||||
.toImmutableMap(),
|
||||
},
|
||||
title = stringResource(MR.strings.pref_date_format),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
|
||||
+9
-10
@@ -11,10 +11,9 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen
|
||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
|
||||
import mihon.domain.extension.interactor.GetExtensionStoreCountAsFlow
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
@@ -34,30 +33,30 @@ object SettingsBrowseScreen : SearchableSettings {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
|
||||
val getExtensionRepoCount = remember { Injekt.get<GetExtensionRepoCount>() }
|
||||
val getExtensionStoreCountAsFlow = remember { Injekt.get<GetExtensionStoreCountAsFlow>() }
|
||||
|
||||
val reposCount by getExtensionRepoCount.subscribe().collectAsState(0)
|
||||
val reposCount by getExtensionStoreCountAsFlow().collectAsState(0)
|
||||
|
||||
return listOf(
|
||||
Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_sources),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = sourcePreferences.hideInLibraryItems,
|
||||
title = stringResource(MR.strings.pref_hide_in_library_items),
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.label_extension_repos),
|
||||
subtitle = pluralStringResource(MR.plurals.num_repos, reposCount, reposCount),
|
||||
title = stringResource(MR.strings.extensionStores),
|
||||
subtitle = pluralStringResource(MR.plurals.num_repos, reposCount.toInt(), reposCount),
|
||||
onClick = {
|
||||
navigator.push(ExtensionReposScreen())
|
||||
navigator.push(ExtensionStoresScreen())
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_nsfw_content),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = sourcePreferences.showNsfwSource,
|
||||
title = stringResource(MR.strings.pref_show_nsfw_source),
|
||||
|
||||
@@ -56,8 +56,6 @@ import eu.kanade.tachiyomi.data.export.LibraryExporter
|
||||
import eu.kanade.tachiyomi.data.export.LibraryExporter.ExportOptions
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
@@ -103,7 +101,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||
val backupPreferences = Injekt.get<BackupPreferences>()
|
||||
val storagePreferences = Injekt.get<StoragePreferences>()
|
||||
|
||||
return persistentListOf(
|
||||
return listOf(
|
||||
getStorageLocationPref(storagePreferences = storagePreferences),
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)),
|
||||
|
||||
@@ -207,7 +205,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_backup),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
// Manual actions
|
||||
Preference.PreferenceItem.CustomPreference(
|
||||
title = stringResource(restorePreferenceKeyString),
|
||||
@@ -255,7 +253,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||
// Automatic backups
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = backupPreferences.backupInterval,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
0 to stringResource(MR.strings.off),
|
||||
6 to stringResource(MR.strings.update_6hour),
|
||||
12 to stringResource(MR.strings.update_12hour),
|
||||
@@ -289,7 +287,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_storage_usage),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.CustomPreference(
|
||||
title = stringResource(MR.strings.pref_storage_usage),
|
||||
) {
|
||||
@@ -382,7 +380,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.export),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.library_list),
|
||||
onClick = { showDialog = true },
|
||||
|
||||
+7
-12
@@ -12,9 +12,6 @@ import androidx.compose.ui.util.fastMap
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
@@ -85,14 +82,14 @@ object SettingsDownloadScreen : SearchableSettings {
|
||||
): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_delete_chapters),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = downloadPreferences.removeAfterMarkedAsRead,
|
||||
title = stringResource(MR.strings.pref_remove_after_marked_as_read),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = downloadPreferences.removeAfterReadSlots,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
-1 to stringResource(MR.strings.disabled),
|
||||
0 to stringResource(MR.strings.last_read_chapter),
|
||||
1 to stringResource(MR.strings.second_to_last),
|
||||
@@ -118,12 +115,11 @@ object SettingsDownloadScreen : SearchableSettings {
|
||||
private fun getExcludedCategoriesPreference(
|
||||
downloadPreferences: DownloadPreferences,
|
||||
categories: () -> List<Category>,
|
||||
): Preference.PreferenceItem.MultiSelectListPreference {
|
||||
): Preference.PreferenceItem.MultiSelectListPreference<String> {
|
||||
return Preference.PreferenceItem.MultiSelectListPreference(
|
||||
preference = downloadPreferences.removeExcludeCategories,
|
||||
entries = categories()
|
||||
.associate { it.id.toString() to it.visualName }
|
||||
.toImmutableMap(),
|
||||
.associate { it.id.toString() to it.visualName },
|
||||
title = stringResource(MR.strings.pref_remove_exclude_categories),
|
||||
)
|
||||
}
|
||||
@@ -162,7 +158,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_auto_download),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = downloadNewChaptersPref,
|
||||
title = stringResource(MR.strings.pref_download_new),
|
||||
@@ -192,7 +188,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
||||
): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.download_ahead),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = downloadPreferences.autoDownloadWhileReading,
|
||||
entries = listOf(0, 2, 3, 5, 10)
|
||||
@@ -202,8 +198,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
||||
} else {
|
||||
pluralStringResource(MR.plurals.next_unread_chapters, count = it, it)
|
||||
}
|
||||
}
|
||||
.toImmutableMap(),
|
||||
},
|
||||
title = stringResource(MR.strings.auto_download_while_reading),
|
||||
),
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.download_ahead_info)),
|
||||
|
||||
+10
-13
@@ -20,9 +20,6 @@ import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
||||
@@ -80,7 +77,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.categories),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.action_edit_categories),
|
||||
subtitle = pluralStringResource(
|
||||
@@ -92,7 +89,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = libraryPreferences.defaultCategory,
|
||||
entries = ids.zip(labels).toMap().toImmutableMap(),
|
||||
entries = ids.zip(labels).toMap(),
|
||||
title = stringResource(MR.strings.default_category),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
@@ -146,10 +143,10 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_library_update),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = autoUpdateIntervalPref,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
0 to stringResource(MR.strings.update_never),
|
||||
12 to stringResource(MR.strings.update_12hour),
|
||||
24 to stringResource(MR.strings.update_24hour),
|
||||
@@ -165,7 +162,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
),
|
||||
Preference.PreferenceItem.MultiSelectListPreference(
|
||||
preference = libraryPreferences.autoUpdateDeviceRestrictions,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
|
||||
DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
|
||||
DEVICE_CHARGING to stringResource(MR.strings.charging),
|
||||
@@ -195,7 +192,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
),
|
||||
Preference.PreferenceItem.MultiSelectListPreference(
|
||||
preference = libraryPreferences.autoUpdateMangaRestrictions,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
|
||||
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
|
||||
MANGA_NON_COMPLETED to stringResource(MR.strings.pref_update_only_non_completed),
|
||||
@@ -217,10 +214,10 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_behavior),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = libraryPreferences.swipeToStartAction,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
LibraryPreferences.ChapterSwipeAction.Disabled to
|
||||
stringResource(MR.strings.disabled),
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
|
||||
@@ -234,7 +231,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = libraryPreferences.swipeToEndAction,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
LibraryPreferences.ChapterSwipeAction.Disabled to
|
||||
stringResource(MR.strings.disabled),
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
|
||||
@@ -248,7 +245,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
),
|
||||
Preference.PreferenceItem.MultiSelectListPreference(
|
||||
preference = libraryPreferences.markDuplicateReadChapterAsRead,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
MARK_DUPLICATE_CHAPTER_READ_EXISTING to
|
||||
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_existing),
|
||||
MARK_DUPLICATE_CHAPTER_READ_NEW to
|
||||
|
||||
@@ -44,7 +44,6 @@ import eu.kanade.presentation.more.settings.screen.about.AboutScreen
|
||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.presentation.util.LocalBackPress
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -88,7 +87,7 @@ object SettingsMainScreen : Screen() {
|
||||
navigateUp = backPress::invoke,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_search),
|
||||
icon = Icons.Outlined.Search,
|
||||
|
||||
+47
-35
@@ -10,9 +10,6 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -35,13 +32,12 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = readerPref.defaultReadingMode,
|
||||
entries = ReadingMode.entries.drop(1)
|
||||
.associate { it.flagValue to stringResource(it.stringRes) }
|
||||
.toImmutableMap(),
|
||||
.associate { it.flagValue to stringResource(it.stringRes) },
|
||||
title = stringResource(MR.strings.pref_viewer_type),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = readerPref.doubleTapAnimSpeed,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
1 to stringResource(MR.strings.double_tap_anim_speed_0),
|
||||
500 to stringResource(MR.strings.double_tap_anim_speed_normal),
|
||||
250 to stringResource(MR.strings.double_tap_anim_speed_fast),
|
||||
@@ -74,21 +70,19 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
|
||||
@Composable
|
||||
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
val fullscreenPref = readerPreferences.fullscreen
|
||||
val fullscreen by fullscreenPref.collectAsState()
|
||||
val fullscreen by readerPreferences.fullscreen.collectAsState()
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_display),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = readerPreferences.defaultOrientationType,
|
||||
entries = ReaderOrientation.entries.drop(1)
|
||||
.associate { it.flagValue to stringResource(it.stringRes) }
|
||||
.toImmutableMap(),
|
||||
.associate { it.flagValue to stringResource(it.stringRes) },
|
||||
title = stringResource(MR.strings.pref_rotation_type),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = readerPreferences.readerTheme,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
1 to stringResource(MR.strings.black_background),
|
||||
2 to stringResource(MR.strings.gray_background),
|
||||
0 to stringResource(MR.strings.white_background),
|
||||
@@ -97,7 +91,7 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
title = stringResource(MR.strings.pref_reader_theme),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = fullscreenPref,
|
||||
preference = readerPreferences.fullscreen,
|
||||
title = stringResource(MR.strings.pref_fullscreen),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
@@ -131,7 +125,7 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = "E-Ink",
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = readerPreferences.flashOnPageChange,
|
||||
title = stringResource(MR.strings.pref_flash_page),
|
||||
@@ -155,7 +149,7 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = flashColorPref,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
|
||||
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
|
||||
ReaderPreferences.FlashColor.WHITE_BLACK
|
||||
@@ -172,7 +166,7 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_reading),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = readerPreferences.skipRead,
|
||||
title = stringResource(MR.strings.pref_skip_read_chapters),
|
||||
@@ -207,25 +201,23 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pager_viewer),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = navModePref,
|
||||
entries = ReaderPreferences.TapZones
|
||||
.mapIndexed { index, it -> index to stringResource(it) }
|
||||
.toMap()
|
||||
.toImmutableMap(),
|
||||
.toMap(),
|
||||
title = stringResource(MR.strings.pref_viewer_nav),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = readerPreferences.pagerNavInverted,
|
||||
entries = persistentListOf(
|
||||
entries = listOf(
|
||||
ReaderPreferences.TappingInvertMode.NONE,
|
||||
ReaderPreferences.TappingInvertMode.HORIZONTAL,
|
||||
ReaderPreferences.TappingInvertMode.VERTICAL,
|
||||
ReaderPreferences.TappingInvertMode.BOTH,
|
||||
)
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
.associateWith { stringResource(it.titleRes) },
|
||||
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
|
||||
enabled = navMode != 5,
|
||||
),
|
||||
@@ -233,16 +225,14 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
preference = imageScaleTypePref,
|
||||
entries = ReaderPreferences.ImageScaleType
|
||||
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
||||
.toMap()
|
||||
.toImmutableMap(),
|
||||
.toMap(),
|
||||
title = stringResource(MR.strings.pref_image_scale_type),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = readerPreferences.zoomStart,
|
||||
entries = ReaderPreferences.ZoomStart
|
||||
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
||||
.toMap()
|
||||
.toImmutableMap(),
|
||||
.toMap(),
|
||||
title = stringResource(MR.strings.pref_zoom_start),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
@@ -306,25 +296,23 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.webtoon_viewer),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = navModePref,
|
||||
entries = ReaderPreferences.TapZones
|
||||
.mapIndexed { index, it -> index to stringResource(it) }
|
||||
.toMap()
|
||||
.toImmutableMap(),
|
||||
.toMap(),
|
||||
title = stringResource(MR.strings.pref_viewer_nav),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = readerPreferences.webtoonNavInverted,
|
||||
entries = persistentListOf(
|
||||
entries = listOf(
|
||||
ReaderPreferences.TappingInvertMode.NONE,
|
||||
ReaderPreferences.TappingInvertMode.HORIZONTAL,
|
||||
ReaderPreferences.TappingInvertMode.VERTICAL,
|
||||
ReaderPreferences.TappingInvertMode.BOTH,
|
||||
)
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
.associateWith { stringResource(it.titleRes) },
|
||||
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
|
||||
enabled = navMode != 5,
|
||||
),
|
||||
@@ -339,7 +327,7 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = readerPreferences.readerHideThreshold,
|
||||
entries = persistentMapOf(
|
||||
entries = mapOf(
|
||||
ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest),
|
||||
ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high),
|
||||
ReaderPreferences.ReaderHideThreshold.LOW to stringResource(MR.strings.pref_low),
|
||||
@@ -394,9 +382,14 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
private fun getNavigationGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys
|
||||
val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState()
|
||||
|
||||
val verticalNavigator by readerPreferences.verticalNavigator.collectAsState()
|
||||
val verticalNavigatorHeightPref = readerPreferences.verticalNavigatorHeight
|
||||
val verticalNavigatorHeight by verticalNavigatorHeightPref.collectAsState()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_reader_navigation),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = readWithVolumeKeysPref,
|
||||
title = stringResource(MR.strings.pref_read_with_volume_keys),
|
||||
@@ -406,6 +399,25 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
title = stringResource(MR.strings.pref_read_with_volume_keys_inverted),
|
||||
enabled = readWithVolumeKeys,
|
||||
),
|
||||
Preference.PreferenceItem.MultiSelectListPreference(
|
||||
preference = readerPreferences.verticalNavigator,
|
||||
entries = ReadingMode.entries.filter { it != ReadingMode.DEFAULT }
|
||||
.associate { it to stringResource(it.stringRes) },
|
||||
title = stringResource(MR.strings.pref_vertical_navigator),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = readerPreferences.verticalNavigatorOnLeft,
|
||||
title = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
|
||||
enabled = verticalNavigator.isNotEmpty(),
|
||||
),
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = verticalNavigatorHeight,
|
||||
valueRange = 65..100,
|
||||
steps = 6,
|
||||
title = stringResource(MR.strings.pref_vertical_navigator_height),
|
||||
onValueChanged = { verticalNavigatorHeightPref.set(it) },
|
||||
enabled = verticalNavigator.isNotEmpty(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -414,7 +426,7 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
private fun getActionsGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_reader_actions),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = readerPreferences.readWithLongTap,
|
||||
title = stringResource(MR.strings.pref_read_with_long_tap),
|
||||
|
||||
+5
-9
@@ -12,8 +12,6 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
||||
import eu.kanade.tachiyomi.util.system.telemetryIncluded
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
@@ -50,7 +48,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_security),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = useAuthPref,
|
||||
title = stringResource(MR.strings.lock_with_biometrics),
|
||||
@@ -70,8 +68,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
0 -> stringResource(MR.strings.lock_always)
|
||||
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
|
||||
}
|
||||
}
|
||||
.toImmutableMap(),
|
||||
},
|
||||
title = stringResource(MR.strings.lock_when_idle),
|
||||
enabled = authSupported && useAuth,
|
||||
onValueChanged = {
|
||||
@@ -88,8 +85,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = securityPreferences.secureScreen,
|
||||
entries = SecurityPreferences.SecureScreenMode.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
.associateWith { stringResource(it.titleRes) },
|
||||
title = stringResource(MR.strings.secure_screen),
|
||||
),
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
|
||||
@@ -103,7 +99,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_firebase),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = privacyPreferences.crashlytics,
|
||||
title = stringResource(MR.strings.onboarding_permission_crashlytics),
|
||||
@@ -120,7 +116,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
}
|
||||
}
|
||||
|
||||
private val LockAfterValues = persistentListOf(
|
||||
private val LockAfterValues = listOf(
|
||||
0, // Always
|
||||
1,
|
||||
2,
|
||||
|
||||
+16
-9
@@ -51,13 +51,12 @@ import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
|
||||
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
|
||||
import eu.kanade.tachiyomi.data.track.hikka.HikkaApi
|
||||
import eu.kanade.tachiyomi.data.track.mangabaka.MangaBakaApi
|
||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
|
||||
import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
@@ -90,7 +89,6 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
|
||||
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
||||
val sourceManager = remember { Injekt.get<SourceManager>() }
|
||||
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead
|
||||
|
||||
var dialog by remember { mutableStateOf<Any?>(null) }
|
||||
dialog?.run {
|
||||
@@ -115,7 +113,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
.filter { it is EnhancedTracker }
|
||||
.partition { service ->
|
||||
val acceptedSources = (service as EnhancedTracker).getAcceptedSources()
|
||||
sourceManager.getCatalogueSources().any { it::class.qualifiedName in acceptedSources }
|
||||
sourceManager.getAll().any { it::class.qualifiedName in acceptedSources }
|
||||
}
|
||||
var enhancedTrackerInfo = stringResource(MR.strings.enhanced_tracking_info)
|
||||
if (enhancedTrackers.second.isNotEmpty()) {
|
||||
@@ -134,13 +132,17 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = trackPreferences.autoUpdateTrackOnMarkRead,
|
||||
entries = AutoTrackState.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toPersistentMap(),
|
||||
.associateWith { stringResource(it.titleRes) },
|
||||
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
|
||||
),
|
||||
Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.services),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TrackerPreference(
|
||||
tracker = trackerManager.mangaBaka,
|
||||
login = { context.openInBrowser(MangaBakaApi.authUrl(), forceDefaultBrowser = true) },
|
||||
logout = { dialog = LogoutDialog(trackerManager.mangaBaka) },
|
||||
),
|
||||
Preference.PreferenceItem.TrackerPreference(
|
||||
tracker = trackerManager.myAnimeList,
|
||||
login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) },
|
||||
@@ -171,6 +173,11 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
login = { context.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true) },
|
||||
logout = { dialog = LogoutDialog(trackerManager.bangumi) },
|
||||
),
|
||||
Preference.PreferenceItem.TrackerPreference(
|
||||
tracker = trackerManager.hikka,
|
||||
login = { context.openInBrowser(HikkaApi.authUrl(), forceDefaultBrowser = true) },
|
||||
logout = { dialog = LogoutDialog(trackerManager.hikka) },
|
||||
),
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.tracking_info)),
|
||||
),
|
||||
),
|
||||
@@ -185,7 +192,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
logout = service::logout,
|
||||
)
|
||||
} + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo))
|
||||
).toImmutableList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package eu.kanade.presentation.more.settings.screen.about
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -40,6 +41,7 @@ import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.system.updaterEnabled
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.Constants
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
@@ -85,7 +87,9 @@ object AboutScreen : Screen() {
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
item {
|
||||
LogoHeader()
|
||||
LogoHeader(
|
||||
iconPadding = PaddingValues(vertical = 56.dp),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
@@ -176,7 +180,7 @@ object AboutScreen : Screen() {
|
||||
LinkIcon(
|
||||
label = "Discord",
|
||||
icon = CustomIcons.Discord,
|
||||
url = "https://discord.gg/mihon",
|
||||
url = Constants.URL_DISCORD,
|
||||
)
|
||||
LinkIcon(
|
||||
label = "X",
|
||||
@@ -215,7 +219,7 @@ object AboutScreen : Screen() {
|
||||
val updateChecker = AppUpdateChecker()
|
||||
withUIContext {
|
||||
try {
|
||||
when (val result = withIOContext { updateChecker.checkForUpdate(context, forceCheck = true) }) {
|
||||
when (val result = withIOContext { updateChecker.checkForUpdate(forceCheck = true) }) {
|
||||
is GetApplicationRelease.Result.NewUpdate -> {
|
||||
onAvailableUpdate(result)
|
||||
}
|
||||
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
package eu.kanade.presentation.more.settings.screen.about
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Public
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.text.HtmlCompat
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
class OpenSourceLibraryLicenseScreen(
|
||||
private val name: String,
|
||||
private val website: String?,
|
||||
private val license: String,
|
||||
) : Screen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = name,
|
||||
navigateUp = navigator::pop,
|
||||
actions = {
|
||||
if (!website.isNullOrEmpty()) {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.website),
|
||||
icon = Icons.Default.Public,
|
||||
onClick = { uriHandler.openUri(website) },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
scrollBehavior = it,
|
||||
)
|
||||
},
|
||||
) { contentPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(contentPadding)
|
||||
.padding(16.dp),
|
||||
) {
|
||||
HtmlLicenseText(html = license)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HtmlLicenseText(html: String) {
|
||||
AndroidView(
|
||||
factory = {
|
||||
MaterialTextView(it)
|
||||
},
|
||||
update = {
|
||||
it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+2
-10
@@ -8,7 +8,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
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.util.htmlReadyLicenseContent
|
||||
import com.mikepenz.aboutlibraries.ui.compose.variant.LibraryDetailMode
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.R
|
||||
@@ -36,15 +36,7 @@ class OpenSourceLicensesScreen : Screen() {
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentPadding = contentPadding,
|
||||
onLibraryClick = {
|
||||
navigator.push(
|
||||
OpenSourceLibraryLicenseScreen(
|
||||
name = it.name,
|
||||
website = it.website,
|
||||
license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
|
||||
),
|
||||
)
|
||||
},
|
||||
detailMode = LibraryDetailMode.Sheet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+19
-20
@@ -30,9 +30,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastMap
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.browse.components.SourceIcon
|
||||
@@ -40,9 +39,9 @@ import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import mihon.core.viewmodel.StateViewModel
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.launchUI
|
||||
import tachiyomi.core.common.util.lang.toLong
|
||||
@@ -68,13 +67,13 @@ class ClearDatabaseScreen : Screen() {
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val model = rememberScreenModel { ClearDatabaseScreenModel() }
|
||||
val state by model.state.collectAsState()
|
||||
val viewModel = viewModel<ClearDatabaseViewModel>()
|
||||
val state by viewModel.state.collectAsState()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
when (val s = state) {
|
||||
is ClearDatabaseScreenModel.State.Loading -> LoadingScreen()
|
||||
is ClearDatabaseScreenModel.State.Ready -> {
|
||||
is ClearDatabaseViewModel.State.Loading -> LoadingScreen()
|
||||
is ClearDatabaseViewModel.State.Ready -> {
|
||||
if (s.showConfirmation) {
|
||||
var keepReadManga by remember { mutableStateOf(true) }
|
||||
AlertDialog(
|
||||
@@ -109,14 +108,14 @@ class ClearDatabaseScreen : Screen() {
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismissRequest = model::hideConfirmation,
|
||||
onDismissRequest = viewModel::hideConfirmation,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
scope.launchUI {
|
||||
model.removeMangaBySourceId(keepReadManga)
|
||||
model.clearSelection()
|
||||
model.hideConfirmation()
|
||||
viewModel.removeMangaBySourceId(keepReadManga)
|
||||
viewModel.clearSelection()
|
||||
viewModel.hideConfirmation()
|
||||
context.toast(MR.strings.clear_database_completed)
|
||||
}
|
||||
},
|
||||
@@ -125,7 +124,7 @@ class ClearDatabaseScreen : Screen() {
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = model::hideConfirmation) {
|
||||
TextButton(onClick = viewModel::hideConfirmation) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
@@ -140,16 +139,16 @@ class ClearDatabaseScreen : Screen() {
|
||||
actions = {
|
||||
if (s.items.isNotEmpty()) {
|
||||
AppBarActions(
|
||||
actions = persistentListOf(
|
||||
actions = listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_select_all),
|
||||
icon = Icons.Outlined.SelectAll,
|
||||
onClick = model::selectAll,
|
||||
onClick = viewModel::selectAll,
|
||||
),
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_select_inverse),
|
||||
icon = Icons.Outlined.FlipToBack,
|
||||
onClick = model::invertSelection,
|
||||
onClick = viewModel::invertSelection,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -169,14 +168,14 @@ class ClearDatabaseScreen : Screen() {
|
||||
contentPadding = contentPadding,
|
||||
actionLabel = stringResource(MR.strings.action_delete),
|
||||
actionEnabled = s.selection.isNotEmpty(),
|
||||
onClickAction = model::showConfirmation,
|
||||
onClickAction = viewModel::showConfirmation,
|
||||
) {
|
||||
items(s.items) { sourceWithCount ->
|
||||
ClearDatabaseItem(
|
||||
source = sourceWithCount.source,
|
||||
count = sourceWithCount.count,
|
||||
isSelected = s.selection.contains(sourceWithCount.id),
|
||||
onClickSelect = { model.toggleSelection(sourceWithCount.source) },
|
||||
onClickSelect = { viewModel.toggleSelection(sourceWithCount.source) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -221,12 +220,12 @@ class ClearDatabaseScreen : Screen() {
|
||||
}
|
||||
}
|
||||
|
||||
private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenModel.State>(State.Loading) {
|
||||
class ClearDatabaseViewModel : StateViewModel<ClearDatabaseViewModel.State>(State.Loading) {
|
||||
private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga = Injekt.get()
|
||||
private val database: Database = Injekt.get()
|
||||
|
||||
init {
|
||||
screenModelScope.launchIO {
|
||||
viewModelScope.launchIO {
|
||||
getSourcesWithNonLibraryManga.subscribe()
|
||||
.collectLatest { list ->
|
||||
mutableState.update { old ->
|
||||
|
||||
+10
-15
@@ -27,8 +27,6 @@ import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -73,12 +71,6 @@ class AppLanguageScreen : Screen() {
|
||||
modifier = Modifier.clickable {
|
||||
currentLanguage = it.langTag
|
||||
},
|
||||
headlineContent = { Text(it.displayName) },
|
||||
supportingContent = {
|
||||
it.localizedDisplayName?.let {
|
||||
Text(it)
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
if (currentLanguage == it.langTag) {
|
||||
Icon(
|
||||
@@ -88,14 +80,19 @@ class AppLanguageScreen : Screen() {
|
||||
)
|
||||
}
|
||||
},
|
||||
supportingContent = {
|
||||
it.localizedDisplayName?.let {
|
||||
Text(it)
|
||||
}
|
||||
},
|
||||
content = { Text(it.displayName) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLangs(context: Context): ImmutableList<Language> {
|
||||
val langs = mutableListOf<Language>()
|
||||
private fun getLangs(context: Context): List<Language> = buildList {
|
||||
val parser = context.resources.getXml(R.xml.locales_config)
|
||||
var eventType = parser.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
@@ -105,7 +102,7 @@ class AppLanguageScreen : Screen() {
|
||||
val langTag = parser.getAttributeValue(i)
|
||||
val displayName = LocaleHelper.getLocalizedDisplayName(langTag)
|
||||
if (displayName.isNotEmpty()) {
|
||||
langs.add(Language(langTag, displayName, LocaleHelper.getDisplayName(langTag)))
|
||||
add(Language(langTag, displayName, LocaleHelper.getDisplayName(langTag)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,10 +110,8 @@ class AppLanguageScreen : Screen() {
|
||||
eventType = parser.next()
|
||||
}
|
||||
|
||||
langs.sortBy { it.displayName }
|
||||
langs.add(0, Language("", context.stringResource(MR.strings.label_default), null))
|
||||
|
||||
return langs.toImmutableList()
|
||||
sortBy { it.displayName }
|
||||
add(0, Language("", context.stringResource(MR.strings.label_default), null))
|
||||
}
|
||||
|
||||
private data class Language(
|
||||
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
package eu.kanade.presentation.more.settings.screen.browse
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConfirmDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConflictDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionReposScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
|
||||
class ExtensionReposScreen(
|
||||
private val url: String? = null,
|
||||
) : Screen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val screenModel = rememberScreenModel { ExtensionReposScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
LaunchedEffect(url) {
|
||||
url?.let { screenModel.showDialog(RepoDialog.Confirm(it)) }
|
||||
}
|
||||
|
||||
if (state is RepoScreenState.Loading) {
|
||||
LoadingScreen()
|
||||
return
|
||||
}
|
||||
|
||||
val successState = state as RepoScreenState.Success
|
||||
|
||||
ExtensionReposScreen(
|
||||
state = successState,
|
||||
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
|
||||
onOpenWebsite = { context.openInBrowser(it.website) },
|
||||
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
|
||||
onClickRefresh = { screenModel.refreshRepos() },
|
||||
navigateUp = navigator::pop,
|
||||
)
|
||||
|
||||
when (val dialog = successState.dialog) {
|
||||
null -> {}
|
||||
is RepoDialog.Create -> {
|
||||
ExtensionRepoCreateDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onCreate = { screenModel.createRepo(it) },
|
||||
repoUrls = successState.repos.map { it.baseUrl }.toImmutableSet(),
|
||||
)
|
||||
}
|
||||
is RepoDialog.Delete -> {
|
||||
ExtensionRepoDeleteDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onDelete = { screenModel.deleteRepo(dialog.repo) },
|
||||
repo = dialog.repo,
|
||||
)
|
||||
}
|
||||
is RepoDialog.Conflict -> {
|
||||
ExtensionRepoConflictDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onMigrate = { screenModel.replaceRepo(dialog.newRepo) },
|
||||
oldRepo = dialog.oldRepo,
|
||||
newRepo = dialog.newRepo,
|
||||
)
|
||||
}
|
||||
is RepoDialog.Confirm -> {
|
||||
ExtensionRepoConfirmDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onCreate = { screenModel.createRepo(dialog.url) },
|
||||
repo = dialog.url,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
screenModel.events.collectLatest { event ->
|
||||
if (event is RepoEvent.LocalizedMessage) {
|
||||
context.toast(event.stringRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
package eu.kanade.presentation.more.settings.screen.browse
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
||||
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ExtensionReposScreenModel(
|
||||
private val getExtensionRepo: GetExtensionRepo = Injekt.get(),
|
||||
private val createExtensionRepo: CreateExtensionRepo = Injekt.get(),
|
||||
private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(),
|
||||
private val replaceExtensionRepo: ReplaceExtensionRepo = Injekt.get(),
|
||||
private val updateExtensionRepo: UpdateExtensionRepo = Injekt.get(),
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) {
|
||||
|
||||
private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE)
|
||||
val events = _events.receiveAsFlow()
|
||||
|
||||
init {
|
||||
screenModelScope.launchIO {
|
||||
getExtensionRepo.subscribeAll()
|
||||
.collectLatest { repos ->
|
||||
mutableState.update {
|
||||
RepoScreenState.Success(
|
||||
repos = repos.toImmutableSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a new repo to the database.
|
||||
*
|
||||
* @param baseUrl The baseUrl of the repo to create.
|
||||
*/
|
||||
fun createRepo(baseUrl: String) {
|
||||
screenModelScope.launchIO {
|
||||
when (val result = createExtensionRepo.await(baseUrl)) {
|
||||
CreateExtensionRepo.Result.Success -> extensionManager.findAvailableExtensions()
|
||||
CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
|
||||
CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists)
|
||||
is CreateExtensionRepo.Result.DuplicateFingerprint -> {
|
||||
showDialog(RepoDialog.Conflict(result.oldRepo, result.newRepo))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a repo to the database, replace a matching repo with the same signing key fingerprint if found.
|
||||
*
|
||||
* @param newRepo The repo to insert
|
||||
*/
|
||||
fun replaceRepo(newRepo: ExtensionRepo) {
|
||||
screenModelScope.launchIO {
|
||||
replaceExtensionRepo.await(newRepo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes information for each repository.
|
||||
*/
|
||||
fun refreshRepos() {
|
||||
val status = state.value
|
||||
|
||||
if (status is RepoScreenState.Success) {
|
||||
screenModelScope.launchIO {
|
||||
updateExtensionRepo.awaitAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given repo from the database
|
||||
*/
|
||||
fun deleteRepo(baseUrl: String) {
|
||||
screenModelScope.launchIO {
|
||||
deleteExtensionRepo.await(baseUrl)
|
||||
extensionManager.findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
|
||||
fun showDialog(dialog: RepoDialog) {
|
||||
mutableState.update {
|
||||
when (it) {
|
||||
RepoScreenState.Loading -> it
|
||||
is RepoScreenState.Success -> it.copy(dialog = dialog)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissDialog() {
|
||||
mutableState.update {
|
||||
when (it) {
|
||||
RepoScreenState.Loading -> it
|
||||
is RepoScreenState.Success -> it.copy(dialog = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class RepoEvent {
|
||||
sealed class LocalizedMessage(val stringRes: StringResource) : RepoEvent()
|
||||
data object InvalidUrl : LocalizedMessage(MR.strings.invalid_repo_name)
|
||||
data object RepoAlreadyExists : LocalizedMessage(MR.strings.error_repo_exists)
|
||||
}
|
||||
|
||||
sealed class RepoDialog {
|
||||
data object Create : RepoDialog()
|
||||
data class Delete(val repo: String) : RepoDialog()
|
||||
data class Conflict(val oldRepo: ExtensionRepo, val newRepo: ExtensionRepo) : RepoDialog()
|
||||
data class Confirm(val url: String) : RepoDialog()
|
||||
}
|
||||
|
||||
sealed class RepoScreenState {
|
||||
|
||||
@Immutable
|
||||
data object Loading : RepoScreenState()
|
||||
|
||||
@Immutable
|
||||
data class Success(
|
||||
val repos: ImmutableSet<ExtensionRepo>,
|
||||
val oldRepos: ImmutableSet<String>? = null,
|
||||
val dialog: RepoDialog? = null,
|
||||
) : RepoScreenState() {
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = repos.isEmpty()
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package eu.kanade.presentation.more.settings.screen.browse
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreConfirmDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreCreateDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreDeleteDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoresScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
|
||||
class ExtensionStoresScreen(
|
||||
private val url: String? = null,
|
||||
) : Screen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val viewModel = viewModel<ExtensionStoresViewModel>()
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
LaunchedEffect(url) {
|
||||
url?.let { viewModel.addFromDeeplink(url) }
|
||||
}
|
||||
|
||||
if (state is ExtensionStoreScreenState.Loading) {
|
||||
LoadingScreen()
|
||||
return
|
||||
}
|
||||
|
||||
val successState = state as ExtensionStoreScreenState.Success
|
||||
|
||||
ExtensionStoresScreen(
|
||||
state = successState,
|
||||
onClickCreate = { viewModel.showDialog(ExtensionStoreDialog.Create()) },
|
||||
onCopy = { context.copyToClipboard(it.indexUrl, it.indexUrl) },
|
||||
onOpenWebsite = { it.contact.website.let(context::openInBrowser) },
|
||||
onOpenDiscord = { it.contact.discord?.let(context::openInBrowser) },
|
||||
onClickDelete = { viewModel.showDialog(ExtensionStoreDialog.Delete(it)) },
|
||||
onClickRefresh = { viewModel.refreshRepos() },
|
||||
navigateUp = navigator::pop,
|
||||
)
|
||||
|
||||
when (val dialog = successState.dialog) {
|
||||
null -> {}
|
||||
is ExtensionStoreDialog.Create -> {
|
||||
ExtensionStoreCreateDialog(
|
||||
onDismissRequest = viewModel::dismissDialog,
|
||||
onCreate = { viewModel.createRepo(it) },
|
||||
storeIndexUrls = successState.stores.map { it.indexUrl }.toSet(),
|
||||
processing = dialog.processing,
|
||||
errorMessage = dialog.errorMessage,
|
||||
)
|
||||
}
|
||||
is ExtensionStoreDialog.Delete -> {
|
||||
ExtensionStoreDeleteDialog(
|
||||
onDismissRequest = viewModel::dismissDialog,
|
||||
onDelete = { viewModel.deleteRepo(dialog.store.indexUrl) },
|
||||
storeName = dialog.store.name,
|
||||
storeIndexUrl = dialog.store.indexUrl,
|
||||
)
|
||||
}
|
||||
is ExtensionStoreDialog.Confirm -> {
|
||||
ExtensionStoreConfirmDialog(
|
||||
onDismissRequest = viewModel::dismissDialog,
|
||||
onCreate = { viewModel.createRepo(dialog.url) },
|
||||
storeIndexUrl = dialog.url,
|
||||
storeAlreadyExists = dialog.alreadyExists,
|
||||
processing = dialog.processing,
|
||||
errorMessage = dialog.errorMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package eu.kanade.presentation.more.settings.screen.browse
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import mihon.core.viewmodel.StateViewModel
|
||||
import mihon.domain.extension.interactor.AddExtensionStore
|
||||
import mihon.domain.extension.interactor.GetExtensionStores
|
||||
import mihon.domain.extension.interactor.RemoveExtensionStore
|
||||
import mihon.domain.extension.interactor.UpdateExtensionStores
|
||||
import mihon.domain.extension.model.ExtensionStore
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ExtensionStoresViewModel(
|
||||
private val getExtensionStores: GetExtensionStores = Injekt.get(),
|
||||
private val addExtensionStore: AddExtensionStore = Injekt.get(),
|
||||
private val removeExtensionStore: RemoveExtensionStore = Injekt.get(),
|
||||
private val updateExtensionStores: UpdateExtensionStores = Injekt.get(),
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
) : StateViewModel<ExtensionStoreScreenState>(ExtensionStoreScreenState.Loading) {
|
||||
|
||||
private inline fun updateSuccessState(
|
||||
func: (ExtensionStoreScreenState.Success) -> ExtensionStoreScreenState.Success,
|
||||
) {
|
||||
mutableState.update {
|
||||
when (it) {
|
||||
ExtensionStoreScreenState.Loading -> it
|
||||
is ExtensionStoreScreenState.Success -> func(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
getExtensionStores.subscribe()
|
||||
.collectLatest { stores ->
|
||||
mutableState.update {
|
||||
when (it) {
|
||||
ExtensionStoreScreenState.Loading -> ExtensionStoreScreenState.Success(stores = stores)
|
||||
is ExtensionStoreScreenState.Success -> it.copy(stores = stores)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a new repo to the database.
|
||||
*
|
||||
* @param baseUrl The baseUrl of the repo to create.
|
||||
*/
|
||||
fun createRepo(baseUrl: String) {
|
||||
viewModelScope.launch {
|
||||
updateSuccessState {
|
||||
it.copy(
|
||||
dialog = when (it.dialog) {
|
||||
is ExtensionStoreDialog.Create -> it.dialog.copy(processing = true)
|
||||
is ExtensionStoreDialog.Confirm -> it.dialog.copy(processing = true)
|
||||
else -> it.dialog
|
||||
},
|
||||
)
|
||||
}
|
||||
addExtensionStore(baseUrl)
|
||||
.onSuccess {
|
||||
extensionManager.findAvailableExtensions()
|
||||
dismissDialog()
|
||||
}
|
||||
.onFailure { throwable ->
|
||||
updateSuccessState {
|
||||
it.copy(
|
||||
dialog = when (it.dialog) {
|
||||
is ExtensionStoreDialog.Create -> it.dialog.copy(
|
||||
processing = false,
|
||||
errorMessage = throwable.message ?: "unknown error",
|
||||
)
|
||||
is ExtensionStoreDialog.Confirm -> it.dialog.copy(
|
||||
processing = false,
|
||||
errorMessage = throwable.message ?: "unknown error",
|
||||
)
|
||||
else -> it.dialog
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes information for each repository.
|
||||
*/
|
||||
fun refreshRepos() {
|
||||
val status = state.value
|
||||
|
||||
if (status is ExtensionStoreScreenState.Success) {
|
||||
viewModelScope.launchIO {
|
||||
updateExtensionStores()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given repo from the database
|
||||
*/
|
||||
fun deleteRepo(baseUrl: String) {
|
||||
viewModelScope.launchIO {
|
||||
removeExtensionStore(baseUrl)
|
||||
extensionManager.findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
|
||||
fun addFromDeeplink(storeIndexUrl: String) {
|
||||
updateSuccessState { state ->
|
||||
state.copy(
|
||||
dialog = ExtensionStoreDialog.Confirm(
|
||||
url = storeIndexUrl,
|
||||
alreadyExists = state.stores.any { it.indexUrl == storeIndexUrl },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun showDialog(dialog: ExtensionStoreDialog) {
|
||||
updateSuccessState { state ->
|
||||
state.copy(dialog = dialog)
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissDialog() {
|
||||
updateSuccessState {
|
||||
it.copy(dialog = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ExtensionStoreDialog {
|
||||
data class Create(val processing: Boolean = false, val errorMessage: String? = null) : ExtensionStoreDialog()
|
||||
data class Delete(val store: ExtensionStore) : ExtensionStoreDialog()
|
||||
data class Confirm(
|
||||
val url: String,
|
||||
val alreadyExists: Boolean = false,
|
||||
val processing: Boolean = false,
|
||||
val errorMessage: String? = null,
|
||||
) : ExtensionStoreDialog()
|
||||
}
|
||||
|
||||
sealed class ExtensionStoreScreenState {
|
||||
|
||||
@Immutable
|
||||
data object Loading : ExtensionStoreScreenState()
|
||||
|
||||
@Immutable
|
||||
data class Success(
|
||||
val stores: List<ExtensionStore>,
|
||||
val dialog: ExtensionStoreDialog? = null,
|
||||
) : ExtensionStoreScreenState() {
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = stores.isEmpty()
|
||||
}
|
||||
}
|
||||
-188
@@ -1,188 +0,0 @@
|
||||
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.coroutines.delay
|
||||
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
fun ExtensionRepoCreateDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: (String) -> Unit,
|
||||
repoUrls: ImmutableSet<String>,
|
||||
) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val nameAlreadyExists = remember(name) { repoUrls.contains(name) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = name.isNotEmpty() && !nameAlreadyExists,
|
||||
onClick = {
|
||||
onCreate(name)
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(MR.strings.action_add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.action_add_repo))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Text(text = stringResource(MR.strings.action_add_repo_message, stringResource(MR.strings.app_name)))
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = {
|
||||
Text(text = stringResource(MR.strings.label_add_repo_input))
|
||||
},
|
||||
supportingText = {
|
||||
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) {
|
||||
MR.strings.error_repo_exists
|
||||
} else {
|
||||
MR.strings.information_required_plain
|
||||
}
|
||||
Text(text = stringResource(msgRes))
|
||||
},
|
||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
LaunchedEffect(focusRequester) {
|
||||
// TODO: https://issuetracker.google.com/issues/204502668
|
||||
delay(0.1.seconds)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExtensionRepoDeleteDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
repo: String,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onDelete()
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.action_delete_repo))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(MR.strings.delete_repo_confirmation, repo))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExtensionRepoConflictDialog(
|
||||
oldRepo: ExtensionRepo,
|
||||
newRepo: ExtensionRepo,
|
||||
onDismissRequest: () -> Unit,
|
||||
onMigrate: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onMigrate()
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(MR.strings.action_replace_repo))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.action_replace_repo_title))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(MR.strings.action_replace_repo_message, newRepo.name, oldRepo.name))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExtensionRepoConfirmDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: () -> Unit,
|
||||
repo: String,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.action_add_repo))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(MR.strings.add_repo_confirmation, repo))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onCreate()
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(MR.strings.action_add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
+31
-24
@@ -9,9 +9,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
|
||||
import androidx.compose.material.icons.outlined.ContentCopy
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -20,21 +20,22 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||
import mihon.domain.extension.model.ExtensionStore
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.icons.CustomIcons
|
||||
import tachiyomi.presentation.core.icons.Discord
|
||||
|
||||
@Composable
|
||||
fun ExtensionReposContent(
|
||||
repos: ImmutableSet<ExtensionRepo>,
|
||||
fun ExtensionStoresContent(
|
||||
repos: List<ExtensionStore>,
|
||||
lazyListState: LazyListState,
|
||||
paddingValues: PaddingValues,
|
||||
onOpenWebsite: (ExtensionRepo) -> Unit,
|
||||
onClickDelete: (String) -> Unit,
|
||||
onCopy: (ExtensionStore) -> Unit,
|
||||
onOpenWebsite: (ExtensionStore) -> Unit,
|
||||
onOpenDiscord: (ExtensionStore) -> Unit,
|
||||
onClickDelete: (ExtensionStore) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
@@ -45,11 +46,13 @@ fun ExtensionReposContent(
|
||||
) {
|
||||
repos.forEach {
|
||||
item {
|
||||
ExtensionRepoListItem(
|
||||
ExtensionStoresListItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
repo = it,
|
||||
store = it,
|
||||
onOpenWebsite = { onOpenWebsite(it) },
|
||||
onDelete = { onClickDelete(it.baseUrl) },
|
||||
onOpenDiscord = { onOpenDiscord(it) },
|
||||
onCopy = { onCopy(it) },
|
||||
onDelete = { onClickDelete(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -57,14 +60,14 @@ fun ExtensionReposContent(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExtensionRepoListItem(
|
||||
repo: ExtensionRepo,
|
||||
private fun ExtensionStoresListItem(
|
||||
store: ExtensionStore,
|
||||
onOpenWebsite: () -> Unit,
|
||||
onOpenDiscord: () -> Unit,
|
||||
onCopy: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
ElevatedCard(
|
||||
modifier = modifier,
|
||||
) {
|
||||
@@ -80,7 +83,7 @@ private fun ExtensionRepoListItem(
|
||||
) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
||||
Text(
|
||||
text = repo.name,
|
||||
text = store.name,
|
||||
modifier = Modifier.padding(start = MaterialTheme.padding.medium),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
@@ -92,17 +95,21 @@ private fun ExtensionRepoListItem(
|
||||
) {
|
||||
IconButton(onClick = onOpenWebsite) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.OpenInNew,
|
||||
imageVector = Icons.Outlined.Public,
|
||||
contentDescription = stringResource(MR.strings.action_open_in_browser),
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
val url = "${repo.baseUrl}/index.min.json"
|
||||
context.copyToClipboard(url, url)
|
||||
},
|
||||
) {
|
||||
if (store.contact.discord != null) {
|
||||
IconButton(onClick = onOpenDiscord) {
|
||||
Icon(
|
||||
imageVector = CustomIcons.Discord,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
IconButton(onClick = onCopy) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.ContentCopy,
|
||||
contentDescription = stringResource(MR.strings.action_copy_to_clipboard),
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun ExtensionStoreCreateDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: (String) -> Unit,
|
||||
storeIndexUrls: Set<String>,
|
||||
processing: Boolean,
|
||||
errorMessage: String?,
|
||||
) {
|
||||
val state = rememberTextFieldState()
|
||||
val storeAlreadyExists by remember(storeIndexUrls) {
|
||||
derivedStateOf {
|
||||
val indexUrl = state.text.toString()
|
||||
storeIndexUrls.contains(indexUrl)
|
||||
}
|
||||
}
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.extensionStoresScreen_addStore_title))
|
||||
},
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
state = state,
|
||||
label = {
|
||||
Text(text = stringResource(MR.strings.extensionStoresScreen_addStoreInput_inputLabel))
|
||||
},
|
||||
supportingText = {
|
||||
val msgRes = if (storeAlreadyExists) {
|
||||
MR.strings.extensionStoresScreen_addStore_alreadyExists
|
||||
} else {
|
||||
MR.strings.information_required_plain
|
||||
}
|
||||
Text(text = errorMessage ?: stringResource(msgRes))
|
||||
},
|
||||
isError = errorMessage != null || storeAlreadyExists,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = { onCreate(state.text.toString()) },
|
||||
enabled = !processing && state.text.isNotEmpty() && !storeAlreadyExists,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
resource = if (processing) {
|
||||
MR.strings.extensionStoresScreen_addStore_processing
|
||||
} else {
|
||||
MR.strings.action_add
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
LaunchedEffect(focusRequester) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExtensionStoreDeleteDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
storeName: String,
|
||||
storeIndexUrl: String,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.extensionStoresScreen_deleteStore_title))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(MR.strings.extensionStoresScreen_deleteStore_body, storeName, storeIndexUrl))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onDelete()
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExtensionStoreConfirmDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: () -> Unit,
|
||||
storeIndexUrl: String,
|
||||
storeAlreadyExists: Boolean,
|
||||
processing: Boolean,
|
||||
errorMessage: String?,
|
||||
) {
|
||||
val state = rememberTextFieldState(initialText = storeIndexUrl)
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.extensionStoresScreen_addStore_title))
|
||||
},
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(text = stringResource(MR.strings.extensionStoresScreen_addStoreDeeplink_bodyText))
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state,
|
||||
readOnly = true,
|
||||
supportingText = when {
|
||||
storeAlreadyExists -> {
|
||||
{
|
||||
Text(text = stringResource(MR.strings.extensionStoresScreen_addStore_alreadyExists))
|
||||
}
|
||||
}
|
||||
errorMessage != null -> {
|
||||
{
|
||||
Text(text = errorMessage)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
},
|
||||
isError = errorMessage != null || storeAlreadyExists,
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onCreate, enabled = !storeAlreadyExists && !processing) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
resource = if (processing) {
|
||||
MR.strings.extensionStoresScreen_addStore_processing
|
||||
} else {
|
||||
MR.strings.action_add
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
+14
-12
@@ -1,5 +1,3 @@
|
||||
@file:JvmName("ExtensionReposScreenKt")
|
||||
|
||||
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -14,8 +12,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.more.settings.screen.browse.RepoScreenState
|
||||
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoreScreenState
|
||||
import mihon.domain.extension.model.ExtensionStore
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
@@ -25,11 +23,13 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
|
||||
@Composable
|
||||
fun ExtensionReposScreen(
|
||||
state: RepoScreenState.Success,
|
||||
fun ExtensionStoresScreen(
|
||||
state: ExtensionStoreScreenState.Success,
|
||||
onClickCreate: () -> Unit,
|
||||
onOpenWebsite: (ExtensionRepo) -> Unit,
|
||||
onClickDelete: (String) -> Unit,
|
||||
onCopy: (ExtensionStore) -> Unit,
|
||||
onOpenWebsite: (ExtensionStore) -> Unit,
|
||||
onOpenDiscord: (ExtensionStore) -> Unit,
|
||||
onClickDelete: (ExtensionStore) -> Unit,
|
||||
onClickRefresh: () -> Unit,
|
||||
navigateUp: () -> Unit,
|
||||
) {
|
||||
@@ -38,7 +38,7 @@ fun ExtensionReposScreen(
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
navigateUp = navigateUp,
|
||||
title = stringResource(MR.strings.label_extension_repos),
|
||||
title = stringResource(MR.strings.extensionStores),
|
||||
scrollBehavior = scrollBehavior,
|
||||
actions = {
|
||||
IconButton(onClick = onClickRefresh) {
|
||||
@@ -59,18 +59,20 @@ fun ExtensionReposScreen(
|
||||
) { paddingValues ->
|
||||
if (state.isEmpty) {
|
||||
EmptyScreen(
|
||||
MR.strings.information_empty_repos,
|
||||
MR.strings.extensionStoresScreen_emptyLabel,
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
return@Scaffold
|
||||
}
|
||||
|
||||
ExtensionReposContent(
|
||||
repos = state.repos,
|
||||
ExtensionStoresContent(
|
||||
repos = state.stores,
|
||||
lazyListState = lazyListState,
|
||||
paddingValues = paddingValues + topSmallPaddingValues +
|
||||
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||
onCopy = onCopy,
|
||||
onOpenWebsite = onOpenWebsite,
|
||||
onOpenDiscord = onOpenDiscord,
|
||||
onClickDelete = onClickDelete,
|
||||
)
|
||||
}
|
||||
+11
-12
@@ -11,8 +11,7 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
@@ -23,8 +22,8 @@ import eu.kanade.tachiyomi.data.backup.create.BackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupOptions
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.update
|
||||
import mihon.core.viewmodel.StateViewModel
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
||||
@@ -38,8 +37,8 @@ class CreateBackupScreen : Screen() {
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val model = rememberScreenModel { CreateBackupScreenModel() }
|
||||
val state by model.state.collectAsState()
|
||||
val viewModel = viewModel<CreateBackupViewModel>()
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
val chooseBackupDir = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/*"),
|
||||
@@ -50,7 +49,7 @@ class CreateBackupScreen : Screen() {
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
||||
)
|
||||
model.createBackup(context, it)
|
||||
viewModel.createBackup(context, it)
|
||||
navigator.pop()
|
||||
}
|
||||
}
|
||||
@@ -88,13 +87,13 @@ class CreateBackupScreen : Screen() {
|
||||
|
||||
item {
|
||||
SectionCard(MR.strings.label_library) {
|
||||
Options(BackupOptions.libraryOptions, state, model)
|
||||
Options(BackupOptions.libraryOptions, state, viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SectionCard(MR.strings.label_settings) {
|
||||
Options(BackupOptions.settingsOptions, state, model)
|
||||
Options(BackupOptions.settingsOptions, state, viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,9 +102,9 @@ class CreateBackupScreen : Screen() {
|
||||
|
||||
@Composable
|
||||
private fun Options(
|
||||
options: ImmutableList<BackupOptions.Entry>,
|
||||
state: CreateBackupScreenModel.State,
|
||||
model: CreateBackupScreenModel,
|
||||
options: List<BackupOptions.Entry>,
|
||||
state: CreateBackupViewModel.State,
|
||||
model: CreateBackupViewModel,
|
||||
) {
|
||||
options.forEach { option ->
|
||||
LabeledCheckbox(
|
||||
@@ -120,7 +119,7 @@ class CreateBackupScreen : Screen() {
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel.State>(State()) {
|
||||
class CreateBackupViewModel : StateViewModel<CreateBackupViewModel.State>(State()) {
|
||||
|
||||
fun toggle(setter: (BackupOptions, Boolean) -> BackupOptions, enabled: Boolean) {
|
||||
mutableState.update {
|
||||
|
||||
+32
-10
@@ -1,5 +1,6 @@
|
||||
package eu.kanade.presentation.more.settings.screen.data
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -14,14 +15,15 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.core.net.toUri
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
@@ -32,6 +34,7 @@ import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import kotlinx.coroutines.flow.update
|
||||
import mihon.core.viewmodel.StateViewModel
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
||||
@@ -39,6 +42,8 @@ import tachiyomi.presentation.core.components.SectionCard
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class RestoreBackupScreen(
|
||||
private val uri: String,
|
||||
@@ -46,10 +51,14 @@ class RestoreBackupScreen(
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val model = rememberScreenModel { RestoreBackupScreenModel(context, uri) }
|
||||
val state by model.state.collectAsState()
|
||||
val viewModel = viewModel<RestoreBackupViewModel>(
|
||||
factory = RestoreBackupViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(RestoreBackupViewModel.URI_KEY, uri)
|
||||
},
|
||||
)
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -65,7 +74,7 @@ class RestoreBackupScreen(
|
||||
actionLabel = stringResource(MR.strings.action_restore),
|
||||
actionEnabled = state.canRestore && state.options.canRestore(),
|
||||
onClickAction = {
|
||||
model.startRestore()
|
||||
viewModel.startRestore()
|
||||
navigator.pop()
|
||||
},
|
||||
) {
|
||||
@@ -83,7 +92,7 @@ class RestoreBackupScreen(
|
||||
label = stringResource(option.label),
|
||||
checked = option.getter(state.options),
|
||||
onCheckedChange = {
|
||||
model.toggle(option.setter, it)
|
||||
viewModel.toggle(option.setter, it)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -164,10 +173,23 @@ class RestoreBackupScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private class RestoreBackupScreenModel(
|
||||
class RestoreBackupViewModel(
|
||||
private val context: Context,
|
||||
private val uri: String,
|
||||
) : StateScreenModel<RestoreBackupScreenModel.State>(State()) {
|
||||
) : StateViewModel<RestoreBackupViewModel.State>(State()) {
|
||||
|
||||
companion object {
|
||||
val URI_KEY = CreationExtras.Key<String>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
RestoreBackupViewModel(
|
||||
context = Injekt.get<Application>(),
|
||||
uri = get(URI_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
validate(uri.toUri())
|
||||
|
||||
+1
-2
@@ -19,7 +19,6 @@ import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
@@ -45,7 +44,7 @@ class BackupSchemaScreen : Screen() {
|
||||
navigateUp = navigator::pop,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_copy_to_clipboard),
|
||||
icon = Icons.Default.ContentCopy,
|
||||
|
||||
+6
-8
@@ -24,8 +24,6 @@ import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.guava.await
|
||||
import kotlinx.coroutines.launch
|
||||
import mihon.core.common.FeatureFlags
|
||||
@@ -69,7 +67,7 @@ class DebugInfoScreen : Screen() {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = "App info",
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = "Version",
|
||||
subtitle = AboutScreen.getVersionName(false),
|
||||
@@ -141,8 +139,8 @@ class DebugInfoScreen : Screen() {
|
||||
}
|
||||
|
||||
private fun getDeviceInfoGroup(): Preference.PreferenceGroup {
|
||||
val items = persistentListOf<Preference.PreferenceItem<out Any, out Any>>().mutate {
|
||||
it.add(
|
||||
val items = buildList {
|
||||
add(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = "Model",
|
||||
subtitle = "${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})",
|
||||
@@ -150,14 +148,14 @@ class DebugInfoScreen : Screen() {
|
||||
)
|
||||
|
||||
if (DeviceUtil.oneUiVersion != null) {
|
||||
it.add(
|
||||
add(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = "OneUI version",
|
||||
subtitle = "${DeviceUtil.oneUiVersion}",
|
||||
),
|
||||
)
|
||||
} else if (DeviceUtil.miuiMajorVersion != null) {
|
||||
it.add(
|
||||
add(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = "MIUI version",
|
||||
subtitle = "${DeviceUtil.miuiMajorVersion}",
|
||||
@@ -172,7 +170,7 @@ class DebugInfoScreen : Screen() {
|
||||
} else {
|
||||
Build.VERSION.RELEASE
|
||||
}
|
||||
it.add(
|
||||
add(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = "Android version",
|
||||
subtitle = "$androidVersion (${Build.DISPLAY})",
|
||||
|
||||
+28
-13
@@ -1,5 +1,6 @@
|
||||
package eu.kanade.presentation.more.settings.screen.debug
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -18,21 +19,22 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkQuery
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.presentation.util.ioCoroutineScope
|
||||
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@@ -57,10 +59,10 @@ class WorkerInfoScreen : Screen() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val screenModel = rememberScreenModel { Model(context) }
|
||||
val enqueued by screenModel.enqueued.collectAsState()
|
||||
val finished by screenModel.finished.collectAsState()
|
||||
val running by screenModel.running.collectAsState()
|
||||
val viewModel = viewModel<WorkerInfoViewModel>(factory = WorkerInfoViewModel.Factory)
|
||||
val enqueued by viewModel.enqueued.collectAsState()
|
||||
val finished by viewModel.finished.collectAsState()
|
||||
val running by viewModel.running.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -69,7 +71,7 @@ class WorkerInfoScreen : Screen() {
|
||||
navigateUp = navigator::pop,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_copy_to_clipboard),
|
||||
icon = Icons.Default.ContentCopy,
|
||||
@@ -118,7 +120,20 @@ class WorkerInfoScreen : Screen() {
|
||||
)
|
||||
}
|
||||
|
||||
private class Model(context: Context) : ScreenModel {
|
||||
class WorkerInfoViewModel(
|
||||
context: Context,
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
WorkerInfoViewModel(
|
||||
context = Injekt.get<Application>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val workManager = context.workManager
|
||||
|
||||
val finished = workManager
|
||||
@@ -126,17 +141,17 @@ class WorkerInfoScreen : Screen() {
|
||||
WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED),
|
||||
)
|
||||
.map(::constructString)
|
||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||
|
||||
val running = workManager
|
||||
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
||||
.map(::constructString)
|
||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||
|
||||
val enqueued = workManager
|
||||
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.ENQUEUED))
|
||||
.map(::constructString)
|
||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||
|
||||
private fun constructString(list: List<WorkInfo>) = buildString {
|
||||
if (list.isEmpty()) {
|
||||
|
||||
+14
-11
@@ -10,39 +10,42 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun MultiSelectListPreferenceWidget(
|
||||
preference: Preference.PreferenceItem.MultiSelectListPreference,
|
||||
values: Set<String>,
|
||||
onValuesChange: (Set<String>) -> Unit,
|
||||
fun <T> MultiSelectListPreferenceWidget(
|
||||
values: Set<T>,
|
||||
title: String,
|
||||
subtitle: String?,
|
||||
icon: ImageVector?,
|
||||
entries: Map<out T, String>,
|
||||
onValuesChange: (Set<T>) -> Unit,
|
||||
) {
|
||||
var isDialogShown by remember { mutableStateOf(false) }
|
||||
|
||||
TextPreferenceWidget(
|
||||
title = preference.title,
|
||||
subtitle = preference.subtitleProvider(values, preference.entries),
|
||||
icon = preference.icon,
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
icon = icon,
|
||||
onPreferenceClick = { isDialogShown = true },
|
||||
)
|
||||
|
||||
if (isDialogShown) {
|
||||
val selected = remember {
|
||||
preference.entries.keys
|
||||
entries.keys
|
||||
.filter { values.contains(it) }
|
||||
.toMutableStateList()
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = { isDialogShown = false },
|
||||
title = { Text(text = preference.title) },
|
||||
title = { Text(text = title) },
|
||||
text = {
|
||||
LazyColumn {
|
||||
preference.entries.forEach { current ->
|
||||
entries.forEach { current ->
|
||||
item {
|
||||
val isSelected = selected.contains(current.key)
|
||||
LabeledCheckbox(
|
||||
|
||||
+27
-11
@@ -2,6 +2,7 @@ package eu.kanade.presentation.more.settings.widget
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -15,6 +16,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
||||
import eu.kanade.presentation.track.components.TrackLogoIcon
|
||||
@@ -26,7 +29,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
fun TrackingPreferenceWidget(
|
||||
modifier: Modifier = Modifier,
|
||||
tracker: Tracker,
|
||||
checked: Boolean,
|
||||
isLoggedIn: Boolean,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val highlighted = LocalPreferenceHighlighted.current
|
||||
@@ -39,16 +42,29 @@ fun TrackingPreferenceWidget(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TrackLogoIcon(tracker)
|
||||
Text(
|
||||
text = tracker.name,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 16.dp),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontSize = TitleFontSize,
|
||||
)
|
||||
if (checked) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = tracker.name,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontSize = TitleFontSize,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
val displayName = tracker.getDisplayUsername()
|
||||
if (isLoggedIn && displayName.isNotBlank()) {
|
||||
Text(
|
||||
text = displayName,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (isLoggedIn) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Done,
|
||||
modifier = Modifier
|
||||
|
||||
@@ -37,7 +37,6 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainChapter
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.service.calculateChapterGap
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -235,7 +234,7 @@ private fun ChapterText(
|
||||
maxLines = 5,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
inlineContent = persistentMapOf(
|
||||
inlineContent = mapOf(
|
||||
DOWNLOADED_ICON_ID to InlineTextContent(
|
||||
Placeholder(
|
||||
width = 22.sp,
|
||||
|
||||
@@ -18,7 +18,7 @@ import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.reader.components.ModeSelectionDialog
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||
import tachiyomi.presentation.core.components.material.IconToggleButton
|
||||
@@ -29,17 +29,17 @@ private val ReaderOrientationsWithoutDefault = ReaderOrientation.entries - Reade
|
||||
@Composable
|
||||
fun OrientationSelectDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
screenModel: ReaderSettingsScreenModel,
|
||||
viewModel: ReaderSettingsViewModel,
|
||||
onChange: (StringResource) -> Unit,
|
||||
) {
|
||||
val manga by screenModel.mangaFlow.collectAsState()
|
||||
val manga by viewModel.mangaFlow.collectAsState()
|
||||
val orientation = remember(manga) { ReaderOrientation.fromPreference(manga?.readerOrientation?.toInt()) }
|
||||
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
DialogContent(
|
||||
orientation = orientation,
|
||||
onChangeOrientation = {
|
||||
screenModel.onChangeOrientation(it)
|
||||
viewModel.onChangeOrientation(it)
|
||||
onChange(it.stringRes)
|
||||
onDismissRequest()
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ import eu.kanade.domain.manga.model.readingMode
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.reader.components.ModeSelectionDialog
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||
@@ -31,17 +31,17 @@ private val ReadingModesWithoutDefault = ReadingMode.entries - ReadingMode.DEFAU
|
||||
@Composable
|
||||
fun ReadingModeSelectDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
screenModel: ReaderSettingsScreenModel,
|
||||
viewModel: ReaderSettingsViewModel,
|
||||
onChange: (StringResource) -> Unit,
|
||||
) {
|
||||
val manga by screenModel.mangaFlow.collectAsState()
|
||||
val manga by viewModel.mangaFlow.collectAsState()
|
||||
val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
|
||||
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
DialogContent(
|
||||
readingMode = readingMode,
|
||||
onChangeReadingMode = {
|
||||
screenModel.onChangeReadingMode(it)
|
||||
viewModel.onChangeReadingMode(it)
|
||||
onChange(it.stringRes)
|
||||
onDismissRequest()
|
||||
},
|
||||
|
||||
@@ -4,31 +4,39 @@ import androidx.compose.animation.AnimatedVisibility
|
||||
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.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.reader.components.ChapterNavigator
|
||||
import eu.kanade.presentation.reader.components.ChapterNavigatorType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
private val readerBarsSlideAnimationSpec = tween<IntOffset>(200)
|
||||
@@ -48,7 +56,8 @@ fun ReaderAppBars(
|
||||
onOpenInBrowser: (() -> Unit)?,
|
||||
onShare: (() -> Unit)?,
|
||||
|
||||
viewer: Viewer?,
|
||||
chapterNavigatorType: ChapterNavigatorType,
|
||||
verticalNavigatorHeight: Float,
|
||||
onNextChapter: () -> Unit,
|
||||
enabledNext: Boolean,
|
||||
onPreviousChapter: () -> Unit,
|
||||
@@ -56,6 +65,7 @@ fun ReaderAppBars(
|
||||
currentPage: Int,
|
||||
totalPages: Int,
|
||||
onPageIndexChange: (Int) -> Unit,
|
||||
onPageIndexChangeFinished: () -> Unit,
|
||||
|
||||
readingMode: ReadingMode,
|
||||
onClickReadingMode: () -> Unit,
|
||||
@@ -65,7 +75,6 @@ fun ReaderAppBars(
|
||||
onClickCropBorder: () -> Unit,
|
||||
onClickSettings: () -> Unit,
|
||||
) {
|
||||
val isRtl = viewer is R2LPagerViewer
|
||||
val backgroundColor = MaterialTheme.colorScheme
|
||||
.surfaceColorAtElevation(3.dp)
|
||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||
@@ -73,10 +82,8 @@ fun ReaderAppBars(
|
||||
Column(modifier = Modifier.fillMaxHeight()) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(initialOffsetY = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||
fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
||||
exit = slideOutVertically(targetOffsetY = { -it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||
fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
||||
enter = slideInVertically(readerBarsSlideAnimationSpec) { -it } + fadeIn(readerBarsFadeAnimationSpec),
|
||||
exit = slideOutVertically(readerBarsSlideAnimationSpec) { -it } + fadeOut(readerBarsFadeAnimationSpec),
|
||||
) {
|
||||
ReaderTopBar(
|
||||
modifier = Modifier
|
||||
@@ -93,26 +100,66 @@ fun ReaderAppBars(
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
if (!chapterNavigatorType.isHorizontal()) {
|
||||
val sliderOnLeft = chapterNavigatorType == ChapterNavigatorType.VERTICAL_LEFT
|
||||
CompositionLocalProvider(
|
||||
LocalLayoutDirection provides if (sliderOnLeft) LayoutDirection.Ltr else LayoutDirection.Rtl,
|
||||
) {
|
||||
Row(modifier = Modifier.weight(1f)) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInHorizontally(readerBarsSlideAnimationSpec) { if (sliderOnLeft) -it else it } +
|
||||
fadeIn(readerBarsFadeAnimationSpec),
|
||||
exit = slideOutHorizontally(readerBarsSlideAnimationSpec) { if (sliderOnLeft) -it else it } +
|
||||
fadeOut(readerBarsFadeAnimationSpec),
|
||||
) {
|
||||
Row {
|
||||
Spacer(modifier = Modifier.width(MaterialTheme.padding.small))
|
||||
Box(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
contentAlignment = Alignment.BottomCenter,
|
||||
) {
|
||||
ChapterNavigator(
|
||||
modifier = Modifier.fillMaxHeight(verticalNavigatorHeight),
|
||||
type = chapterNavigatorType,
|
||||
onNextChapter = onNextChapter,
|
||||
enabledNext = enabledNext,
|
||||
onPreviousChapter = onPreviousChapter,
|
||||
enabledPrevious = enabledPrevious,
|
||||
currentPage = currentPage,
|
||||
totalPages = totalPages,
|
||||
onPageIndexChange = onPageIndexChange,
|
||||
onPageIndexChangeFinished = onPageIndexChangeFinished,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Spacer(Modifier.weight(1f))
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(initialOffsetY = { it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||
fadeIn(animationSpec = readerBarsFadeAnimationSpec),
|
||||
exit = slideOutVertically(targetOffsetY = { it }, animationSpec = readerBarsSlideAnimationSpec) +
|
||||
fadeOut(animationSpec = readerBarsFadeAnimationSpec),
|
||||
enter = slideInVertically(readerBarsSlideAnimationSpec) { it } + fadeIn(readerBarsFadeAnimationSpec),
|
||||
exit = slideOutVertically(readerBarsSlideAnimationSpec) { it } + fadeOut(readerBarsFadeAnimationSpec),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small)) {
|
||||
ChapterNavigator(
|
||||
isRtl = isRtl,
|
||||
onNextChapter = onNextChapter,
|
||||
enabledNext = enabledNext,
|
||||
onPreviousChapter = onPreviousChapter,
|
||||
enabledPrevious = enabledPrevious,
|
||||
currentPage = currentPage,
|
||||
totalPages = totalPages,
|
||||
onPageIndexChange = onPageIndexChange,
|
||||
)
|
||||
if (chapterNavigatorType.isHorizontal()) {
|
||||
ChapterNavigator(
|
||||
type = chapterNavigatorType,
|
||||
onNextChapter = onNextChapter,
|
||||
enabledNext = enabledNext,
|
||||
onPreviousChapter = onPreviousChapter,
|
||||
enabledPrevious = enabledPrevious,
|
||||
currentPage = currentPage,
|
||||
totalPages = totalPages,
|
||||
onPageIndexChange = onPageIndexChange,
|
||||
onPageIndexChangeFinished = onPageIndexChangeFinished,
|
||||
)
|
||||
}
|
||||
ReaderBottomBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user