Compare commits
249 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
043fe03e04 | ||
|
a9c7cbf2c4 | ||
|
e63a52b8e3 | ||
|
49991d38d9 | ||
|
33c62ab711 | ||
|
899bd26956 | ||
|
a37f3eb709 | ||
|
9ae71dfe93 | ||
|
c65a9aecf5 | ||
|
02e50411de | ||
|
6e822dfd5b | ||
|
7292dadd5f | ||
|
b1067b942e | ||
|
d6c4af89c4 | ||
|
cf6f7c521c | ||
|
c6601c1f94 | ||
|
68899aea61 | ||
|
c3edf9b5d0 | ||
|
97e04392d3 | ||
|
3d178737b1 | ||
|
bf737cf95c | ||
|
c91ec9a33b | ||
|
a8040cb21a | ||
|
f60782f11f | ||
|
7d6e1bdafc | ||
|
5854ad97e0 | ||
|
4b8fa059d5 | ||
|
3dc2f9a711 | ||
|
8033a94ee2 | ||
|
028da099dd | ||
|
e6c6c32d81 | ||
|
bce6af62fc | ||
|
6510a9617a | ||
|
14510f1d26 | ||
|
f115edf2ea | ||
|
8a8362203f | ||
|
f3336fc5c3 | ||
|
727289c8eb | ||
|
9c91ddd4e3 | ||
|
3ea026e311 | ||
|
36f307e3bb | ||
|
89678ebb17 | ||
|
80b7d14af1 | ||
|
bbd8098a61 | ||
|
f8ef0f143b | ||
|
a3ef3604ee | ||
|
c4ceda59df | ||
|
7e053b5862 | ||
|
ac8ed3c028 | ||
|
8321ff6000 | ||
|
9c899e97a9 | ||
|
556f5a42a7 | ||
|
850813820c | ||
|
dba5e6fbfd | ||
|
c17ada2c98 | ||
|
32bed9b041 | ||
|
e0a0942015 | ||
|
8409ebe4eb | ||
|
493da5c3f4 | ||
|
4e221397ce | ||
|
8a7d6a328a | ||
|
22589a9c30 | ||
|
ec478cbb1b | ||
|
b5e3f429fc | ||
|
83130f9bf9 | ||
|
6f34c5e894 | ||
|
74931fad86 | ||
|
6ab8e1e73d | ||
|
1cdaa761b7 | ||
|
901b77f55c | ||
|
54f4711f7b | ||
|
3d0d5c0472 | ||
|
f0a0ecfd4a | ||
|
f3b7eaf4a3 | ||
|
5bba7af24a | ||
|
32c3269291 | ||
|
a1e84911be | ||
|
6bb77bcf1a | ||
|
ccec5c3efe | ||
|
8735836498 | ||
|
8b65fd5751 | ||
|
f0710df356 | ||
|
3afcee81f4 | ||
|
9c120e6231 | ||
|
4b208fc7ce | ||
|
a9b0ac43c4 | ||
|
fca4f25122 | ||
|
bfb0d31ff6 | ||
|
8939274b5c | ||
|
087da2b2f3 | ||
|
4571dc6b56 | ||
|
f31bc47757 | ||
|
950b4a6c90 | ||
|
2d7650537d | ||
|
80d6d412f3 | ||
|
446b146f95 | ||
|
6887d98f15 | ||
|
6d74a86711 | ||
|
5908bd1930 | ||
|
1a559124eb | ||
|
54ba1d719e | ||
|
93cbeca5c0 | ||
|
19f0175a56 | ||
|
bf3899d04a | ||
|
dcf0379496 | ||
|
9f90ee358b | ||
|
565317d99c | ||
|
83a67feb48 | ||
|
a51108cbe8 | ||
|
b9fd416fc6 | ||
|
c10cd6c808 | ||
|
c62cd6e997 | ||
|
7ae17e6aac | ||
|
f20980b4c9 | ||
|
02cd2d2ca3 | ||
|
3847d4f4cf | ||
|
f9b57800b1 | ||
|
387159b5af | ||
|
09531e7f5a | ||
|
c6356fe4b2 | ||
|
ff3bc66055 | ||
|
13b3bec8ad | ||
|
8aaf8df708 | ||
|
c00f05a1c1 | ||
|
db3ddf07ee | ||
|
cd16522805 | ||
|
5fec881387 | ||
|
3ac68e810d | ||
|
e6fe5c827c | ||
|
65e1e2cf4f | ||
|
e36a2c68f1 | ||
|
add9357257 | ||
|
ad3d915fc5 | ||
|
36f400d542 | ||
|
dd1a19745a | ||
|
58daedc89e | ||
|
d20a8fcf13 | ||
|
e56bf82c31 | ||
|
0f9895eec8 | ||
|
f776c36e70 | ||
|
1ef01b53f2 | ||
|
720169dce3 | ||
|
0d09039e5f | ||
|
cc56fde9fe | ||
|
3a0b3de175 | ||
|
47e544b710 | ||
|
44d6c4fe44 | ||
|
e5693ed668 | ||
|
8c21aa86e9 | ||
|
f7c5b42435 | ||
|
e3404cd3d3 | ||
|
8b57169e92 | ||
|
ab9a26f6bd | ||
|
8779b263ab | ||
|
3135db4bb2 | ||
|
734cb0be6e | ||
|
1f259f9298 | ||
|
427fbfdf5e | ||
|
0c860c0fe9 | ||
|
5b2a099203 | ||
|
ccadfc8fe5 | ||
|
3aead3a2a9 | ||
|
6a48fed170 | ||
|
ea1684133b | ||
|
e5263d0345 | ||
|
24e1b4034e | ||
|
dfa5c229b3 | ||
|
87be54aa4a | ||
|
82d9ae31bd | ||
|
e5518b7615 | ||
|
e5a22eafe7 | ||
|
7a52afd223 | ||
|
296201d6b7 | ||
|
162b639705 | ||
|
5dda32bb81 | ||
|
8ce8b60092 | ||
|
8ff2c01bf2 | ||
|
e22eebfd02 | ||
|
4fcdde4913 | ||
|
e41668862f | ||
|
a74a689c90 | ||
|
d85a76484c | ||
|
82bdf63419 | ||
|
9ce0bc6b5f | ||
|
bf524595e2 | ||
|
27c4db752c | ||
|
ca54984344 | ||
|
46aeab9a7a | ||
|
f365b53a0f | ||
|
d4dfa9a2c2 | ||
|
cf9e60fd92 | ||
|
21ae04d25d | ||
|
f1778ac5b4 | ||
|
ba10093ddc | ||
|
a5c9469698 | ||
|
75314c78e0 | ||
|
53edae1b6b | ||
|
356fc5b524 | ||
|
60150423d7 | ||
|
bcc42dd259 | ||
|
d59cb9c1e3 | ||
|
3006604922 | ||
|
1fbf8ca079 | ||
|
695813ef7d | ||
|
e3b70ca08d | ||
|
8857b7e0c1 | ||
|
4a7c20f5a0 | ||
|
29368fc953 | ||
|
0696e4bce0 | ||
|
255ed50685 | ||
|
00afee83b8 | ||
|
0d1bced122 | ||
|
46e734fc8e | ||
|
c39ae21f4a | ||
|
69aa13bc56 | ||
|
2c032ff70d | ||
|
0af4703b78 | ||
|
ea15bc782a | ||
|
9ec0f73e87 | ||
|
f9fb034330 | ||
|
6eb5a25ea1 | ||
|
45d8411f98 | ||
|
d9e2317e62 | ||
|
336221a972 | ||
|
dd998be1e7 | ||
|
3c3b09209c | ||
|
4a6571d310 | ||
|
cb67f1de52 | ||
|
402e2c47fb | ||
|
58b2895ec9 | ||
|
00b2853d3d | ||
|
634ceeec50 | ||
|
d7442d771b | ||
|
8f22480ec9 | ||
|
9d974273af | ||
|
3a8aa3e8cd | ||
|
9e67abcc8a | ||
|
d0bcd30909 | ||
|
b97aa23548 | ||
|
e6ca54fd04 | ||
|
4502902fb0 | ||
|
5f34539525 | ||
|
953f5fb025 | ||
|
4f3a0b3523 | ||
|
1d144e6767 | ||
|
056dbaefda | ||
|
3a15c6b843 | ||
|
db20d04c4b | ||
|
c5e8c9f01f |
@ -1,7 +1,8 @@
|
|||||||
[*.{kt,kts}]
|
[*.{kt,kts}]
|
||||||
indent_size=4
|
max_line_length = 120
|
||||||
insert_final_newline=true
|
indent_size = 4
|
||||||
ij_kotlin_allow_trailing_comma=true
|
insert_final_newline = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site=true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
ij_kotlin_name_count_to_use_star_import=2147483647
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members=2147483647
|
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||||
|
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||||
|
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@ -3,10 +3,10 @@
|
|||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated:
|
- I have updated:
|
||||||
- To the latest version of the app (stable is v0.14.7)
|
- To the latest version of the app (stable is v0.15.3)
|
||||||
- All extensions
|
- All extensions
|
||||||
- I have gone through the FAQ (https://tachiyomi.org/docs/faq/general) and troubleshooting guide (https://tachiyomi.org/docs/guides/troubleshooting/)
|
- I have gone through the FAQ (https://tachiyomi.org/docs/faq/general) and troubleshooting guide (https://tachiyomi.org/docs/guides/troubleshooting/)
|
||||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
- If this is an issue with an official extension, that I should be opening an issue in https://github.com/tachiyomiorg/extensions
|
||||||
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
|
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
|
||||||
- I will fill out the title and the information in this template
|
- I will fill out the title and the information in this template
|
||||||
|
|
||||||
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: ⚠️ Extension/source issue
|
- name: ⚠️ Extension/source issue
|
||||||
url: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
url: https://github.com/tachiyomiorg/extensions/issues/new/choose
|
||||||
about: Issues and requests for extensions and sources should be opened in the tachiyomi-extensions repository instead
|
about: Issues and requests for official extensions and sources should be opened in the extensions repository instead
|
||||||
- name: 📦 Tachiyomi extensions
|
- name: 📦 Tachiyomi extensions
|
||||||
url: https://tachiyomi.org/extensions/
|
url: https://tachiyomi.org/extensions/
|
||||||
about: List of all available extensions with download links
|
about: List of all available extensions with download links
|
||||||
|
6
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
6
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
label: Tachiyomi version
|
label: Tachiyomi version
|
||||||
description: You can find your Tachiyomi version in **More → About**.
|
description: You can find your Tachiyomi version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "0.14.7"
|
Example: "0.15.3"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -94,11 +94,11 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
- label: If this is an issue with an official extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/extensions/issues/new/choose).
|
||||||
required: true
|
required: true
|
||||||
- label: I have gone through the [FAQ](https://tachiyomi.org/docs/faq/general) and [troubleshooting guide](https://tachiyomi.org/docs/guides/troubleshooting/).
|
- label: I have gone through the [FAQ](https://tachiyomi.org/docs/faq/general) and [troubleshooting guide](https://tachiyomi.org/docs/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.15.3](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
|
4
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
4
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -31,9 +31,9 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
- label: If this is an issue with an official extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/extensions/issues/new/choose).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.15.3](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
5
.github/workflows/build_pull_request.yml
vendored
5
.github/workflows/build_pull_request.yml
vendored
@ -3,7 +3,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- 'i18n/src/main/res/**/strings.xml'
|
- 'i18n/src/commonMain/resources/**/strings.xml'
|
||||||
|
- 'i18n/src/commonMain/resources/**/plurals.xml'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
@ -28,7 +29,7 @@ jobs:
|
|||||||
uses: actions/dependency-review-action@v3
|
uses: actions/dependency-review-action@v3
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
|
6
.github/workflows/build_push.yml
vendored
6
.github/workflows/build_push.yml
vendored
@ -22,8 +22,12 @@ jobs:
|
|||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
run: |
|
||||||
|
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v4
|
- uses: dessant/lock-threads@v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-inactive-days: '2'
|
issue-inactive-days: '2'
|
||||||
|
@ -59,8 +59,7 @@ representative at an online or offline event.
|
|||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community moderators responsible for enforcement at
|
reported to the community moderators via issues.
|
||||||
the [Tachiyomi Discord server](https://discord.gg/tachiyomi).
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community moderators are obligated to respect the privacy and security of the
|
All community moderators are obligated to respect the privacy and security of the
|
||||||
|
@ -30,7 +30,7 @@ To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
|||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
No support is currently provided.
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
|
||||||
|
13
README.md
13
README.md
@ -1,7 +1,6 @@
|
|||||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
| Build | Stable | Weekly Preview | Contribute |
|
||||||
|-------|----------|---------|------------|---------|
|
|-------|--------|----------------|------------|
|
||||||
| [![CI](https://github.com/tachiyomiorg/tachiyomi/actions/workflows/build_push.yml/badge.svg)](https://github.com/tachiyomiorg/tachiyomi/actions/workflows/build_push.yml) | [![stable release](https://img.shields.io/github/release/tachiyomiorg/tachiyomi.svg?maxAge=3600&label=download)](https://github.com/tachiyomiorg/tachiyomi/releases) | [![latest preview build](https://img.shields.io/github/v/release/tachiyomiorg/tachiyomi-preview.svg?maxAge=3600&label=download)](https://github.com/tachiyomiorg/tachiyomi-preview/releases) | [![Translation status](https://hosted.weblate.org/widgets/tachiyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/349436576037732353.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/tachiyomi) |
|
| [![CI](https://github.com/tachiyomiorg/tachiyomi/actions/workflows/build_push.yml/badge.svg)](https://github.com/tachiyomiorg/tachiyomi/actions/workflows/build_push.yml) | [![stable release](https://img.shields.io/github/release/tachiyomiorg/tachiyomi.svg?maxAge=3600&label=download)](https://github.com/tachiyomiorg/tachiyomi/releases) | [![latest preview build](https://img.shields.io/github/v/release/tachiyomiorg/tachiyomi-preview.svg?maxAge=3600&label=download)](https://github.com/tachiyomiorg/tachiyomi-preview/releases) | [![Translation status](https://hosted.weblate.org/widgets/tachiyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) |
|
||||||
|
|
||||||
|
|
||||||
# ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
|
# ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
|
||||||
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
|
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
|
||||||
@ -29,8 +28,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||||||
|
|
||||||
<details><summary>Issues</summary>
|
<details><summary>Issues</summary>
|
||||||
|
|
||||||
1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/docs/faq/general), the [changelog](https://tachiyomi.org/changelogs/) and the already opened [issues](https://github.com/tachiyomiorg/tachiyomi/issues).**
|
**Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/docs/faq/general), the [changelog](https://tachiyomi.org/changelogs/) and the already opened [issues](https://github.com/tachiyomiorg/tachiyomi/issues).**
|
||||||
2. If you are unsure, ask here: [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi)
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@ -55,7 +53,7 @@ DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
|||||||
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
|
|
||||||
Source requests should be created at https://github.com/tachiyomiorg/tachiyomi-extensions, they do not belong in this repository.
|
Source requests are not accepted.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details><summary>Contributing</summary>
|
<details><summary>Contributing</summary>
|
||||||
@ -71,7 +69,6 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
[See our website.](https://tachiyomi.org/)
|
[See our website.](https://tachiyomi.org/)
|
||||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -8,10 +8,6 @@ plugins {
|
|||||||
id("com.github.zellius.shortcut-helper")
|
id("com.github.zellius.shortcut-helper")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
|
||||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||||
|
|
||||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||||
@ -22,8 +18,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
applicationId = "eu.kanade.tachiyomi"
|
||||||
|
|
||||||
versionCode = 108
|
versionCode = 119
|
||||||
versionName = "0.14.7"
|
versionName = "0.15.3"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@ -31,9 +27,6 @@ android {
|
|||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||||
buildConfigField("boolean", "PREVIEW", "false")
|
buildConfigField("boolean", "PREVIEW", "false")
|
||||||
|
|
||||||
// Please disable ACRA or use your own instance in forked versions of the project
|
|
||||||
buildConfigField("String", "ACRA_URI", "\"https://tachiyomi.kanade.eu/crash_report\"")
|
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += SUPPORTED_ABIS
|
abiFilters += SUPPORTED_ABIS
|
||||||
}
|
}
|
||||||
@ -97,7 +90,7 @@ android {
|
|||||||
}
|
}
|
||||||
create("dev") {
|
create("dev") {
|
||||||
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
||||||
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
// resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,6 +116,7 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
compose = true
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
|
|
||||||
// Disable some unused things
|
// Disable some unused things
|
||||||
aidl = false
|
aidl = false
|
||||||
@ -164,9 +158,8 @@ dependencies {
|
|||||||
implementation(compose.ui.tooling.preview)
|
implementation(compose.ui.tooling.preview)
|
||||||
implementation(compose.ui.util)
|
implementation(compose.ui.util)
|
||||||
implementation(compose.accompanist.webview)
|
implementation(compose.accompanist.webview)
|
||||||
implementation(compose.accompanist.permissions)
|
|
||||||
implementation(compose.accompanist.themeadapter)
|
|
||||||
implementation(compose.accompanist.systemuicontroller)
|
implementation(compose.accompanist.systemuicontroller)
|
||||||
|
lintChecks(compose.lintchecks)
|
||||||
|
|
||||||
implementation(androidx.paging.runtime)
|
implementation(androidx.paging.runtime)
|
||||||
implementation(androidx.paging.compose)
|
implementation(androidx.paging.compose)
|
||||||
@ -174,6 +167,7 @@ dependencies {
|
|||||||
implementation(libs.bundles.sqlite)
|
implementation(libs.bundles.sqlite)
|
||||||
|
|
||||||
implementation(kotlinx.reflect)
|
implementation(kotlinx.reflect)
|
||||||
|
implementation(kotlinx.immutables)
|
||||||
|
|
||||||
implementation(platform(kotlinx.coroutines.bom))
|
implementation(platform(kotlinx.coroutines.bom))
|
||||||
implementation(kotlinx.bundles.coroutines)
|
implementation(kotlinx.bundles.coroutines)
|
||||||
@ -196,7 +190,6 @@ dependencies {
|
|||||||
|
|
||||||
// RxJava
|
// RxJava
|
||||||
implementation(libs.rxjava)
|
implementation(libs.rxjava)
|
||||||
implementation(libs.flowreactivenetwork)
|
|
||||||
|
|
||||||
// Networking
|
// Networking
|
||||||
implementation(libs.bundles.okhttp)
|
implementation(libs.bundles.okhttp)
|
||||||
@ -245,10 +238,6 @@ dependencies {
|
|||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
|
|
||||||
// Crash reports/analytics
|
|
||||||
implementation(libs.acra.http)
|
|
||||||
"standardImplementation"(libs.firebase.analytics)
|
|
||||||
|
|
||||||
// Shizuku
|
// Shizuku
|
||||||
implementation(libs.bundles.shizuku)
|
implementation(libs.bundles.shizuku)
|
||||||
|
|
||||||
|
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||||
-keepattributes *Annotation*, InnerClasses
|
-keepattributes *Annotation*, InnerClasses
|
||||||
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
|
-dontnote kotlinx.serialization.** # core serialization annotations
|
||||||
|
|
||||||
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
||||||
-keepclassmembers class kotlinx.serialization.json.** {
|
-keepclassmembers class kotlinx.serialization.json.** {
|
||||||
@ -71,7 +71,3 @@
|
|||||||
|
|
||||||
# XmlUtil
|
# XmlUtil
|
||||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
||||||
|
|
||||||
# Firebase
|
|
||||||
-keep class com.google.firebase.installations.** { *; }
|
|
||||||
-keep interface com.google.firebase.installations.** { *; }
|
|
@ -8,7 +8,9 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
|
||||||
<!-- Storage -->
|
<!-- Storage -->
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
<!-- For background jobs -->
|
<!-- For background jobs -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
@ -20,14 +22,16 @@
|
|||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||||
<!-- To view extension packages in API 30+ -->
|
<!-- To view extension packages in API 30+ -->
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission
|
||||||
|
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
|
<uses-permission
|
||||||
|
android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
<!-- Remove permission from Firebase dependency -->
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
|
||||||
tools:node="remove" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
@ -39,6 +43,7 @@
|
|||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:preserveLegacyExternalStorage="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
@ -46,13 +51,53 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
android:theme="@style/Theme.Tachiyomi.SplashScreen">
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Deep link to add repos -->
|
||||||
|
<intent-filter android:label="@string/action_add_repo">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="tachiyomi" />
|
||||||
|
<data android:host="add-repo" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Open backup files -->
|
||||||
|
<intent-filter android:label="@string/pref_restore_backup">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="file" />
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:host="*" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
<!--
|
||||||
|
Work around Android's ugly primitive PatternMatcher
|
||||||
|
implementation that can't cope with finding a . early in
|
||||||
|
the path unless it's explicitly matched.
|
||||||
|
|
||||||
|
See https://stackoverflow.com/a/31028507
|
||||||
|
-->
|
||||||
|
<data android:pathPattern=".*\\.tachibk" />
|
||||||
|
<data android:pathPattern=".*\\..*\\.tachibk" />
|
||||||
|
<data android:pathPattern=".*\\..*\\..*\\.tachibk" />
|
||||||
|
<data android:pathPattern=".*\\..*\\..*\\..*\\.tachibk" />
|
||||||
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.tachibk" />
|
||||||
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.tachibk" />
|
||||||
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.tachibk" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<!--suppress AndroidDomInspection -->
|
<!--suppress AndroidDomInspection -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
@ -60,16 +105,16 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:process=":error_handler"
|
|
||||||
android:name=".crash.CrashActivity"
|
android:name=".crash.CrashActivity"
|
||||||
android:exported="false" />
|
android:exported="false"
|
||||||
|
android:process=":error_handler" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.deeplink.DeepLinkActivity"
|
android:name=".ui.deeplink.DeepLinkActivity"
|
||||||
android:launchMode="singleTask"
|
android:exported="true"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
|
||||||
android:label="@string/action_search"
|
android:label="@string/action_search"
|
||||||
android:exported="true">
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||||
@ -93,20 +138,21 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.reader.ReaderActivity"
|
android:name=".ui.reader.ReaderActivity"
|
||||||
android:launchMode="singleTask"
|
android:exported="false"
|
||||||
android:exported="false">
|
android:launchMode="singleTask">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
<meta-data
|
||||||
android:resource="@xml/s_pen_actions"/>
|
android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||||
|
android:resource="@xml/s_pen_actions" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.security.UnlockActivity"
|
android:name=".ui.security.UnlockActivity"
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
android:exported="false"
|
||||||
android:exported="false" />
|
android:theme="@style/Theme.Tachiyomi" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.webview.WebViewActivity"
|
android:name=".ui.webview.WebViewActivity"
|
||||||
@ -115,25 +161,25 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".extension.util.ExtensionInstallActivity"
|
android:name=".extension.util.ExtensionInstallActivity"
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
android:exported="false"
|
||||||
android:exported="false" />
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.TrackLoginActivity"
|
android:name=".ui.setting.track.TrackLoginActivity"
|
||||||
android:label="@string/track_activity_name"
|
android:exported="true"
|
||||||
android:exported="true">
|
android:label="@string/track_activity_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data android:host="anilist-auth"/>
|
<data android:scheme="tachiyomi" />
|
||||||
<data android:host="bangumi-auth"/>
|
|
||||||
<data android:host="myanimelist-auth"/>
|
|
||||||
<data android:host="shikimori-auth"/>
|
|
||||||
|
|
||||||
<data android:scheme="tachiyomi"/>
|
<data android:host="anilist-auth" />
|
||||||
|
<data android:host="bangumi-auth" />
|
||||||
|
<data android:host="myanimelist-auth" />
|
||||||
|
<data android:host="shikimori-auth" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
@ -141,13 +187,10 @@
|
|||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".data.download.DownloadService"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".extension.util.ExtensionInstallService"
|
android:name=".extension.util.ExtensionInstallService"
|
||||||
android:exported="false" />
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="shortService" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
@ -158,6 +201,11 @@
|
|||||||
android:value="true" />
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
|
tools:node="merge" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
@ -171,9 +219,9 @@
|
|||||||
<provider
|
<provider
|
||||||
android:name="rikka.shizuku.ShizukuProvider"
|
android:name="rikka.shizuku.ShizukuProvider"
|
||||||
android:authorities="${applicationId}.shizuku"
|
android:authorities="${applicationId}.shizuku"
|
||||||
android:multiprocess="false"
|
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:multiprocess="false"
|
||||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
@ -183,11 +231,6 @@
|
|||||||
android:name="android.webkit.WebView.MetricsOptOut"
|
android:name="android.webkit.WebView.MetricsOptOut"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
<!-- Disable advertising ID collection for Firebase -->
|
|
||||||
<meta-data
|
|
||||||
android:name="google_analytics_adid_collection_enabled"
|
|
||||||
android:value="false" />
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
package eu.kanade.domain
|
package eu.kanade.domain
|
||||||
|
|
||||||
|
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
|
||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
|
import eu.kanade.domain.extension.interactor.CreateExtensionRepo
|
||||||
|
import eu.kanade.domain.extension.interactor.DeleteExtensionRepo
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
|
import eu.kanade.domain.extension.interactor.GetExtensionRepos
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
|
import eu.kanade.domain.extension.interactor.TrustExtension
|
||||||
|
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||||
|
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
@ -112,6 +119,8 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { NetworkToLocalManga(get()) }
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
addFactory { UpdateManga(get(), get()) }
|
addFactory { UpdateManga(get(), get()) }
|
||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
|
addFactory { GetExcludedScanlators(get()) }
|
||||||
|
addFactory { SetExcludedScanlators(get()) }
|
||||||
|
|
||||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||||
addFactory { GetApplicationRelease(get(), get()) }
|
addFactory { GetApplicationRelease(get(), get()) }
|
||||||
@ -133,7 +142,8 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { UpdateChapter(get()) }
|
addFactory { UpdateChapter(get()) }
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||||
addFactory { ShouldUpdateDbChapter() }
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
|
addFactory { GetAvailableScanlators(get()) }
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
addFactory { GetHistory(get()) }
|
addFactory { GetHistory(get()) }
|
||||||
@ -161,5 +171,10 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { ToggleLanguage(get()) }
|
addFactory { ToggleLanguage(get()) }
|
||||||
addFactory { ToggleSource(get()) }
|
addFactory { ToggleSource(get()) }
|
||||||
addFactory { ToggleSourcePin(get()) }
|
addFactory { ToggleSourcePin(get()) }
|
||||||
|
addFactory { TrustExtension(get()) }
|
||||||
|
|
||||||
|
addFactory { CreateExtensionRepo(get()) }
|
||||||
|
addFactory { DeleteExtensionRepo(get()) }
|
||||||
|
addFactory { GetExtensionRepos(get()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package eu.kanade.domain.base
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.StringRes
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
|
||||||
import tachiyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
class BasePreferences(
|
class BasePreferences(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
@ -22,12 +20,12 @@ class BasePreferences(
|
|||||||
|
|
||||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||||
|
|
||||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||||
|
|
||||||
enum class ExtensionInstaller(@StringRes val titleResId: Int) {
|
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
|
||||||
LEGACY(R.string.ext_installer_legacy),
|
LEGACY(MR.strings.ext_installer_legacy, true),
|
||||||
PACKAGEINSTALLER(R.string.ext_installer_packageinstaller),
|
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller, true),
|
||||||
SHIZUKU(R.string.ext_installer_shizuku),
|
SHIZUKU(MR.strings.ext_installer_shizuku, false),
|
||||||
PRIVATE(R.string.ext_installer_private),
|
PRIVATE(MR.strings.ext_installer_private, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
|
||||||
|
class GetAvailableScanlators(
|
||||||
|
private val repository: ChapterRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private fun List<String>.cleanupAvailableScanlators(): Set<String> {
|
||||||
|
return mapNotNull { it.ifBlank { null } }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long): Set<String> {
|
||||||
|
return repository.getScanlatorsByMangaId(mangaId)
|
||||||
|
.cleanupAvailableScanlators()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||||
|
return repository.getScanlatorsByMangaIdAsFlow(mangaId)
|
||||||
|
.map { it.cleanupAvailableScanlators() }
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package eu.kanade.domain.chapter.interactor
|
|||||||
|
|
||||||
import eu.kanade.domain.chapter.model.copyFromSChapter
|
import eu.kanade.domain.chapter.model.copyFromSChapter
|
||||||
import eu.kanade.domain.chapter.model.toSChapter
|
import eu.kanade.domain.chapter.model.toSChapter
|
||||||
|
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
@ -22,7 +23,6 @@ import tachiyomi.domain.manga.model.Manga
|
|||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
|
|
||||||
class SyncChaptersWithSource(
|
class SyncChaptersWithSource(
|
||||||
@ -33,6 +33,7 @@ class SyncChaptersWithSource(
|
|||||||
private val updateManga: UpdateManga,
|
private val updateManga: UpdateManga,
|
||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
private val getExcludedScanlators: GetExcludedScanlators,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,6 +56,7 @@ class SyncChaptersWithSource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val now = ZonedDateTime.now()
|
val now = ZonedDateTime.now()
|
||||||
|
val nowMillis = now.toInstant().toEpochMilli()
|
||||||
|
|
||||||
val sourceChapters = rawSourceChapters
|
val sourceChapters = rawSourceChapters
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }
|
||||||
@ -65,36 +67,27 @@ class SyncChaptersWithSource(
|
|||||||
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters from db.
|
|
||||||
val dbChapters = getChaptersByMangaId.await(manga.id)
|
val dbChapters = getChaptersByMangaId.await(manga.id)
|
||||||
|
|
||||||
// Chapters from the source not in db.
|
val newChapters = mutableListOf<Chapter>()
|
||||||
val toAdd = mutableListOf<Chapter>()
|
val updatedChapters = mutableListOf<Chapter>()
|
||||||
|
val removedChapters = dbChapters.filterNot { dbChapter ->
|
||||||
// Chapters whose metadata have changed.
|
|
||||||
val toChange = mutableListOf<Chapter>()
|
|
||||||
|
|
||||||
// Chapters from the db not in source.
|
|
||||||
val toDelete = dbChapters.filterNot { dbChapter ->
|
|
||||||
sourceChapters.any { sourceChapter ->
|
sourceChapters.any { sourceChapter ->
|
||||||
dbChapter.url == sourceChapter.url
|
dbChapter.url == sourceChapter.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val rightNow = Date().time
|
|
||||||
|
|
||||||
// Used to not set upload date of older chapters
|
// Used to not set upload date of older chapters
|
||||||
// to a higher value than newer chapters
|
// to a higher value than newer chapters
|
||||||
var maxSeenUploadDate = 0L
|
var maxSeenUploadDate = 0L
|
||||||
|
|
||||||
val sManga = manga.toSManga()
|
|
||||||
for (sourceChapter in sourceChapters) {
|
for (sourceChapter in sourceChapters) {
|
||||||
var chapter = sourceChapter
|
var chapter = sourceChapter
|
||||||
|
|
||||||
// Update metadata from source if necessary.
|
// Update metadata from source if necessary.
|
||||||
if (source is HttpSource) {
|
if (source is HttpSource) {
|
||||||
val sChapter = chapter.toSChapter()
|
val sChapter = chapter.toSChapter()
|
||||||
source.prepareNewChapter(sChapter, sManga)
|
source.prepareNewChapter(sChapter, manga.toSManga())
|
||||||
chapter = chapter.copyFromSChapter(sChapter)
|
chapter = chapter.copyFromSChapter(sChapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,17 +99,19 @@ class SyncChaptersWithSource(
|
|||||||
|
|
||||||
if (dbChapter == null) {
|
if (dbChapter == null) {
|
||||||
val toAddChapter = if (chapter.dateUpload == 0L) {
|
val toAddChapter = if (chapter.dateUpload == 0L) {
|
||||||
val altDateUpload = if (maxSeenUploadDate == 0L) rightNow else maxSeenUploadDate
|
val altDateUpload = if (maxSeenUploadDate == 0L) nowMillis else maxSeenUploadDate
|
||||||
chapter.copy(dateUpload = altDateUpload)
|
chapter.copy(dateUpload = altDateUpload)
|
||||||
} else {
|
} else {
|
||||||
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
||||||
chapter
|
chapter
|
||||||
}
|
}
|
||||||
toAdd.add(toAddChapter)
|
newChapters.add(toAddChapter)
|
||||||
} else {
|
} else {
|
||||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
||||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
downloadManager.isChapterDownloaded(
|
||||||
|
dbChapter.name, dbChapter.scanlator, manga.title, manga.source,
|
||||||
|
)
|
||||||
|
|
||||||
if (shouldRenameChapter) {
|
if (shouldRenameChapter) {
|
||||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||||
@ -130,13 +125,13 @@ class SyncChaptersWithSource(
|
|||||||
if (chapter.dateUpload != 0L) {
|
if (chapter.dateUpload != 0L) {
|
||||||
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
||||||
}
|
}
|
||||||
toChange.add(toChangeChapter)
|
updatedChapters.add(toChangeChapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
// Return if there's nothing to add, delete, or update to avoid unnecessary db transactions.
|
||||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
if (newChapters.isEmpty() && removedChapters.isEmpty() && updatedChapters.isEmpty()) {
|
||||||
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||||
updateManga.awaitUpdateFetchInterval(
|
updateManga.awaitUpdateFetchInterval(
|
||||||
manga,
|
manga,
|
||||||
@ -153,20 +148,20 @@ class SyncChaptersWithSource(
|
|||||||
val deletedReadChapterNumbers = TreeSet<Double>()
|
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||||
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||||
|
|
||||||
toDelete.forEach { chapter ->
|
removedChapters.forEach { chapter ->
|
||||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||||
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
||||||
deletedChapterNumbers.add(chapter.chapterNumber)
|
deletedChapterNumbers.add(chapter.chapterNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
val deletedChapterNumberDateFetchMap = toDelete.sortedByDescending { it.dateFetch }
|
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
||||||
.associate { it.chapterNumber to it.dateFetch }
|
.associate { it.chapterNumber to it.dateFetch }
|
||||||
|
|
||||||
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
var itemCount = toAdd.size
|
var itemCount = newChapters.size
|
||||||
var updatedToAdd = toAdd.map { toAddItem ->
|
var updatedToAdd = newChapters.map { toAddItem ->
|
||||||
var chapter = toAddItem.copy(dateFetch = rightNow + itemCount--)
|
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||||
|
|
||||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||||
|
|
||||||
@ -185,8 +180,8 @@ class SyncChaptersWithSource(
|
|||||||
chapter
|
chapter
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toDelete.isNotEmpty()) {
|
if (removedChapters.isNotEmpty()) {
|
||||||
val toDeleteIds = toDelete.map { it.id }
|
val toDeleteIds = removedChapters.map { it.id }
|
||||||
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,8 +189,8 @@ class SyncChaptersWithSource(
|
|||||||
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toChange.isNotEmpty()) {
|
if (updatedChapters.isNotEmpty()) {
|
||||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() }
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
}
|
}
|
||||||
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||||
@ -206,6 +201,10 @@ class SyncChaptersWithSource(
|
|||||||
|
|
||||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
||||||
|
|
||||||
return updatedToAdd.filterNot { it.url in reAddedUrls }
|
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
||||||
|
|
||||||
|
return updatedToAdd.filterNot {
|
||||||
|
it.url in reAddedUrls || it.scanlator in excludedScanlators
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
|||||||
url = sChapter.url,
|
url = sChapter.url,
|
||||||
dateUpload = sChapter.date_upload,
|
dateUpload = sChapter.date_upload,
|
||||||
chapterNumber = sChapter.chapter_number.toDouble(),
|
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,12 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager):
|
|||||||
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||||
.filter { chapter ->
|
.filter { chapter ->
|
||||||
applyFilter(downloadedFilter) {
|
applyFilter(downloadedFilter) {
|
||||||
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
|
val downloaded = downloadManager.isChapterDownloaded(
|
||||||
|
chapter.name,
|
||||||
|
chapter.scanlator,
|
||||||
|
manga.title,
|
||||||
|
manga.source,
|
||||||
|
)
|
||||||
downloaded || isLocalManga
|
downloaded || isLocalManga
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import tachiyomi.core.preference.plusAssign
|
||||||
|
|
||||||
|
class CreateExtensionRepo(private val preferences: SourcePreferences) {
|
||||||
|
|
||||||
|
fun await(name: String): Result {
|
||||||
|
// Do not allow invalid formats
|
||||||
|
if (!name.matches(repoRegex)) {
|
||||||
|
return Result.InvalidUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences.extensionRepos() += name.removeSuffix("/index.min.json")
|
||||||
|
|
||||||
|
return Result.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Result {
|
||||||
|
data object InvalidUrl : Result
|
||||||
|
data object Success : Result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val repoRegex = """^https://.*/index\.min\.json$""".toRegex()
|
@ -0,0 +1,11 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import tachiyomi.core.preference.minusAssign
|
||||||
|
|
||||||
|
class DeleteExtensionRepo(private val preferences: SourcePreferences) {
|
||||||
|
|
||||||
|
fun await(repo: String) {
|
||||||
|
preferences.extensionRepos() -= repo
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class GetExtensionRepos(private val preferences: SourcePreferences) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<Set<String>> {
|
||||||
|
return preferences.extensionRepos().changes()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
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 tachiyomi.core.preference.getAndSet
|
||||||
|
|
||||||
|
class TrustExtension(
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean {
|
||||||
|
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash"
|
||||||
|
return key in preferences.trustedExtensions().get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
|
||||||
|
preferences.trustedExtensions().getAndSet { exts ->
|
||||||
|
// Remove previously trusted versions
|
||||||
|
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
|
||||||
|
|
||||||
|
removed.also {
|
||||||
|
it += "$pkgName:$versionCode:$signatureHash"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun revokeAll() {
|
||||||
|
preferences.trustedExtensions().delete()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import tachiyomi.data.DatabaseHandler
|
||||||
|
|
||||||
|
class GetExcludedScanlators(
|
||||||
|
private val handler: DatabaseHandler,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long): Set<String> {
|
||||||
|
return handler.awaitList {
|
||||||
|
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||||
|
}
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||||
|
return handler.subscribeToList {
|
||||||
|
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||||
|
}
|
||||||
|
.map { it.toSet() }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
|
import tachiyomi.data.DatabaseHandler
|
||||||
|
|
||||||
|
class SetExcludedScanlators(
|
||||||
|
private val handler: DatabaseHandler,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
|
||||||
|
handler.await(inTransaction = true) {
|
||||||
|
val currentExcluded = handler.awaitList {
|
||||||
|
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||||
|
}.toSet()
|
||||||
|
val toAdd = excludedScanlators.minus(currentExcluded)
|
||||||
|
for (scanlator in toAdd) {
|
||||||
|
excluded_scanlatorsQueries.insert(mangaId, scanlator)
|
||||||
|
}
|
||||||
|
val toRemove = currentExcluded.minus(excludedScanlators)
|
||||||
|
excluded_scanlatorsQueries.remove(mangaId, toRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ class SetMangaViewerFlags(
|
|||||||
mangaRepository.update(
|
mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
id = id,
|
||||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingModeType.MASK.toLong()),
|
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingMode.MASK.toLong()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ class SetMangaViewerFlags(
|
|||||||
mangaRepository.update(
|
mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
id = id,
|
||||||
viewerFlags = manga.viewerFlags.setFlag(flag, OrientationType.MASK.toLong()),
|
viewerFlags = manga.viewerFlags.setFlag(flag, ReaderOrientation.MASK.toLong()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import tachiyomi.domain.manga.repository.MangaRepository
|
|||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.time.Instant
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class UpdateManga(
|
class UpdateManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
@ -46,14 +46,14 @@ class UpdateManga(
|
|||||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||||
localManga.isLocal() -> Date().time
|
localManga.isLocal() -> Instant.now().toEpochMilli()
|
||||||
localManga.hasCustomCover(coverCache) -> {
|
localManga.hasCustomCover(coverCache) -> {
|
||||||
coverCache.deleteFromCache(localManga, false)
|
coverCache.deleteFromCache(localManga, false)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
coverCache.deleteFromCache(localManga, false)
|
coverCache.deleteFromCache(localManga, false)
|
||||||
Date().time
|
Instant.now().toEpochMilli()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,22 +81,22 @@ class UpdateManga(
|
|||||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||||
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return fetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
|
return mangaRepository.update(
|
||||||
?.let { mangaRepository.update(it) }
|
fetchInterval.toMangaUpdate(manga, dateTime, window),
|
||||||
?: false
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||||
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Instant.now().toEpochMilli()))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
||||||
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Date().time))
|
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Instant.now().toEpochMilli()))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
||||||
val dateAdded = when (favorite) {
|
val dateAdded = when (favorite) {
|
||||||
true -> Date().time
|
true -> Instant.now().toEpochMilli()
|
||||||
false -> 0
|
false -> 0
|
||||||
}
|
}
|
||||||
return mangaRepository.update(
|
return mangaRepository.update(
|
||||||
|
@ -3,8 +3,8 @@ package eu.kanade.domain.manga.model
|
|||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||||
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
@ -14,11 +14,11 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
// TODO: move these into the domain model
|
// TODO: move these into the domain model
|
||||||
val Manga.readingModeType: Long
|
val Manga.readingMode: Long
|
||||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
get() = viewerFlags and ReadingMode.MASK.toLong()
|
||||||
|
|
||||||
val Manga.orientationType: Long
|
val Manga.readerOrientation: Long
|
||||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
get() = viewerFlags and ReaderOrientation.MASK.toLong()
|
||||||
|
|
||||||
val Manga.downloadedFilter: TriState
|
val Manga.downloadedFilter: TriState
|
||||||
get() {
|
get() {
|
||||||
|
@ -11,7 +11,12 @@ class SourcePreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun sourceDisplayMode() = preferenceStore.getObject("pref_display_mode_catalogue", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
fun sourceDisplayMode() = preferenceStore.getObject(
|
||||||
|
"pref_display_mode_catalogue",
|
||||||
|
LibraryDisplayMode.default,
|
||||||
|
LibraryDisplayMode.Serializer::serialize,
|
||||||
|
LibraryDisplayMode.Serializer::deserialize,
|
||||||
|
)
|
||||||
|
|
||||||
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
||||||
|
|
||||||
@ -28,11 +33,19 @@ class SourcePreferences(
|
|||||||
|
|
||||||
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
||||||
|
|
||||||
fun migrationSortingDirection() = preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING)
|
fun migrationSortingDirection() = preferenceStore.getEnum(
|
||||||
|
"pref_migration_direction",
|
||||||
|
SetMigrateSorting.Direction.ASCENDING,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||||
|
|
||||||
|
fun extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet())
|
||||||
|
|
||||||
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||||
|
|
||||||
fun trustedSignatures() = preferenceStore.getStringSet(Preference.appStateKey("trusted_signatures"), emptySet())
|
fun trustedExtensions() = preferenceStore.getStringSet(
|
||||||
|
Preference.appStateKey("trusted_extensions"),
|
||||||
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
emptySet(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,10 @@ class AddTracks(
|
|||||||
?.readAt
|
?.readAt
|
||||||
|
|
||||||
firstReadChapterDate?.let {
|
firstReadChapterDate?.let {
|
||||||
val startDate = firstReadChapterDate.time.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
|
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
||||||
|
ZoneOffset.systemDefault(),
|
||||||
|
ZoneOffset.UTC,
|
||||||
|
)
|
||||||
track = track.copy(
|
track = track.copy(
|
||||||
startDate = startDate,
|
startDate = startDate,
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ class RefreshTracks(
|
|||||||
suspend fun await(mangaId: Long): List<Pair<Tracker?, Throwable>> {
|
suspend fun await(mangaId: Long): List<Pair<Tracker?, Throwable>> {
|
||||||
return supervisorScope {
|
return supervisorScope {
|
||||||
return@supervisorScope getTracks.await(mangaId)
|
return@supervisorScope getTracks.await(mangaId)
|
||||||
.map { it to trackerManager.get(it.syncId) }
|
.map { it to trackerManager.get(it.trackerId) }
|
||||||
.filter { (_, service) -> service?.isLoggedIn == true }
|
.filter { (_, service) -> service?.isLoggedIn == true }
|
||||||
.map { (track, service) ->
|
.map { (track, service) ->
|
||||||
async {
|
async {
|
||||||
|
@ -27,7 +27,7 @@ class TrackChapter(
|
|||||||
if (tracks.isEmpty()) return@withNonCancellableContext
|
if (tracks.isEmpty()) return@withNonCancellableContext
|
||||||
|
|
||||||
tracks.mapNotNull { track ->
|
tracks.mapNotNull { track ->
|
||||||
val service = trackerManager.get(track.syncId)
|
val service = trackerManager.get(track.trackerId)
|
||||||
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
|
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,10 @@ fun Track.copyPersonalFrom(other: Track): Track {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId).also {
|
fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also {
|
||||||
it.id = id
|
it.id = id
|
||||||
it.manga_id = mangaId
|
it.manga_id = mangaId
|
||||||
it.media_id = remoteId
|
it.remote_id = remoteId
|
||||||
it.library_id = libraryId
|
it.library_id = libraryId
|
||||||
it.title = title
|
it.title = title
|
||||||
it.last_chapter_read = lastChapterRead.toFloat()
|
it.last_chapter_read = lastChapterRead.toFloat()
|
||||||
@ -33,14 +33,16 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
|||||||
return Track(
|
return Track(
|
||||||
id = trackId,
|
id = trackId,
|
||||||
mangaId = manga_id,
|
mangaId = manga_id,
|
||||||
syncId = sync_id.toLong(),
|
trackerId = tracker_id.toLong(),
|
||||||
remoteId = media_id,
|
remoteId = remote_id,
|
||||||
libraryId = library_id,
|
libraryId = library_id,
|
||||||
title = title,
|
title = title,
|
||||||
lastChapterRead = last_chapter_read.toDouble(),
|
lastChapterRead = last_chapter_read.toDouble(),
|
||||||
totalChapters = total_chapters.toLong(),
|
totalChapters = total_chapters.toLong(),
|
||||||
status = status.toLong(),
|
status = status.toLong(),
|
||||||
score = score.toDouble(),
|
// Jank workaround due to precision issues while converting
|
||||||
|
// See https://github.com/tachiyomiorg/tachiyomi/issues/10343
|
||||||
|
score = score.toString().toDouble(),
|
||||||
remoteUrl = tracking_url,
|
remoteUrl = tracking_url,
|
||||||
startDate = started_reading_date,
|
startDate = started_reading_date,
|
||||||
finishDate = finished_reading_date,
|
finishDate = finished_reading_date,
|
||||||
|
@ -17,8 +17,7 @@ import tachiyomi.core.util.system.logcat
|
|||||||
import tachiyomi.domain.track.interactor.GetTracks
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.time.toJavaDuration
|
|
||||||
|
|
||||||
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
CoroutineWorker(context, workerParams) {
|
CoroutineWorker(context, workerParams) {
|
||||||
@ -43,7 +42,9 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
|
|||||||
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||||
}
|
}
|
||||||
.forEach { track ->
|
.forEach { track ->
|
||||||
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}" }
|
logcat(LogPriority.DEBUG) {
|
||||||
|
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
||||||
|
}
|
||||||
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,7 +62,7 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
|
|||||||
|
|
||||||
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
|
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5.minutes.toJavaDuration())
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -9,26 +9,24 @@ class TrackPreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun trackUsername(sync: Tracker) = preferenceStore.getString(trackUsername(sync.id), "")
|
fun trackUsername(tracker: Tracker) = preferenceStore.getString(
|
||||||
|
Preference.privateKey("pref_mangasync_username_${tracker.id}"),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
fun trackPassword(sync: Tracker) = preferenceStore.getString(trackPassword(sync.id), "")
|
fun trackPassword(tracker: Tracker) = preferenceStore.getString(
|
||||||
|
Preference.privateKey("pref_mangasync_password_${tracker.id}"),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
fun setCredentials(sync: Tracker, username: String, password: String) {
|
fun setCredentials(tracker: Tracker, username: String, password: String) {
|
||||||
trackUsername(sync).set(username)
|
trackUsername(tracker).set(username)
|
||||||
trackPassword(sync).set(password)
|
trackPassword(tracker).set(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trackToken(sync: Tracker) = preferenceStore.getString(trackToken(sync.id), "")
|
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
|
||||||
|
|
||||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||||
|
|
||||||
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun trackUsername(syncId: Long) = Preference.privateKey("pref_mangasync_username_$syncId")
|
|
||||||
|
|
||||||
private fun trackPassword(syncId: Long) = Preference.privateKey("pref_mangasync_password_$syncId")
|
|
||||||
|
|
||||||
private fun trackToken(syncId: Long) = Preference.privateKey("track_token_$syncId")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.domain.ui
|
package eu.kanade.domain.ui
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
import eu.kanade.domain.ui.model.ThemeMode
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
@ -15,7 +16,10 @@ class UiPreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun themeMode() = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
|
fun themeMode() = preferenceStore.getEnum(
|
||||||
|
"pref_theme_mode_key",
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ThemeMode.SYSTEM } else { ThemeMode.LIGHT },
|
||||||
|
)
|
||||||
|
|
||||||
fun appTheme() = preferenceStore.getEnum(
|
fun appTheme() = preferenceStore.getEnum(
|
||||||
"pref_app_theme",
|
"pref_app_theme",
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
package eu.kanade.domain.ui.model
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
enum class AppTheme(val titleResId: Int?) {
|
enum class AppTheme(val titleRes: StringResource?) {
|
||||||
DEFAULT(R.string.label_default),
|
DEFAULT(MR.strings.label_default),
|
||||||
MONET(R.string.theme_monet),
|
MONET(MR.strings.theme_monet),
|
||||||
GREEN_APPLE(R.string.theme_greenapple),
|
GREEN_APPLE(MR.strings.theme_greenapple),
|
||||||
LAVENDER(R.string.theme_lavender),
|
LAVENDER(MR.strings.theme_lavender),
|
||||||
MIDNIGHT_DUSK(R.string.theme_midnightdusk),
|
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
||||||
STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
|
|
||||||
TAKO(R.string.theme_tako),
|
// TODO: re-enable for preview
|
||||||
TEALTURQUOISE(R.string.theme_tealturquoise),
|
NORD(MR.strings.theme_nord.takeIf { isDevFlavor || isPreviewBuildType }),
|
||||||
TIDAL_WAVE(R.string.theme_tidalwave),
|
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
||||||
YINYANG(R.string.theme_yinyang),
|
TAKO(MR.strings.theme_tako),
|
||||||
YOTSUBA(R.string.theme_yotsuba),
|
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
||||||
|
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
||||||
|
YINYANG(MR.strings.theme_yinyang),
|
||||||
|
YOTSUBA(MR.strings.theme_yotsuba),
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
DARK_BLUE(null),
|
DARK_BLUE(null),
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package eu.kanade.domain.ui.model
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
enum class TabletUiMode(val titleResId: Int) {
|
enum class TabletUiMode(val titleRes: StringResource) {
|
||||||
AUTOMATIC(R.string.automatic_background),
|
AUTOMATIC(MR.strings.automatic_background),
|
||||||
ALWAYS(R.string.lock_always),
|
ALWAYS(MR.strings.lock_always),
|
||||||
LANDSCAPE(R.string.landscape),
|
LANDSCAPE(MR.strings.landscape),
|
||||||
NEVER(R.string.lock_never),
|
NEVER(MR.strings.lock_never),
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
@ -14,7 +14,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
||||||
@ -22,13 +21,16 @@ import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
|||||||
import eu.kanade.presentation.browse.components.BrowseSourceList
|
import eu.kanade.presentation.browse.components.BrowseSourceList
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.util.formattedMessage
|
import eu.kanade.presentation.util.formattedMessage
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import tachiyomi.core.i18n.stringResource
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.source.model.StubSource
|
import tachiyomi.domain.source.model.StubSource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
@ -61,7 +63,7 @@ fun BrowseSourceContent(
|
|||||||
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
val result = snackbarHostState.showSnackbar(
|
val result = snackbarHostState.showSnackbar(
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actionLabel = context.getString(R.string.action_retry),
|
actionLabel = context.stringResource(MR.strings.action_retry),
|
||||||
duration = SnackbarDuration.Indefinite,
|
duration = SnackbarDuration.Indefinite,
|
||||||
)
|
)
|
||||||
when (result) {
|
when (result) {
|
||||||
@ -76,28 +78,28 @@ fun BrowseSourceContent(
|
|||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actions = if (source is LocalSource) {
|
actions = if (source is LocalSource) {
|
||||||
listOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.local_source_help_guide,
|
stringRes = MR.strings.local_source_help_guide,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onLocalSourceHelpClick,
|
onClick = onLocalSourceHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
listOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_retry,
|
stringRes = MR.strings.action_retry,
|
||||||
icon = Icons.Outlined.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
onClick = mangaList::refresh,
|
onClick = mangaList::refresh,
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_open_in_web_view,
|
stringRes = MR.strings.action_open_in_web_view,
|
||||||
icon = Icons.Outlined.Public,
|
icon = Icons.Outlined.Public,
|
||||||
onClick = onWebViewClick,
|
onClick = onWebViewClick,
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.label_help,
|
stringRes = MR.strings.label_help,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -145,7 +147,7 @@ fun BrowseSourceContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MissingSourceScreen(
|
internal fun MissingSourceScreen(
|
||||||
source: StubSource,
|
source: StubSource,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -159,7 +161,7 @@ fun MissingSourceScreen(
|
|||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
message = stringResource(R.string.source_not_installed, source.toString()),
|
message = stringResource(MR.strings.source_not_installed, source.toString()),
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||||
import androidx.compose.material.icons.outlined.History
|
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
@ -38,7 +37,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@ -50,14 +49,17 @@ import eu.kanade.presentation.components.AppBarActions
|
|||||||
import eu.kanade.presentation.components.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
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.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -65,55 +67,61 @@ fun ExtensionDetailsScreen(
|
|||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: ExtensionDetailsScreenModel.State,
|
state: ExtensionDetailsScreenModel.State,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickWhatsNew: () -> Unit,
|
|
||||||
onClickReadme: () -> Unit,
|
|
||||||
onClickEnableAll: () -> Unit,
|
onClickEnableAll: () -> Unit,
|
||||||
onClickDisableAll: () -> Unit,
|
onClickDisableAll: () -> Unit,
|
||||||
onClickClearCookies: () -> Unit,
|
onClickClearCookies: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
val url = remember(state.extension) {
|
||||||
|
val regex = """https://raw.githubusercontent.com/(.+?)/(.+?)/.+""".toRegex()
|
||||||
|
regex.find(state.extension?.repoUrl.orEmpty())
|
||||||
|
?.let {
|
||||||
|
val (user, repo) = it.destructured
|
||||||
|
"https://github.com/$user/$repo"
|
||||||
|
}
|
||||||
|
?: state.extension?.repoUrl
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.label_extension_info),
|
title = stringResource(MR.strings.label_extension_info),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = buildList {
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
if (state.extension?.isUnofficial == false) {
|
.apply {
|
||||||
|
if (url != null) {
|
||||||
add(
|
add(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.whats_new),
|
title = stringResource(MR.strings.action_open_repo),
|
||||||
icon = Icons.Outlined.History,
|
icon = Icons.AutoMirrored.Outlined.Launch,
|
||||||
onClick = onClickWhatsNew,
|
onClick = {
|
||||||
),
|
uriHandler.openUri(url)
|
||||||
)
|
},
|
||||||
add(
|
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(R.string.action_faq_and_guides),
|
|
||||||
icon = Icons.Outlined.HelpOutline,
|
|
||||||
onClick = onClickReadme,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addAll(
|
addAll(
|
||||||
listOf(
|
listOf(
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_enable_all),
|
title = stringResource(MR.strings.action_enable_all),
|
||||||
onClick = onClickEnableAll,
|
onClick = onClickEnableAll,
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_disable_all),
|
title = stringResource(MR.strings.action_disable_all),
|
||||||
onClick = onClickDisableAll,
|
onClick = onClickDisableAll,
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.pref_clear_cookies),
|
title = stringResource(MR.strings.pref_clear_cookies),
|
||||||
onClick = onClickClearCookies,
|
onClick = onClickClearCookies,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
|
.build(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
@ -122,7 +130,7 @@ fun ExtensionDetailsScreen(
|
|||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
if (state.extension == null) {
|
if (state.extension == null) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.empty_screen,
|
MR.strings.empty_screen,
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
@ -143,7 +151,7 @@ fun ExtensionDetailsScreen(
|
|||||||
private fun ExtensionDetails(
|
private fun ExtensionDetails(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
extension: Extension.Installed,
|
extension: Extension.Installed,
|
||||||
sources: List<ExtensionSourceItem>,
|
sources: ImmutableList<ExtensionSourceItem>,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
@ -154,14 +162,9 @@ private fun ExtensionDetails(
|
|||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
when {
|
if (extension.isObsolete) {
|
||||||
extension.isUnofficial ->
|
|
||||||
item {
|
item {
|
||||||
WarningBanner(R.string.unofficial_extension_message)
|
WarningBanner(MR.strings.obsolete_extension_message)
|
||||||
}
|
|
||||||
extension.isObsolete ->
|
|
||||||
item {
|
|
||||||
WarningBanner(R.string.obsolete_extension_message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +261,7 @@ private fun DetailsHeader(
|
|||||||
InfoText(
|
InfoText(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
primaryText = extension.versionName,
|
primaryText = extension.versionName,
|
||||||
secondaryText = stringResource(R.string.ext_info_version),
|
secondaryText = stringResource(MR.strings.ext_info_version),
|
||||||
)
|
)
|
||||||
|
|
||||||
InfoDivider()
|
InfoDivider()
|
||||||
@ -266,7 +269,7 @@ private fun DetailsHeader(
|
|||||||
InfoText(
|
InfoText(
|
||||||
modifier = Modifier.weight(if (extension.isNsfw) 1.5f else 1f),
|
modifier = Modifier.weight(if (extension.isNsfw) 1.5f else 1f),
|
||||||
primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context),
|
primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context),
|
||||||
secondaryText = stringResource(R.string.ext_info_language),
|
secondaryText = stringResource(MR.strings.ext_info_language),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (extension.isNsfw) {
|
if (extension.isNsfw) {
|
||||||
@ -274,12 +277,12 @@ private fun DetailsHeader(
|
|||||||
|
|
||||||
InfoText(
|
InfoText(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
primaryText = stringResource(R.string.ext_nsfw_short),
|
primaryText = stringResource(MR.strings.ext_nsfw_short),
|
||||||
primaryTextStyle = MaterialTheme.typography.bodyLarge.copy(
|
primaryTextStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
),
|
),
|
||||||
secondaryText = stringResource(R.string.ext_info_age_rating),
|
secondaryText = stringResource(MR.strings.ext_info_age_rating),
|
||||||
onClick = onClickAgeRating,
|
onClick = onClickAgeRating,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -292,13 +295,13 @@ private fun DetailsHeader(
|
|||||||
top = MaterialTheme.padding.small,
|
top = MaterialTheme.padding.small,
|
||||||
bottom = MaterialTheme.padding.medium,
|
bottom = MaterialTheme.padding.medium,
|
||||||
),
|
),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
onClick = onClickUninstall,
|
onClick = onClickUninstall,
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.ext_uninstall))
|
Text(stringResource(MR.strings.ext_uninstall))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onClickAppInfo != null) {
|
if (onClickAppInfo != null) {
|
||||||
@ -307,7 +310,7 @@ private fun DetailsHeader(
|
|||||||
onClick = onClickAppInfo,
|
onClick = onClickAppInfo,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.ext_app_info),
|
text = stringResource(MR.strings.ext_app_info),
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -320,10 +323,10 @@ private fun DetailsHeader(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoText(
|
private fun InfoText(
|
||||||
modifier: Modifier,
|
|
||||||
primaryText: String,
|
primaryText: String,
|
||||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
|
||||||
secondaryText: String,
|
secondaryText: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
@ -363,10 +366,10 @@ private fun InfoDivider() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceSwitchPreference(
|
private fun SourceSwitchPreference(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: ExtensionSourceItem,
|
source: ExtensionSourceItem,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -385,7 +388,7 @@ private fun SourceSwitchPreference(
|
|||||||
IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
|
IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Settings,
|
imageVector = Icons.Outlined.Settings,
|
||||||
contentDescription = stringResource(R.string.label_settings),
|
contentDescription = stringResource(MR.strings.label_settings),
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -408,11 +411,11 @@ private fun NsfwWarningDialog(
|
|||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.ext_nsfw_warning))
|
Text(text = stringResource(MR.strings.ext_nsfw_warning))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onClickConfirm) {
|
TextButton(onClick = onClickConfirm) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismissRequest = onClickConfirm,
|
onDismissRequest = onClickConfirm,
|
||||||
|
@ -7,13 +7,13 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -25,7 +25,7 @@ fun ExtensionFilterScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.label_extensions),
|
title = stringResource(MR.strings.label_extensions),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@ -33,7 +33,7 @@ fun ExtensionFilterScreen(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.empty_screen,
|
stringRes = MR.strings.empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -15,6 +15,11 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material.icons.outlined.GetApp
|
||||||
|
import androidx.compose.material.icons.outlined.Public
|
||||||
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
|
import androidx.compose.material.icons.outlined.VerifiedUser
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
@ -33,23 +38,32 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
||||||
import eu.kanade.presentation.browse.components.ExtensionIcon
|
import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||||
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||||
|
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
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.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import tachiyomi.presentation.core.theme.header
|
import tachiyomi.presentation.core.theme.header
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
@ -62,6 +76,7 @@ fun ExtensionScreen(
|
|||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
onOpenWebView: (Extension.Available) -> Unit,
|
||||||
onInstallExtension: (Extension.Available) -> Unit,
|
onInstallExtension: (Extension.Available) -> Unit,
|
||||||
onUninstallExtension: (Extension) -> Unit,
|
onUninstallExtension: (Extension) -> Unit,
|
||||||
onUpdateExtension: (Extension.Installed) -> Unit,
|
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||||
@ -70,22 +85,31 @@ fun ExtensionScreen(
|
|||||||
onClickUpdateAll: () -> Unit,
|
onClickUpdateAll: () -> Unit,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.isRefreshing,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !state.isLoading,
|
enabled = { !state.isLoading },
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> {
|
state.isEmpty -> {
|
||||||
val msg = if (!searchQuery.isNullOrEmpty()) {
|
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||||
R.string.no_results_found
|
MR.strings.no_results_found
|
||||||
} else {
|
} else {
|
||||||
R.string.empty_screen
|
MR.strings.empty_screen
|
||||||
}
|
}
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = msg,
|
stringRes = msg,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
actions = persistentListOf(
|
||||||
|
EmptyScreenAction(
|
||||||
|
stringRes = MR.strings.label_extension_repos,
|
||||||
|
icon = Icons.Outlined.Settings,
|
||||||
|
onClick = { navigator.push(ExtensionReposScreen()) },
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@ -94,6 +118,7 @@ fun ExtensionScreen(
|
|||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
onClickItemCancel = onClickItemCancel,
|
onClickItemCancel = onClickItemCancel,
|
||||||
|
onOpenWebView = onOpenWebView,
|
||||||
onInstallExtension = onInstallExtension,
|
onInstallExtension = onInstallExtension,
|
||||||
onUninstallExtension = onUninstallExtension,
|
onUninstallExtension = onUninstallExtension,
|
||||||
onUpdateExtension = onUpdateExtension,
|
onUpdateExtension = onUpdateExtension,
|
||||||
@ -112,6 +137,7 @@ private fun ExtensionContent(
|
|||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
onOpenWebView: (Extension.Available) -> Unit,
|
||||||
onInstallExtension: (Extension.Available) -> Unit,
|
onInstallExtension: (Extension.Available) -> Unit,
|
||||||
onUninstallExtension: (Extension) -> Unit,
|
onUninstallExtension: (Extension) -> Unit,
|
||||||
onUpdateExtension: (Extension.Installed) -> Unit,
|
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||||
@ -119,11 +145,24 @@ private fun ExtensionContent(
|
|||||||
onOpenExtension: (Extension.Installed) -> Unit,
|
onOpenExtension: (Extension.Installed) -> Unit,
|
||||||
onClickUpdateAll: () -> Unit,
|
onClickUpdateAll: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
||||||
|
val installGranted = rememberRequestPackageInstallsPermissionState(initialValue = true)
|
||||||
|
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding + topSmallPaddingValues,
|
contentPadding = contentPadding + topSmallPaddingValues,
|
||||||
) {
|
) {
|
||||||
|
if (!installGranted && state.installer?.requiresSystemPermission == true) {
|
||||||
|
item(key = "extension-permissions-warning") {
|
||||||
|
WarningBanner(
|
||||||
|
textRes = MR.strings.ext_permission_install_apps_warning,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
context.launchRequestPackageInstallsPermission()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state.items.forEach { (header, items) ->
|
state.items.forEach { (header, items) ->
|
||||||
item(
|
item(
|
||||||
contentType = "header",
|
contentType = "header",
|
||||||
@ -132,11 +171,11 @@ private fun ExtensionContent(
|
|||||||
when (header) {
|
when (header) {
|
||||||
is ExtensionUiModel.Header.Resource -> {
|
is ExtensionUiModel.Header.Resource -> {
|
||||||
val action: @Composable RowScope.() -> Unit =
|
val action: @Composable RowScope.() -> Unit =
|
||||||
if (header.textRes == R.string.ext_updates_pending) {
|
if (header.textRes == MR.strings.ext_updates_pending) {
|
||||||
{
|
{
|
||||||
Button(onClick = { onClickUpdateAll() }) {
|
Button(onClick = { onClickUpdateAll() }) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.ext_update_all),
|
text = stringResource(MR.strings.ext_update_all),
|
||||||
style = LocalTextStyle.current.copy(
|
style = LocalTextStyle.current.copy(
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
@ -177,6 +216,13 @@ private fun ExtensionContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
|
onClickItemSecondaryAction = {
|
||||||
|
when (it) {
|
||||||
|
is Extension.Available -> onOpenWebView(it)
|
||||||
|
is Extension.Installed -> onOpenExtension(it)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
},
|
||||||
onClickItemCancel = onClickItemCancel,
|
onClickItemCancel = onClickItemCancel,
|
||||||
onClickItemAction = {
|
onClickItemAction = {
|
||||||
when (it) {
|
when (it) {
|
||||||
@ -214,12 +260,13 @@ private fun ExtensionContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionItem(
|
private fun ExtensionItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
item: ExtensionUiModel.Item,
|
item: ExtensionUiModel.Item,
|
||||||
onClickItem: (Extension) -> Unit,
|
onClickItem: (Extension) -> Unit,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
onClickItemAction: (Extension) -> Unit,
|
onClickItemAction: (Extension) -> Unit,
|
||||||
|
onClickItemSecondaryAction: (Extension) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val (extension, installStep) = item
|
val (extension, installStep) = item
|
||||||
BaseBrowseItem(
|
BaseBrowseItem(
|
||||||
@ -262,6 +309,7 @@ private fun ExtensionItem(
|
|||||||
installStep = installStep,
|
installStep = installStep,
|
||||||
onClickItemCancel = onClickItemCancel,
|
onClickItemCancel = onClickItemCancel,
|
||||||
onClickItemAction = onClickItemAction,
|
onClickItemAction = onClickItemAction,
|
||||||
|
onClickItemSecondaryAction = onClickItemSecondaryAction,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -291,7 +339,7 @@ private fun ExtensionItemContent(
|
|||||||
// Won't look good but it's not like we can ellipsize overflowing content
|
// Won't look good but it's not like we can ellipsize overflowing content
|
||||||
FlowRow(
|
FlowRow(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
) {
|
) {
|
||||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||||
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
||||||
@ -307,10 +355,9 @@ private fun ExtensionItemContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val warning = when {
|
val warning = when {
|
||||||
extension is Extension.Untrusted -> R.string.ext_untrusted
|
extension is Extension.Untrusted -> MR.strings.ext_untrusted
|
||||||
extension is Extension.Installed && extension.isUnofficial -> R.string.ext_unofficial
|
extension is Extension.Installed && extension.isObsolete -> MR.strings.ext_obsolete
|
||||||
extension is Extension.Installed && extension.isObsolete -> R.string.ext_obsolete
|
extension.isNsfw -> MR.strings.ext_nsfw_short
|
||||||
extension.isNsfw -> R.string.ext_nsfw_short
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (warning != null) {
|
if (warning != null) {
|
||||||
@ -326,9 +373,9 @@ private fun ExtensionItemContent(
|
|||||||
DotSeparatorNoSpaceText()
|
DotSeparatorNoSpaceText()
|
||||||
Text(
|
Text(
|
||||||
text = when (installStep) {
|
text = when (installStep) {
|
||||||
InstallStep.Pending -> stringResource(R.string.ext_pending)
|
InstallStep.Pending -> stringResource(MR.strings.ext_pending)
|
||||||
InstallStep.Downloading -> stringResource(R.string.ext_downloading)
|
InstallStep.Downloading -> stringResource(MR.strings.ext_downloading)
|
||||||
InstallStep.Installing -> stringResource(R.string.ext_installing)
|
InstallStep.Installing -> stringResource(MR.strings.ext_installing)
|
||||||
else -> error("Must not show non-install process text")
|
else -> error("Must not show non-install process text")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -345,48 +392,86 @@ private fun ExtensionItemActions(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickItemCancel: (Extension) -> Unit = {},
|
onClickItemCancel: (Extension) -> Unit = {},
|
||||||
onClickItemAction: (Extension) -> Unit = {},
|
onClickItemAction: (Extension) -> Unit = {},
|
||||||
|
onClickItemSecondaryAction: (Extension) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val isIdle = installStep.isCompleted()
|
val isIdle = installStep.isCompleted()
|
||||||
Row(modifier = modifier) {
|
|
||||||
if (isIdle) {
|
Row(
|
||||||
TextButton(
|
modifier = modifier,
|
||||||
onClick = { onClickItemAction(extension) },
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
Text(
|
when {
|
||||||
text = when (installStep) {
|
!isIdle -> {
|
||||||
InstallStep.Installed -> stringResource(R.string.ext_installed)
|
|
||||||
InstallStep.Error -> stringResource(R.string.action_retry)
|
|
||||||
InstallStep.Idle -> {
|
|
||||||
when (extension) {
|
|
||||||
is Extension.Installed -> {
|
|
||||||
if (extension.hasUpdate) {
|
|
||||||
stringResource(R.string.ext_update)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.action_settings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Extension.Untrusted -> stringResource(R.string.ext_trust)
|
|
||||||
is Extension.Available -> stringResource(R.string.ext_install)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> error("Must not show install process text")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_cancel),
|
contentDescription = stringResource(MR.strings.action_cancel),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
installStep == InstallStep.Error -> {
|
||||||
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Refresh,
|
||||||
|
contentDescription = stringResource(MR.strings.action_retry),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installStep == InstallStep.Idle -> {
|
||||||
|
when (extension) {
|
||||||
|
is Extension.Installed -> {
|
||||||
|
IconButton(onClick = { onClickItemSecondaryAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Settings,
|
||||||
|
contentDescription = stringResource(MR.strings.action_settings),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension.hasUpdate) {
|
||||||
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.GetApp,
|
||||||
|
contentDescription = stringResource(MR.strings.ext_update),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Extension.Untrusted -> {
|
||||||
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.VerifiedUser,
|
||||||
|
contentDescription = stringResource(MR.strings.ext_trust),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Extension.Available -> {
|
||||||
|
if (extension.sources.isNotEmpty()) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { onClickItemSecondaryAction(extension) },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Public,
|
||||||
|
contentDescription = stringResource(MR.strings.action_open_in_web_view),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.GetApp,
|
||||||
|
contentDescription = stringResource(MR.strings.ext_install),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionHeader(
|
private fun ExtensionHeader(
|
||||||
@StringRes textRes: Int,
|
textRes: StringResource,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
action: @Composable RowScope.() -> Unit = {},
|
action: @Composable RowScope.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
@ -426,19 +511,19 @@ private fun ExtensionTrustDialog(
|
|||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.untrusted_extension))
|
Text(text = stringResource(MR.strings.untrusted_extension))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.untrusted_extension_message))
|
Text(text = stringResource(MR.strings.untrusted_extension_message))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onClickConfirm) {
|
TextButton(onClick = onClickConfirm) {
|
||||||
Text(text = stringResource(R.string.ext_trust))
|
Text(text = stringResource(MR.strings.ext_trust))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onClickDismiss) {
|
TextButton(onClick = onClickDismiss) {
|
||||||
Text(text = stringResource(R.string.ext_uninstall))
|
Text(text = stringResource(MR.strings.ext_uninstall))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
@ -60,13 +60,13 @@ fun GlobalSearchScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun GlobalSearchContent(
|
internal fun GlobalSearchContent(
|
||||||
fromSourceId: Long? = null,
|
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
items: Map<CatalogueSource, SearchItemResult>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onLongClickItem: (Manga) -> Unit,
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
fromSourceId: Long? = null,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
@ -74,8 +74,10 @@ internal fun GlobalSearchContent(
|
|||||||
items.forEach { (source, result) ->
|
items.forEach { (source, result) ->
|
||||||
item(key = source.id) {
|
item(key = source.id) {
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
title = fromSourceId?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
|
title = fromSourceId?.let {
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
"▶ ${source.name}".takeIf { source.id == fromSourceId }
|
||||||
|
} ?: source.name,
|
||||||
|
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
) {
|
) {
|
||||||
when (result) {
|
when (result) {
|
||||||
|
@ -7,9 +7,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
@ -33,7 +33,7 @@ fun MigrateMangaScreen(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.empty_screen,
|
stringRes = MR.strings.empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
@ -70,10 +70,10 @@ private fun MigrateMangaContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateMangaItem(
|
private fun MigrateMangaItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseMangaListItem(
|
BaseMangaListItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -20,21 +20,22 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.Badge
|
import tachiyomi.presentation.core.components.Badge
|
||||||
import tachiyomi.presentation.core.components.BadgeGroup
|
import tachiyomi.presentation.core.components.BadgeGroup
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import tachiyomi.presentation.core.theme.header
|
import tachiyomi.presentation.core.theme.header
|
||||||
@ -53,7 +54,7 @@ fun MigrateSourceScreen(
|
|||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.information_empty_library,
|
stringRes = MR.strings.information_empty_library,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
else ->
|
else ->
|
||||||
@ -75,7 +76,7 @@ fun MigrateSourceScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateSourceList(
|
private fun MigrateSourceList(
|
||||||
list: List<Pair<Source, Long>>,
|
list: ImmutableList<Pair<Source, Long>>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
@ -95,21 +96,33 @@ private fun MigrateSourceList(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.migration_selection_prompt),
|
text = stringResource(MR.strings.migration_selection_prompt),
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
style = MaterialTheme.typography.header,
|
style = MaterialTheme.typography.header,
|
||||||
)
|
)
|
||||||
|
|
||||||
IconButton(onClick = onToggleSortingMode) {
|
IconButton(onClick = onToggleSortingMode) {
|
||||||
when (sortingMode) {
|
when (sortingMode) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(Icons.Outlined.SortByAlpha, contentDescription = stringResource(R.string.action_sort_alpha))
|
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(
|
||||||
SetMigrateSorting.Mode.TOTAL -> Icon(Icons.Outlined.Numbers, contentDescription = stringResource(R.string.action_sort_count))
|
Icons.Outlined.SortByAlpha,
|
||||||
|
contentDescription = stringResource(MR.strings.action_sort_alpha),
|
||||||
|
)
|
||||||
|
SetMigrateSorting.Mode.TOTAL -> Icon(
|
||||||
|
Icons.Outlined.Numbers,
|
||||||
|
contentDescription = stringResource(MR.strings.action_sort_count),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IconButton(onClick = onToggleSortingDirection) {
|
IconButton(onClick = onToggleSortingDirection) {
|
||||||
when (sortingDirection) {
|
when (sortingDirection) {
|
||||||
SetMigrateSorting.Direction.ASCENDING -> Icon(Icons.Outlined.ArrowUpward, contentDescription = stringResource(R.string.action_asc))
|
SetMigrateSorting.Direction.ASCENDING -> Icon(
|
||||||
SetMigrateSorting.Direction.DESCENDING -> Icon(Icons.Outlined.ArrowDownward, contentDescription = stringResource(R.string.action_desc))
|
Icons.Outlined.ArrowUpward,
|
||||||
|
contentDescription = stringResource(MR.strings.action_asc),
|
||||||
|
)
|
||||||
|
SetMigrateSorting.Direction.DESCENDING -> Icon(
|
||||||
|
Icons.Outlined.ArrowDownward,
|
||||||
|
contentDescription = stringResource(MR.strings.action_desc),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,11 +145,11 @@ private fun MigrateSourceList(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateSourceItem(
|
private fun MigrateSourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
count: Long,
|
count: Long,
|
||||||
onClickItem: () -> Unit,
|
onClickItem: () -> Unit,
|
||||||
onLongClickItem: () -> Unit,
|
onLongClickItem: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseSourceItem(
|
BaseSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -178,7 +191,7 @@ private fun MigrateSourceItem(
|
|||||||
if (source.isStub) {
|
if (source.isStub) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
text = stringResource(R.string.not_installed),
|
text = stringResource(MR.strings.not_installed),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
@ -7,16 +7,16 @@ import androidx.compose.material3.Checkbox
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -29,7 +29,7 @@ fun SourcesFilterScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.label_sources),
|
title = stringResource(MR.strings.label_sources),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@ -37,7 +37,7 @@ fun SourcesFilterScreen(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.source_filter_empty_screen,
|
stringRes = MR.strings.source_filter_empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
@ -94,10 +94,10 @@ private fun SourcesFilterContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourcesFilterHeader(
|
private fun SourcesFilterHeader(
|
||||||
modifier: Modifier,
|
|
||||||
language: String,
|
language: String,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onClickItem: (String) -> Unit,
|
onClickItem: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -109,10 +109,10 @@ private fun SourcesFilterHeader(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourcesFilterItem(
|
private fun SourcesFilterItem(
|
||||||
modifier: Modifier,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseSourceItem(
|
BaseSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -19,19 +19,19 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
|
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.browse.BrowseSourceScreenModel.Listing
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Pin
|
import tachiyomi.domain.source.model.Pin
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import tachiyomi.presentation.core.theme.header
|
import tachiyomi.presentation.core.theme.header
|
||||||
@ -49,7 +49,7 @@ fun SourcesScreen(
|
|||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.source_empty_screen,
|
stringRes = MR.strings.source_empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
else -> {
|
else -> {
|
||||||
@ -94,8 +94,8 @@ fun SourcesScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceHeader(
|
private fun SourceHeader(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
language: String,
|
language: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Text(
|
Text(
|
||||||
@ -108,11 +108,11 @@ private fun SourceHeader(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceItem(
|
private fun SourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
onClickItem: (Source, Listing) -> Unit,
|
onClickItem: (Source, Listing) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
onClickPin: (Source) -> Unit,
|
onClickPin: (Source) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseSourceItem(
|
BaseSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -123,7 +123,7 @@ private fun SourceItem(
|
|||||||
if (source.supportsLatest) {
|
if (source.supportsLatest) {
|
||||||
TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
|
TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.latest),
|
text = stringResource(MR.strings.latest),
|
||||||
style = LocalTextStyle.current.copy(
|
style = LocalTextStyle.current.copy(
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
@ -144,8 +144,14 @@ private fun SourcePinButton(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
||||||
val tint = if (isPinned) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground.copy(alpha = SecondaryItemAlpha)
|
val tint = if (isPinned) {
|
||||||
val description = if (isPinned) R.string.action_unpin else R.string.action_pin
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = SecondaryItemAlpha,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
|
||||||
IconButton(onClick = onClick) {
|
IconButton(onClick = onClick) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
@ -168,7 +174,7 @@ fun SourceOptionsDialog(
|
|||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
val textId = if (Pin.Pinned in source.pin) R.string.action_unpin else R.string.action_pin
|
val textId = if (Pin.Pinned in source.pin) MR.strings.action_unpin else MR.strings.action_pin
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(textId),
|
text = stringResource(textId),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -178,7 +184,7 @@ fun SourceOptionsDialog(
|
|||||||
)
|
)
|
||||||
if (!source.isLocal()) {
|
if (!source.isLocal()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.action_disable),
|
text = stringResource(MR.strings.action_disable),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(onClick = onClickDisable)
|
.clickable(onClick = onClickDisable)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
@ -16,8 +16,8 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BaseSourceItem(
|
fun BaseSourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
showLanguageInContent: Boolean = true,
|
showLanguageInContent: Boolean = true,
|
||||||
onClickItem: () -> Unit = {},
|
onClickItem: () -> Unit = {},
|
||||||
onLongClickItem: () -> Unit = {},
|
onLongClickItem: () -> Unit = {},
|
||||||
@ -25,7 +25,9 @@ fun BaseSourceItem(
|
|||||||
action: @Composable RowScope.(Source) -> Unit = {},
|
action: @Composable RowScope.(Source) -> Unit = {},
|
||||||
content: @Composable RowScope.(Source, String?) -> Unit = defaultContent,
|
content: @Composable RowScope.(Source, String?) -> Unit = defaultContent,
|
||||||
) {
|
) {
|
||||||
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf { showLanguageInContent }
|
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf {
|
||||||
|
showLanguageInContent
|
||||||
|
}
|
||||||
BaseBrowseItem(
|
BaseBrowseItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
@ -4,11 +4,9 @@ import androidx.compose.material3.AlertDialog
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RemoveMangaDialog(
|
fun RemoveMangaDialog(
|
||||||
@ -20,7 +18,7 @@ fun RemoveMangaDialog(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -30,14 +28,14 @@ fun RemoveMangaDialog(
|
|||||||
onConfirm()
|
onConfirm()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_remove))
|
Text(text = stringResource(MR.strings.action_remove))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.are_you_sure))
|
Text(text = stringResource(MR.strings.are_you_sure))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.remove_manga, mangaToRemove.title))
|
Text(text = stringResource(MR.strings.remove_manga, mangaToRemove.title))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.presentation.browse.components
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ViewList
|
import androidx.compose.material.icons.automirrored.filled.ViewList
|
||||||
import androidx.compose.material.icons.filled.ViewModule
|
import androidx.compose.material.icons.filled.ViewModule
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
@ -10,17 +10,18 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.AppBarTitle
|
import eu.kanade.presentation.components.AppBarTitle
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.presentation.components.RadioMenuItem
|
import eu.kanade.presentation.components.RadioMenuItem
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.source.local.LocalSource
|
import tachiyomi.source.local.LocalSource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -53,50 +54,66 @@ fun BrowseSourceToolbar(
|
|||||||
onClickCloseSearch = navigateUp,
|
onClickCloseSearch = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = listOfNotNull(
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
|
.apply {
|
||||||
|
add(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_display_mode),
|
title = stringResource(MR.strings.action_display_mode),
|
||||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
|
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||||
|
Icons.AutoMirrored.Filled.ViewList
|
||||||
|
} else {
|
||||||
|
Icons.Filled.ViewModule
|
||||||
|
},
|
||||||
onClick = { selectingDisplayMode = true },
|
onClick = { selectingDisplayMode = true },
|
||||||
),
|
),
|
||||||
|
)
|
||||||
if (isLocalSource) {
|
if (isLocalSource) {
|
||||||
|
add(
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.label_help),
|
title = stringResource(MR.strings.label_help),
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
add(
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_open_in_web_view),
|
title = stringResource(MR.strings.action_open_in_web_view),
|
||||||
onClick = onWebViewClick,
|
onClick = onWebViewClick,
|
||||||
)
|
|
||||||
},
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_settings),
|
|
||||||
onClick = onSettingsClick,
|
|
||||||
).takeIf { isConfigurableSource },
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
if (isConfigurableSource) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_settings),
|
||||||
|
onClick = onSettingsClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = selectingDisplayMode,
|
expanded = selectingDisplayMode,
|
||||||
onDismissRequest = { selectingDisplayMode = false },
|
onDismissRequest = { selectingDisplayMode = false },
|
||||||
) {
|
) {
|
||||||
RadioMenuItem(
|
RadioMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_display_comfortable_grid)) },
|
text = { Text(text = stringResource(MR.strings.action_display_comfortable_grid)) },
|
||||||
isChecked = displayMode == LibraryDisplayMode.ComfortableGrid,
|
isChecked = displayMode == LibraryDisplayMode.ComfortableGrid,
|
||||||
) {
|
) {
|
||||||
selectingDisplayMode = false
|
selectingDisplayMode = false
|
||||||
onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
|
onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
|
||||||
}
|
}
|
||||||
RadioMenuItem(
|
RadioMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_display_grid)) },
|
text = { Text(text = stringResource(MR.strings.action_display_grid)) },
|
||||||
isChecked = displayMode == LibraryDisplayMode.CompactGrid,
|
isChecked = displayMode == LibraryDisplayMode.CompactGrid,
|
||||||
) {
|
) {
|
||||||
selectingDisplayMode = false
|
selectingDisplayMode = false
|
||||||
onDisplayModeChange(LibraryDisplayMode.CompactGrid)
|
onDisplayModeChange(LibraryDisplayMode.CompactGrid)
|
||||||
}
|
}
|
||||||
RadioMenuItem(
|
RadioMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_display_list)) },
|
text = { Text(text = stringResource(MR.strings.action_display_list)) },
|
||||||
isChecked = displayMode == LibraryDisplayMode.List,
|
isChecked = displayMode == LibraryDisplayMode.List,
|
||||||
) {
|
) {
|
||||||
selectingDisplayMode = false
|
selectingDisplayMode = false
|
||||||
|
@ -13,15 +13,15 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import tachiyomi.domain.manga.model.asMangaCover
|
import tachiyomi.domain.manga.model.asMangaCover
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchCardRow(
|
fun GlobalSearchCardRow(
|
||||||
@ -37,7 +37,7 @@ fun GlobalSearchCardRow(
|
|||||||
|
|
||||||
LazyRow(
|
LazyRow(
|
||||||
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
) {
|
) {
|
||||||
items(titles) {
|
items(titles) {
|
||||||
val title by getManga(it)
|
val title by getManga(it)
|
||||||
@ -78,7 +78,7 @@ private fun MangaItem(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun EmptyResultItem() {
|
private fun EmptyResultItem() {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.no_results_found),
|
text = stringResource(MR.strings.no_results_found),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
horizontal = MaterialTheme.padding.medium,
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.ArrowForward
|
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
||||||
import androidx.compose.material.icons.outlined.Error
|
import androidx.compose.material.icons.outlined.Error
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -21,11 +21,11 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchResultItem(
|
fun GlobalSearchResultItem(
|
||||||
@ -39,7 +39,7 @@ fun GlobalSearchResultItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
start = MaterialTheme.padding.medium,
|
start = MaterialTheme.padding.medium,
|
||||||
end = MaterialTheme.padding.tiny,
|
end = MaterialTheme.padding.extraSmall,
|
||||||
)
|
)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick),
|
.clickable(onClick = onClick),
|
||||||
@ -54,7 +54,7 @@ fun GlobalSearchResultItem(
|
|||||||
Text(text = subtitle)
|
Text(text = subtitle)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onClick) {
|
IconButton(onClick = onClick) {
|
||||||
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
|
Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content()
|
content()
|
||||||
@ -92,7 +92,7 @@ fun GlobalSearchErrorResultItem(message: String?) {
|
|||||||
Icon(imageVector = Icons.Outlined.Error, contentDescription = null)
|
Icon(imageVector = Icons.Outlined.Error, contentDescription = null)
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = message ?: stringResource(R.string.unknown_error),
|
text = message ?: stringResource(MR.strings.unknown_error),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,11 @@ import androidx.compose.material3.VerticalDivider
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchToolbar(
|
fun GlobalSearchToolbar(
|
||||||
@ -58,7 +58,7 @@ fun GlobalSearchToolbar(
|
|||||||
)
|
)
|
||||||
if (progress in 1..<total) {
|
if (progress in 1..<total) {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = progress / total.toFloat(),
|
progress = { progress / total.toFloat() },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomStart)
|
.align(Alignment.BottomStart)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
@ -85,7 +85,7 @@ fun GlobalSearchToolbar(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(id = R.string.pinned_sources))
|
Text(text = stringResource(MR.strings.pinned_sources))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
FilterChip(
|
FilterChip(
|
||||||
@ -100,7 +100,7 @@ fun GlobalSearchToolbar(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(id = R.string.all))
|
Text(text = stringResource(MR.strings.all))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ fun GlobalSearchToolbar(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(id = R.string.has_results))
|
Text(text = stringResource(MR.strings.has_results))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,20 @@ package eu.kanade.presentation.category
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import tachiyomi.core.i18n.stringResource
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
val Category.visualName: String
|
val Category.visualName: String
|
||||||
@Composable
|
@Composable
|
||||||
get() = when {
|
get() = when {
|
||||||
isSystemCategory -> stringResource(R.string.label_default)
|
isSystemCategory -> stringResource(MR.strings.label_default)
|
||||||
else -> name
|
else -> name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Category.visualName(context: Context): String =
|
fun Category.visualName(context: Context): String =
|
||||||
when {
|
when {
|
||||||
isSystemCategory -> context.getString(R.string.label_default)
|
isSystemCategory -> context.stringResource(MR.strings.label_default)
|
||||||
else -> name
|
else -> name
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,18 @@ import androidx.compose.material.icons.outlined.SortByAlpha
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
||||||
import eu.kanade.presentation.category.components.CategoryListItem
|
import eu.kanade.presentation.category.components.CategoryListItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreenState
|
import eu.kanade.tachiyomi.ui.category.CategoryScreenState
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
@ -41,13 +42,13 @@ fun CategoryScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.action_edit_categories),
|
title = stringResource(MR.strings.action_edit_categories),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
persistentListOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_sort),
|
title = stringResource(MR.strings.action_sort),
|
||||||
icon = Icons.Outlined.SortByAlpha,
|
icon = Icons.Outlined.SortByAlpha,
|
||||||
onClick = onClickSortAlphabetically,
|
onClick = onClickSortAlphabetically,
|
||||||
),
|
),
|
||||||
@ -66,7 +67,7 @@ fun CategoryScreen(
|
|||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.information_empty_category,
|
stringRes = MR.strings.information_empty_category,
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
@ -75,7 +76,9 @@ fun CategoryScreen(
|
|||||||
CategoryContent(
|
CategoryContent(
|
||||||
categories = state.categories,
|
categories = state.categories,
|
||||||
lazyListState = lazyListState,
|
lazyListState = lazyListState,
|
||||||
paddingValues = paddingValues + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
|
paddingValues = paddingValues +
|
||||||
|
topSmallPaddingValues +
|
||||||
|
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||||
onClickRename = onClickRename,
|
onClickRename = onClickRename,
|
||||||
onClickDelete = onClickDelete,
|
onClickDelete = onClickDelete,
|
||||||
onMoveUp = onClickMoveUp,
|
onMoveUp = onClickMoveUp,
|
||||||
|
@ -25,26 +25,28 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.core.preference.asToggleableState
|
import eu.kanade.core.preference.asToggleableState
|
||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
import eu.kanade.tachiyomi.R
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import tachiyomi.core.preference.CheckboxState
|
import tachiyomi.core.preference.CheckboxState
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryCreateDialog(
|
fun CategoryCreateDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onCreate: (String) -> Unit,
|
onCreate: (String) -> Unit,
|
||||||
categories: List<Category>,
|
categories: ImmutableList<String>,
|
||||||
) {
|
) {
|
||||||
var name by remember { mutableStateOf("") }
|
var name by remember { mutableStateOf("") }
|
||||||
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val nameAlreadyExists = remember(name) { categories.anyWithName(name) }
|
val nameAlreadyExists = remember(name) { categories.contains(name) }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
@ -56,25 +58,32 @@ fun CategoryCreateDialog(
|
|||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_add))
|
Text(text = stringResource(MR.strings.action_add))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.action_add_category))
|
Text(text = stringResource(MR.strings.action_add_category))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.focusRequester(focusRequester),
|
modifier = Modifier
|
||||||
|
.focusRequester(focusRequester),
|
||||||
value = name,
|
value = name,
|
||||||
onValueChange = { name = it },
|
onValueChange = { name = it },
|
||||||
label = { Text(text = stringResource(R.string.name)) },
|
label = {
|
||||||
|
Text(text = stringResource(MR.strings.name))
|
||||||
|
},
|
||||||
supportingText = {
|
supportingText = {
|
||||||
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) R.string.error_category_exists else R.string.information_required_plain
|
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) {
|
||||||
|
MR.strings.error_category_exists
|
||||||
|
} else {
|
||||||
|
MR.strings.information_required_plain
|
||||||
|
}
|
||||||
Text(text = stringResource(msgRes))
|
Text(text = stringResource(msgRes))
|
||||||
},
|
},
|
||||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||||
@ -94,14 +103,14 @@ fun CategoryCreateDialog(
|
|||||||
fun CategoryRenameDialog(
|
fun CategoryRenameDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onRename: (String) -> Unit,
|
onRename: (String) -> Unit,
|
||||||
categories: List<Category>,
|
categories: ImmutableList<String>,
|
||||||
category: Category,
|
category: String,
|
||||||
) {
|
) {
|
||||||
var name by remember { mutableStateOf(category.name) }
|
var name by remember { mutableStateOf(category) }
|
||||||
var valueHasChanged by remember { mutableStateOf(false) }
|
var valueHasChanged by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val nameAlreadyExists = remember(name) { categories.anyWithName(name) }
|
val nameAlreadyExists = remember(name) { categories.contains(name) }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
@ -113,16 +122,16 @@ fun CategoryRenameDialog(
|
|||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.action_rename_category))
|
Text(text = stringResource(MR.strings.action_rename_category))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@ -132,9 +141,13 @@ fun CategoryRenameDialog(
|
|||||||
valueHasChanged = name != it
|
valueHasChanged = name != it
|
||||||
name = it
|
name = it
|
||||||
},
|
},
|
||||||
label = { Text(text = stringResource(R.string.name)) },
|
label = { Text(text = stringResource(MR.strings.name)) },
|
||||||
supportingText = {
|
supportingText = {
|
||||||
val msgRes = if (valueHasChanged && nameAlreadyExists) R.string.error_category_exists else R.string.information_required_plain
|
val msgRes = if (valueHasChanged && nameAlreadyExists) {
|
||||||
|
MR.strings.error_category_exists
|
||||||
|
} else {
|
||||||
|
MR.strings.information_required_plain
|
||||||
|
}
|
||||||
Text(text = stringResource(msgRes))
|
Text(text = stringResource(msgRes))
|
||||||
},
|
},
|
||||||
isError = valueHasChanged && nameAlreadyExists,
|
isError = valueHasChanged && nameAlreadyExists,
|
||||||
@ -154,7 +167,7 @@ fun CategoryRenameDialog(
|
|||||||
fun CategoryDeleteDialog(
|
fun CategoryDeleteDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
category: Category,
|
category: String,
|
||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
@ -163,19 +176,19 @@ fun CategoryDeleteDialog(
|
|||||||
onDelete()
|
onDelete()
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.delete_category))
|
Text(text = stringResource(MR.strings.delete_category))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.delete_category_confirmation, category.name))
|
Text(text = stringResource(MR.strings.delete_category_confirmation, category))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -192,26 +205,26 @@ fun CategorySortAlphabeticallyDialog(
|
|||||||
onSort()
|
onSort()
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.action_sort_category))
|
Text(text = stringResource(MR.strings.action_sort_category))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.sort_category_confirmation))
|
Text(text = stringResource(MR.strings.sort_category_confirmation))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChangeCategoryDialog(
|
fun ChangeCategoryDialog(
|
||||||
initialSelection: List<CheckboxState<Category>>,
|
initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onEditCategories: () -> Unit,
|
onEditCategories: () -> Unit,
|
||||||
onConfirm: (List<Long>, List<Long>) -> Unit,
|
onConfirm: (List<Long>, List<Long>) -> Unit,
|
||||||
@ -226,14 +239,14 @@ fun ChangeCategoryDialog(
|
|||||||
onEditCategories()
|
onEditCategories()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_edit_categories))
|
Text(text = stringResource(MR.strings.action_edit_categories))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.action_move_category))
|
Text(text = stringResource(MR.strings.action_move_category))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.information_empty_category_dialog))
|
Text(text = stringResource(MR.strings.information_empty_category_dialog))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -247,27 +260,31 @@ fun ChangeCategoryDialog(
|
|||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
onEditCategories()
|
onEditCategories()
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(R.string.action_edit))
|
Text(text = stringResource(MR.strings.action_edit))
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
tachiyomi.presentation.core.components.material.TextButton(onClick = onDismissRequest) {
|
tachiyomi.presentation.core.components.material.TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
tachiyomi.presentation.core.components.material.TextButton(
|
tachiyomi.presentation.core.components.material.TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
onConfirm(
|
onConfirm(
|
||||||
selection.filter { it is CheckboxState.State.Checked || it is CheckboxState.TriState.Include }.map { it.value.id },
|
selection
|
||||||
selection.filter { it is CheckboxState.State.None || it is CheckboxState.TriState.None }.map { it.value.id },
|
.filter { it is CheckboxState.State.Checked || it is CheckboxState.TriState.Include }
|
||||||
|
.map { it.value.id },
|
||||||
|
selection
|
||||||
|
.filter { it is CheckboxState.State.None || it is CheckboxState.TriState.None }
|
||||||
|
.map { it.value.id },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.action_move_category))
|
Text(text = stringResource(MR.strings.action_move_category))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
@ -279,7 +296,7 @@ fun ChangeCategoryDialog(
|
|||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
val mutableList = selection.toMutableList()
|
val mutableList = selection.toMutableList()
|
||||||
mutableList[index] = it.next()
|
mutableList[index] = it.next()
|
||||||
selection = mutableList.toList()
|
selection = mutableList.toList().toImmutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
@ -313,7 +330,3 @@ fun ChangeCategoryDialog(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Category>.anyWithName(name: String): Boolean {
|
|
||||||
return any { name == it.name }
|
|
||||||
}
|
|
||||||
|
@ -6,9 +6,10 @@ import androidx.compose.material.icons.outlined.Add
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.tachiyomi.R
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrollingUp
|
import tachiyomi.presentation.core.util.isScrollingUp
|
||||||
|
|
||||||
@ -16,11 +17,13 @@ import tachiyomi.presentation.core.util.isScrollingUp
|
|||||||
fun CategoryFloatingActionButton(
|
fun CategoryFloatingActionButton(
|
||||||
lazyListState: LazyListState,
|
lazyListState: LazyListState,
|
||||||
onCreate: () -> Unit,
|
onCreate: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = { Text(text = stringResource(R.string.action_add)) },
|
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") },
|
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||||
onClick = onCreate,
|
onClick = onCreate,
|
||||||
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,11 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||||
import androidx.compose.material.icons.outlined.ArrowDropDown
|
import androidx.compose.material.icons.outlined.ArrowDropDown
|
||||||
import androidx.compose.material.icons.outlined.ArrowDropUp
|
import androidx.compose.material.icons.outlined.ArrowDropUp
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
import androidx.compose.material.icons.outlined.Label
|
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@ -19,14 +19,13 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryListItem(
|
fun CategoryListItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
category: Category,
|
category: Category,
|
||||||
canMoveUp: Boolean,
|
canMoveUp: Boolean,
|
||||||
canMoveDown: Boolean,
|
canMoveDown: Boolean,
|
||||||
@ -34,6 +33,7 @@ fun CategoryListItem(
|
|||||||
onMoveDown: (Category) -> Unit,
|
onMoveDown: (Category) -> Unit,
|
||||||
onRename: () -> Unit,
|
onRename: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -49,7 +49,7 @@ fun CategoryListItem(
|
|||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.Outlined.Label, contentDescription = "")
|
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
||||||
Text(
|
Text(
|
||||||
text = category.name,
|
text = category.name,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -61,20 +61,23 @@ fun CategoryListItem(
|
|||||||
onClick = { onMoveUp(category) },
|
onClick = { onMoveUp(category) },
|
||||||
enabled = canMoveUp,
|
enabled = canMoveUp,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = null)
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { onMoveDown(category) },
|
onClick = { onMoveDown(category) },
|
||||||
enabled = canMoveDown,
|
enabled = canMoveDown,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = null)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
IconButton(onClick = onRename) {
|
IconButton(onClick = onRename) {
|
||||||
Icon(imageVector = Icons.Outlined.Edit, contentDescription = stringResource(R.string.action_rename_category))
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Edit,
|
||||||
|
contentDescription = stringResource(MR.strings.action_rename_category),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onDelete) {
|
IconButton(onClick = onDelete) {
|
||||||
Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.action_delete))
|
Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(MR.strings.action_delete))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,10 +71,10 @@ fun NavigatorAdaptiveSheet(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AdaptiveSheet(
|
fun AdaptiveSheet(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
tonalElevation: Dp = 1.dp,
|
tonalElevation: Dp = 1.dp,
|
||||||
enableSwipeDismiss: Boolean = true,
|
enableSwipeDismiss: Boolean = true,
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val isTabletUi = isTabletUi()
|
val isTabletUi = isTabletUi()
|
||||||
|
@ -10,8 +10,7 @@ import androidx.compose.foundation.text.KeyboardActions
|
|||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.TextFieldDefaults
|
import androidx.compose.material.TextFieldDefaults
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.ArrowBack
|
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.ArrowForward
|
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
@ -20,11 +19,14 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PlainTooltipBox
|
import androidx.compose.material3.PlainTooltip
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TooltipBox
|
||||||
|
import androidx.compose.material3.TooltipDefaults
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.rememberTooltipState
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
@ -40,17 +42,16 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.tachiyomi.R
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide
|
import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide
|
||||||
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
@ -60,10 +61,11 @@ const val SEARCH_DEBOUNCE_MILLIS = 250L
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppBar(
|
fun AppBar(
|
||||||
|
title: String?,
|
||||||
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
backgroundColor: Color? = null,
|
backgroundColor: Color? = null,
|
||||||
// Text
|
// Text
|
||||||
title: String?,
|
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
// Up button
|
// Up button
|
||||||
navigateUp: (() -> Unit)? = null,
|
navigateUp: (() -> Unit)? = null,
|
||||||
@ -88,7 +90,7 @@ fun AppBar(
|
|||||||
if (isActionMode) {
|
if (isActionMode) {
|
||||||
AppBarTitle(actionModeCounter.toString())
|
AppBarTitle(actionModeCounter.toString())
|
||||||
} else {
|
} else {
|
||||||
AppBarTitle(title, subtitle)
|
AppBarTitle(title, subtitle = subtitle)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
@ -108,10 +110,11 @@ fun AppBar(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppBar(
|
fun AppBar(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
backgroundColor: Color? = null,
|
|
||||||
// Title
|
// Title
|
||||||
titleContent: @Composable () -> Unit,
|
titleContent: @Composable () -> Unit,
|
||||||
|
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
backgroundColor: Color? = null,
|
||||||
// Up button
|
// Up button
|
||||||
navigateUp: (() -> Unit)? = null,
|
navigateUp: (() -> Unit)? = null,
|
||||||
navigationIcon: ImageVector? = null,
|
navigationIcon: ImageVector? = null,
|
||||||
@ -132,13 +135,13 @@ fun AppBar(
|
|||||||
IconButton(onClick = onCancelActionMode) {
|
IconButton(onClick = onCancelActionMode) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_cancel),
|
contentDescription = stringResource(MR.strings.action_cancel),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
navigateUp?.let {
|
navigateUp?.let {
|
||||||
IconButton(onClick = it) {
|
IconButton(onClick = it) {
|
||||||
UpIcon(navigationIcon)
|
UpIcon(navigationIcon = navigationIcon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,9 +161,10 @@ fun AppBar(
|
|||||||
@Composable
|
@Composable
|
||||||
fun AppBarTitle(
|
fun AppBarTitle(
|
||||||
title: String?,
|
title: String?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(modifier = modifier) {
|
||||||
title?.let {
|
title?.let {
|
||||||
Text(
|
Text(
|
||||||
text = it,
|
text = it,
|
||||||
@ -184,18 +188,23 @@ fun AppBarTitle(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppBarActions(
|
fun AppBarActions(
|
||||||
actions: List<AppBar.AppBarAction>,
|
actions: ImmutableList<AppBar.AppBarAction>,
|
||||||
) {
|
) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
actions.filterIsInstance<AppBar.Action>().map {
|
actions.filterIsInstance<AppBar.Action>().map {
|
||||||
PlainTooltipBox(
|
TooltipBox(
|
||||||
tooltip = { Text(it.title) },
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = {
|
||||||
|
PlainTooltip {
|
||||||
|
Text(it.title)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = rememberTooltipState(),
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = it.onClick,
|
onClick = it.onClick,
|
||||||
enabled = it.enabled,
|
enabled = it.enabled,
|
||||||
modifier = Modifier.tooltipTrigger(),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = it.icon,
|
imageVector = it.icon,
|
||||||
@ -208,16 +217,21 @@ fun AppBarActions(
|
|||||||
|
|
||||||
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
|
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
|
||||||
if (overflowActions.isNotEmpty()) {
|
if (overflowActions.isNotEmpty()) {
|
||||||
PlainTooltipBox(
|
TooltipBox(
|
||||||
tooltip = { Text(stringResource(R.string.abc_action_menu_overflow_description)) },
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = {
|
||||||
|
PlainTooltip {
|
||||||
|
Text(stringResource(MR.strings.action_menu_overflow_description))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = rememberTooltipState(),
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { showMenu = !showMenu },
|
onClick = { showMenu = !showMenu },
|
||||||
modifier = Modifier.tooltipTrigger(),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.MoreVert,
|
Icons.Outlined.MoreVert,
|
||||||
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
|
contentDescription = stringResource(MR.strings.action_menu_overflow_description),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,15 +256,16 @@ fun AppBarActions(
|
|||||||
/**
|
/**
|
||||||
* @param searchEnabled Set to false if you don't want to show search action.
|
* @param searchEnabled Set to false if you don't want to show search action.
|
||||||
* @param searchQuery If null, use normal toolbar.
|
* @param searchQuery If null, use normal toolbar.
|
||||||
* @param placeholderText If null, [R.string.action_search_hint] is used.
|
* @param placeholderText If null, [MR.strings.action_search_hint] is used.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchToolbar(
|
fun SearchToolbar(
|
||||||
|
searchQuery: String?,
|
||||||
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
titleContent: @Composable () -> Unit = {},
|
titleContent: @Composable () -> Unit = {},
|
||||||
navigateUp: (() -> Unit)? = null,
|
navigateUp: (() -> Unit)? = null,
|
||||||
searchEnabled: Boolean = true,
|
searchEnabled: Boolean = true,
|
||||||
searchQuery: String?,
|
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
|
||||||
placeholderText: String? = null,
|
placeholderText: String? = null,
|
||||||
onSearch: (String) -> Unit = {},
|
onSearch: (String) -> Unit = {},
|
||||||
onClickCloseSearch: () -> Unit = { onChangeSearchQuery(null) },
|
onClickCloseSearch: () -> Unit = { onChangeSearchQuery(null) },
|
||||||
@ -262,6 +277,7 @@ fun SearchToolbar(
|
|||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
AppBar(
|
AppBar(
|
||||||
|
modifier = modifier,
|
||||||
titleContent = {
|
titleContent = {
|
||||||
if (searchQuery == null) return@AppBar titleContent()
|
if (searchQuery == null) return@AppBar titleContent()
|
||||||
|
|
||||||
@ -306,7 +322,7 @@ fun SearchToolbar(
|
|||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
text = (placeholderText ?: stringResource(R.string.action_search_hint)),
|
text = (placeholderText ?: stringResource(MR.strings.action_search_hint)),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
@ -327,33 +343,43 @@ fun SearchToolbar(
|
|||||||
if (!searchEnabled) {
|
if (!searchEnabled) {
|
||||||
// Don't show search action
|
// Don't show search action
|
||||||
} else if (searchQuery == null) {
|
} else if (searchQuery == null) {
|
||||||
PlainTooltipBox(
|
TooltipBox(
|
||||||
tooltip = { Text(stringResource(R.string.action_search)) },
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = {
|
||||||
|
PlainTooltip {
|
||||||
|
Text(stringResource(MR.strings.action_search))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = rememberTooltipState(),
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier.tooltipTrigger(),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Search,
|
Icons.Outlined.Search,
|
||||||
contentDescription = stringResource(R.string.action_search),
|
contentDescription = stringResource(MR.strings.action_search),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (searchQuery.isNotEmpty()) {
|
} else if (searchQuery.isNotEmpty()) {
|
||||||
PlainTooltipBox(
|
TooltipBox(
|
||||||
tooltip = { Text(stringResource(R.string.action_reset)) },
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
tooltip = {
|
||||||
|
PlainTooltip {
|
||||||
|
Text(stringResource(MR.strings.action_reset))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = rememberTooltipState(),
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onClick()
|
onClick()
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
},
|
},
|
||||||
modifier = Modifier.tooltipTrigger(),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Close,
|
Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_reset),
|
contentDescription = stringResource(MR.strings.action_reset),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,12 +394,16 @@ fun SearchToolbar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpIcon(navigationIcon: ImageVector? = null) {
|
fun UpIcon(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
navigationIcon: ImageVector? = null,
|
||||||
|
) {
|
||||||
val icon = navigationIcon
|
val icon = navigationIcon
|
||||||
?: if (LocalLayoutDirection.current == LayoutDirection.Ltr) Icons.Outlined.ArrowBack else Icons.Outlined.ArrowForward
|
?: Icons.AutoMirrored.Outlined.ArrowBack
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
contentDescription = stringResource(MR.strings.action_bar_up_description),
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
@ -26,13 +25,14 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.SubcomposeLayout
|
import androidx.compose.ui.layout.SubcomposeLayout
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import androidx.compose.ui.util.fastMaxBy
|
import androidx.compose.ui.util.fastMaxBy
|
||||||
import eu.kanade.tachiyomi.R
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
val DownloadedOnlyBannerBackgroundColor
|
val DownloadedOnlyBannerBackgroundColor
|
||||||
@Composable get() = MaterialTheme.colorScheme.tertiary
|
@Composable get() = MaterialTheme.colorScheme.tertiary
|
||||||
@ -43,7 +43,7 @@ val IndexingBannerBackgroundColor
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WarningBanner(
|
fun WarningBanner(
|
||||||
@StringRes textRes: Int,
|
textRes: StringResource,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -127,7 +127,7 @@ fun AppStateBanners(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
|
private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.label_downloaded_only),
|
text = stringResource(MR.strings.label_downloaded_only),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(DownloadedOnlyBannerBackgroundColor)
|
.background(DownloadedOnlyBannerBackgroundColor)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -142,7 +142,7 @@ private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun IncognitoModeBanner(modifier: Modifier = Modifier) {
|
private fun IncognitoModeBanner(modifier: Modifier = Modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.pref_incognito_mode),
|
text = stringResource(MR.strings.pref_incognito_mode),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(IncognitoModeBannerBackgroundColor)
|
.background(IncognitoModeBannerBackgroundColor)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -173,7 +173,7 @@ private fun IndexingDownloadBanner(modifier: Modifier = Modifier) {
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.download_notifier_cache_renewal),
|
text = stringResource(MR.strings.download_notifier_cache_renewal),
|
||||||
color = MaterialTheme.colorScheme.onSecondary,
|
color = MaterialTheme.colorScheme.onSecondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
|
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun relativeDateText(
|
||||||
|
dateEpochMillis: Long,
|
||||||
|
): String {
|
||||||
|
return relativeDateText(
|
||||||
|
date = Date(dateEpochMillis).takeIf { dateEpochMillis > 0L },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun relativeDateText(
|
||||||
|
date: Date?,
|
||||||
|
): String {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val preferences = remember { Injekt.get<UiPreferences>() }
|
||||||
|
val relativeTime = remember { preferences.relativeTime().get() }
|
||||||
|
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
||||||
|
|
||||||
|
return date
|
||||||
|
?.toRelativeString(
|
||||||
|
context = context,
|
||||||
|
relative = relativeTime,
|
||||||
|
dateFormat = dateFormat,
|
||||||
|
)
|
||||||
|
?: stringResource(MR.strings.not_applicable)
|
||||||
|
}
|
@ -3,28 +3,34 @@ package eu.kanade.presentation.components
|
|||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.manga.DownloadAction
|
import eu.kanade.presentation.manga.DownloadAction
|
||||||
import eu.kanade.tachiyomi.R
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadDropdownMenu(
|
fun DownloadDropdownMenu(
|
||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onDownloadClicked: (DownloadAction) -> Unit,
|
onDownloadClicked: (DownloadAction) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val options = persistentListOf(
|
||||||
|
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),
|
||||||
|
DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25),
|
||||||
|
DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread),
|
||||||
|
)
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
listOfNotNull(
|
options.map { (downloadAction, string) ->
|
||||||
DownloadAction.NEXT_1_CHAPTER to pluralStringResource(R.plurals.download_amount, 1, 1),
|
|
||||||
DownloadAction.NEXT_5_CHAPTERS to pluralStringResource(R.plurals.download_amount, 5, 5),
|
|
||||||
DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(R.plurals.download_amount, 10, 10),
|
|
||||||
DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(R.plurals.download_amount, 25, 25),
|
|
||||||
DownloadAction.UNREAD_CHAPTERS to stringResource(R.string.download_unread),
|
|
||||||
).map { (downloadAction, string) ->
|
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = string) },
|
text = { Text(text = string) },
|
||||||
onClick = {
|
onClick = {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.ArrowLeft
|
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
|
||||||
import androidx.compose.material.icons.outlined.ArrowRight
|
|
||||||
import androidx.compose.material.icons.outlined.RadioButtonChecked
|
import androidx.compose.material.icons.outlined.RadioButtonChecked
|
||||||
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@ -16,21 +18,24 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import eu.kanade.tachiyomi.R
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
|
import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DropdownMenu but overlaps anchor and has width constraints to better
|
||||||
|
* match non-Compose implementation.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun DropdownMenu(
|
fun DropdownMenu(
|
||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
offset: DpOffset = DpOffset(8.dp, (-56).dp),
|
offset: DpOffset = DpOffset(8.dp, (-56).dp),
|
||||||
|
scrollState: ScrollState = rememberScrollState(),
|
||||||
properties: PopupProperties = PopupProperties(focusable = true),
|
properties: PopupProperties = PopupProperties(focusable = true),
|
||||||
content: @Composable ColumnScope.() -> Unit,
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -39,6 +44,7 @@ fun DropdownMenu(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp),
|
modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp),
|
||||||
offset = offset,
|
offset = offset,
|
||||||
|
scrollState = scrollState,
|
||||||
properties = properties,
|
properties = properties,
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
@ -48,6 +54,7 @@ fun DropdownMenu(
|
|||||||
fun RadioMenuItem(
|
fun RadioMenuItem(
|
||||||
text: @Composable () -> Unit,
|
text: @Composable () -> Unit,
|
||||||
isChecked: Boolean,
|
isChecked: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
@ -57,16 +64,17 @@ fun RadioMenuItem(
|
|||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.RadioButtonChecked,
|
imageVector = Icons.Outlined.RadioButtonChecked,
|
||||||
contentDescription = stringResource(R.string.selected),
|
contentDescription = stringResource(MR.strings.selected),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.RadioButtonUnchecked,
|
imageVector = Icons.Outlined.RadioButtonUnchecked,
|
||||||
contentDescription = stringResource(R.string.not_selected),
|
contentDescription = stringResource(MR.strings.not_selected),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,17 +82,18 @@ fun RadioMenuItem(
|
|||||||
fun NestedMenuItem(
|
fun NestedMenuItem(
|
||||||
text: @Composable () -> Unit,
|
text: @Composable () -> Unit,
|
||||||
children: @Composable ColumnScope.(() -> Unit) -> Unit,
|
children: @Composable ColumnScope.(() -> Unit) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
var nestedExpanded by remember { mutableStateOf(false) }
|
var nestedExpanded by remember { mutableStateOf(false) }
|
||||||
val closeMenu = { nestedExpanded = false }
|
val closeMenu = { nestedExpanded = false }
|
||||||
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
|
||||||
|
|
||||||
|
Box {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = text,
|
text = text,
|
||||||
onClick = { nestedExpanded = true },
|
onClick = { nestedExpanded = true },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (isLtr) Icons.Outlined.ArrowRight else Icons.Outlined.ArrowLeft,
|
imageVector = Icons.AutoMirrored.Outlined.ArrowRight,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -93,7 +102,9 @@ fun NestedMenuItem(
|
|||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = nestedExpanded,
|
expanded = nestedExpanded,
|
||||||
onDismissRequest = closeMenu,
|
onDismissRequest = closeMenu,
|
||||||
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
children(closeMenu)
|
children(closeMenu)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,45 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.tachiyomi.R
|
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.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun NoActionPreview() {
|
private fun NoActionPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiPreviewTheme {
|
||||||
Surface {
|
Surface {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.empty_screen,
|
stringRes = MR.strings.empty_screen,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun WithActionPreview() {
|
private fun WithActionPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiPreviewTheme {
|
||||||
Surface {
|
Surface {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.empty_screen,
|
stringRes = MR.strings.empty_screen,
|
||||||
actions = listOf(
|
actions = persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_retry,
|
stringRes = MR.strings.action_retry,
|
||||||
icon = Icons.Outlined.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.getting_started_guide,
|
stringRes = MR.strings.getting_started_guide,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
package eu.kanade.presentation.components
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
|
||||||
import tachiyomi.presentation.core.components.ListGroupHeader
|
|
||||||
import java.text.DateFormat
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RelativeDateHeader(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
date: Date,
|
|
||||||
relativeTime: Boolean,
|
|
||||||
dateFormat: DateFormat,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
ListGroupHeader(
|
|
||||||
modifier = modifier,
|
|
||||||
text = remember {
|
|
||||||
date.toRelativeString(
|
|
||||||
context,
|
|
||||||
relativeTime,
|
|
||||||
dateFormat,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
@ -14,8 +14,8 @@ import androidx.compose.material3.HorizontalDivider
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -24,14 +24,14 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEachIndexed
|
import androidx.compose.ui.util.fastForEachIndexed
|
||||||
import eu.kanade.tachiyomi.R
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
|
||||||
import tachiyomi.presentation.core.components.material.TabText
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
object TabbedDialogPaddings {
|
object TabbedDialogPaddings {
|
||||||
val Horizontal = 24.dp
|
val Horizontal = 24.dp
|
||||||
@ -40,9 +40,9 @@ object TabbedDialogPaddings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TabbedDialog(
|
fun TabbedDialog(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
tabTitles: List<String>,
|
tabTitles: ImmutableList<String>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
|
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
|
||||||
pagerState: PagerState = rememberPagerState { tabTitles.size },
|
pagerState: PagerState = rememberPagerState { tabTitles.size },
|
||||||
content: @Composable (Int) -> Unit,
|
content: @Composable (Int) -> Unit,
|
||||||
@ -55,10 +55,9 @@ fun TabbedDialog(
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
Row {
|
Row {
|
||||||
TabRow(
|
PrimaryTabRow(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
|
|
||||||
divider = {},
|
divider = {},
|
||||||
) {
|
) {
|
||||||
tabTitles.fastForEachIndexed { index, tab ->
|
tabTitles.fastForEachIndexed { index, tab ->
|
||||||
@ -95,7 +94,7 @@ private fun MoreMenu(
|
|||||||
IconButton(onClick = { expanded = true }) {
|
IconButton(onClick = { expanded = true }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.MoreVert,
|
imageVector = Icons.Default.MoreVert,
|
||||||
contentDescription = stringResource(R.string.label_more),
|
contentDescription = stringResource(MR.strings.label_more),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
@ -9,10 +8,10 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -20,17 +19,20 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.stringResource
|
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 kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
|
||||||
import tachiyomi.presentation.core.components.material.TabText
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TabbedScreen(
|
fun TabbedScreen(
|
||||||
@StringRes titleRes: Int,
|
titleRes: StringResource,
|
||||||
tabs: List<TabContent>,
|
tabs: ImmutableList<TabContent>,
|
||||||
startIndex: Int? = null,
|
startIndex: Int? = null,
|
||||||
searchQuery: String? = null,
|
searchQuery: String? = null,
|
||||||
onChangeSearchQuery: (String?) -> Unit = {},
|
onChangeSearchQuery: (String?) -> Unit = {},
|
||||||
@ -67,9 +69,9 @@ fun TabbedScreen(
|
|||||||
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
TabRow(
|
PrimaryTabRow(
|
||||||
selectedTabIndex = state.currentPage,
|
selectedTabIndex = state.currentPage,
|
||||||
indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) },
|
modifier = Modifier.zIndex(1f),
|
||||||
) {
|
) {
|
||||||
tabs.forEachIndexed { index, tab ->
|
tabs.forEachIndexed { index, tab ->
|
||||||
Tab(
|
Tab(
|
||||||
@ -96,9 +98,9 @@ fun TabbedScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class TabContent(
|
data class TabContent(
|
||||||
@StringRes val titleRes: Int,
|
val titleRes: StringResource,
|
||||||
val badgeNumber: Int? = null,
|
val badgeNumber: Int? = null,
|
||||||
val searchEnabled: Boolean = false,
|
val searchEnabled: Boolean = false,
|
||||||
val actions: List<AppBar.Action> = emptyList(),
|
val actions: ImmutableList<AppBar.AppBarAction> = persistentListOf(),
|
||||||
val content: @Composable (contentPadding: PaddingValues, snackbarHostState: SnackbarHostState) -> Unit,
|
val content: @Composable (contentPadding: PaddingValues, snackbarHostState: SnackbarHostState) -> Unit,
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.presentation.crash
|
|||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.BugReport
|
import androidx.compose.material.icons.outlined.BugReport
|
||||||
@ -13,14 +13,14 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.InfoScreen
|
import tachiyomi.presentation.core.screens.InfoScreen
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CrashScreen(
|
fun CrashScreen(
|
||||||
@ -32,22 +32,22 @@ fun CrashScreen(
|
|||||||
|
|
||||||
InfoScreen(
|
InfoScreen(
|
||||||
icon = Icons.Outlined.BugReport,
|
icon = Icons.Outlined.BugReport,
|
||||||
headingText = stringResource(R.string.crash_screen_title),
|
headingText = stringResource(MR.strings.crash_screen_title),
|
||||||
subtitleText = stringResource(R.string.crash_screen_description, stringResource(R.string.app_name)),
|
subtitleText = stringResource(MR.strings.crash_screen_description, stringResource(MR.strings.app_name)),
|
||||||
acceptText = stringResource(R.string.pref_dump_crash_logs),
|
acceptText = stringResource(MR.strings.pref_dump_crash_logs),
|
||||||
onAcceptClick = {
|
onAcceptClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
CrashLogUtil(context).dumpLogs()
|
CrashLogUtil(context).dumpLogs()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rejectText = stringResource(R.string.crash_screen_restart_application),
|
rejectText = stringResource(MR.strings.crash_screen_restart_application),
|
||||||
onRejectClick = onRestartClick,
|
onRejectClick = onRestartClick,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = MaterialTheme.padding.small)
|
.padding(vertical = MaterialTheme.padding.small)
|
||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
.fillMaxWidth()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -60,10 +60,10 @@ fun CrashScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun CrashScreenPreview() {
|
private fun CrashScreenPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiPreviewTheme {
|
||||||
CrashScreen(exception = RuntimeException("Dummy")) {}
|
CrashScreen(exception = RuntimeException("Dummy")) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,29 +8,26 @@ import androidx.compose.material.icons.outlined.DeleteSweep
|
|||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.AppBarTitle
|
import eu.kanade.presentation.components.AppBarTitle
|
||||||
import eu.kanade.presentation.components.RelativeDateHeader
|
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
|
import eu.kanade.presentation.components.relativeDateText
|
||||||
import eu.kanade.presentation.history.components.HistoryItem
|
import eu.kanade.presentation.history.components.HistoryItem
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
import tachiyomi.core.preference.InMemoryPreferenceStore
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
|
import tachiyomi.presentation.core.components.ListGroupHeader
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -41,19 +38,18 @@ fun HistoryScreen(
|
|||||||
onClickCover: (mangaId: Long) -> Unit,
|
onClickCover: (mangaId: Long) -> Unit,
|
||||||
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
||||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
||||||
preferences: UiPreferences = Injekt.get(),
|
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
SearchToolbar(
|
SearchToolbar(
|
||||||
titleContent = { AppBarTitle(stringResource(R.string.history)) },
|
titleContent = { AppBarTitle(stringResource(MR.strings.history)) },
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
onChangeSearchQuery = onSearchQueryChange,
|
onChangeSearchQuery = onSearchQueryChange,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
persistentListOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.pref_clear_history),
|
title = stringResource(MR.strings.pref_clear_history),
|
||||||
icon = Icons.Outlined.DeleteSweep,
|
icon = Icons.Outlined.DeleteSweep,
|
||||||
onClick = {
|
onClick = {
|
||||||
onDialogChange(HistoryScreenModel.Dialog.DeleteAll)
|
onDialogChange(HistoryScreenModel.Dialog.DeleteAll)
|
||||||
@ -72,12 +68,12 @@ fun HistoryScreen(
|
|||||||
LoadingScreen(Modifier.padding(contentPadding))
|
LoadingScreen(Modifier.padding(contentPadding))
|
||||||
} else if (it.isEmpty()) {
|
} else if (it.isEmpty()) {
|
||||||
val msg = if (!state.searchQuery.isNullOrEmpty()) {
|
val msg = if (!state.searchQuery.isNullOrEmpty()) {
|
||||||
R.string.no_results_found
|
MR.strings.no_results_found
|
||||||
} else {
|
} else {
|
||||||
R.string.information_no_recent_manga
|
MR.strings.information_no_recent_manga
|
||||||
}
|
}
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = msg,
|
stringRes = msg,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -87,7 +83,6 @@ fun HistoryScreen(
|
|||||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
||||||
preferences = preferences,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,11 +96,7 @@ private fun HistoryScreenContent(
|
|||||||
onClickCover: (HistoryWithRelations) -> Unit,
|
onClickCover: (HistoryWithRelations) -> Unit,
|
||||||
onClickResume: (HistoryWithRelations) -> Unit,
|
onClickResume: (HistoryWithRelations) -> Unit,
|
||||||
onClickDelete: (HistoryWithRelations) -> Unit,
|
onClickDelete: (HistoryWithRelations) -> Unit,
|
||||||
preferences: UiPreferences,
|
|
||||||
) {
|
) {
|
||||||
val relativeTime = remember { preferences.relativeTime().get() }
|
|
||||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
|
||||||
|
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
@ -121,11 +112,9 @@ private fun HistoryScreenContent(
|
|||||||
) { item ->
|
) { item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is HistoryUiModel.Header -> {
|
is HistoryUiModel.Header -> {
|
||||||
RelativeDateHeader(
|
ListGroupHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
date = item.date,
|
text = relativeDateText(item.date),
|
||||||
relativeTime = relativeTime,
|
|
||||||
dateFormat = dateFormat,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HistoryUiModel.Item -> {
|
is HistoryUiModel.Item -> {
|
||||||
@ -148,13 +137,13 @@ sealed interface HistoryUiModel {
|
|||||||
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
internal fun HistoryScreenPreviews(
|
internal fun HistoryScreenPreviews(
|
||||||
@PreviewParameter(HistoryScreenModelStateProvider::class)
|
@PreviewParameter(HistoryScreenModelStateProvider::class)
|
||||||
historyState: HistoryScreenModel.State,
|
historyState: HistoryScreenModel.State,
|
||||||
) {
|
) {
|
||||||
TachiyomiTheme {
|
TachiyomiPreviewTheme {
|
||||||
HistoryScreen(
|
HistoryScreen(
|
||||||
state = historyState,
|
state = historyState,
|
||||||
snackbarHostState = SnackbarHostState(),
|
snackbarHostState = SnackbarHostState(),
|
||||||
@ -162,17 +151,6 @@ internal fun HistoryScreenPreviews(
|
|||||||
onClickCover = {},
|
onClickCover = {},
|
||||||
onClickResume = { _, _ -> run {} },
|
onClickResume = { _, _ -> run {} },
|
||||||
onDialogChange = {},
|
onDialogChange = {},
|
||||||
preferences = UiPreferences(
|
|
||||||
InMemoryPreferenceStore(
|
|
||||||
sequenceOf(
|
|
||||||
InMemoryPreferenceStore.InMemoryPreference(
|
|
||||||
key = "relative_time_v2",
|
|
||||||
data = false,
|
|
||||||
defaultValue = false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.presentation.history
|
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
object HistoryUiModelProviders {
|
|
||||||
|
|
||||||
class HeadNow : PreviewParameterProvider<HistoryUiModel> {
|
|
||||||
override val values: Sequence<HistoryUiModel> =
|
|
||||||
sequenceOf(HistoryUiModel.Header(Date.from(Instant.now())))
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,7 @@ package eu.kanade.presentation.history.components
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -10,12 +11,12 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.unit.dp
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import tachiyomi.i18n.MR
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryDeleteDialog(
|
fun HistoryDeleteDialog(
|
||||||
@ -26,16 +27,16 @@ fun HistoryDeleteDialog(
|
|||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.action_remove))
|
Text(text = stringResource(MR.strings.action_remove))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.dialog_with_checkbox_remove_description))
|
Text(text = stringResource(MR.strings.dialog_with_checkbox_remove_description))
|
||||||
|
|
||||||
LabeledCheckbox(
|
LabeledCheckbox(
|
||||||
label = stringResource(R.string.dialog_with_checkbox_reset),
|
label = stringResource(MR.strings.dialog_with_checkbox_reset),
|
||||||
checked = removeEverything,
|
checked = removeEverything,
|
||||||
onCheckedChange = { removeEverything = it },
|
onCheckedChange = { removeEverything = it },
|
||||||
)
|
)
|
||||||
@ -47,12 +48,12 @@ fun HistoryDeleteDialog(
|
|||||||
onDelete(removeEverything)
|
onDelete(removeEverything)
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(R.string.action_remove))
|
Text(text = stringResource(MR.strings.action_remove))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -65,10 +66,10 @@ fun HistoryDeleteAllDialog(
|
|||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.action_remove_everything))
|
Text(text = stringResource(MR.strings.action_remove_everything))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.clear_history_confirmation))
|
Text(text = stringResource(MR.strings.clear_history_confirmation))
|
||||||
},
|
},
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -76,21 +77,21 @@ fun HistoryDeleteAllDialog(
|
|||||||
onDelete()
|
onDelete()
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun HistoryDeleteDialogPreview() {
|
private fun HistoryDeleteDialogPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiPreviewTheme {
|
||||||
HistoryDeleteDialog(
|
HistoryDeleteDialog(
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
onDelete = {},
|
onDelete = {},
|
||||||
|
@ -11,34 +11,35 @@ import androidx.compose.material.icons.outlined.Delete
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.presentation.util.formatChapterNumber
|
import eu.kanade.presentation.util.formatChapterNumber
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
private val HistoryItemHeight = 96.dp
|
private val HistoryItemHeight = 96.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryItem(
|
fun HistoryItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
history: HistoryWithRelations,
|
history: HistoryWithRelations,
|
||||||
onClickCover: () -> Unit,
|
onClickCover: () -> Unit,
|
||||||
onClickResume: () -> Unit,
|
onClickResume: () -> Unit,
|
||||||
onClickDelete: () -> Unit,
|
onClickDelete: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@ -69,7 +70,7 @@ fun HistoryItem(
|
|||||||
Text(
|
Text(
|
||||||
text = if (history.chapterNumber > -1) {
|
text = if (history.chapterNumber > -1) {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.recent_manga_time,
|
MR.strings.recent_manga_time,
|
||||||
formatChapterNumber(history.chapterNumber),
|
formatChapterNumber(history.chapterNumber),
|
||||||
readAt,
|
readAt,
|
||||||
)
|
)
|
||||||
@ -84,20 +85,21 @@ fun HistoryItem(
|
|||||||
IconButton(onClick = onClickDelete) {
|
IconButton(onClick = onClickDelete) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Delete,
|
imageVector = Icons.Outlined.Delete,
|
||||||
contentDescription = stringResource(R.string.action_delete),
|
contentDescription = stringResource(MR.strings.action_delete),
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun HistoryItemPreviews(
|
private fun HistoryItemPreviews(
|
||||||
@PreviewParameter(HistoryWithRelationsProvider::class)
|
@PreviewParameter(HistoryWithRelationsProvider::class)
|
||||||
historyWithRelations: HistoryWithRelations,
|
historyWithRelations: HistoryWithRelations,
|
||||||
) {
|
) {
|
||||||
TachiyomiTheme {
|
TachiyomiPreviewTheme {
|
||||||
|
Surface {
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
history = historyWithRelations,
|
history = historyWithRelations,
|
||||||
onClickCover = {},
|
onClickCover = {},
|
||||||
@ -105,4 +107,5 @@ private fun HistoryItemPreviews(
|
|||||||
onClickDelete = {},
|
onClickDelete = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,11 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import tachiyomi.core.preference.CheckboxState
|
import tachiyomi.core.preference.CheckboxState
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeleteLibraryMangaDialog(
|
fun DeleteLibraryMangaDialog(
|
||||||
@ -22,10 +23,10 @@ fun DeleteLibraryMangaDialog(
|
|||||||
) {
|
) {
|
||||||
var list by remember {
|
var list by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
buildList<CheckboxState.State<Int>> {
|
buildList<CheckboxState.State<StringResource>> {
|
||||||
add(CheckboxState.State.None(R.string.manga_from_library))
|
add(CheckboxState.State.None(MR.strings.manga_from_library))
|
||||||
if (!containsLocalManga) {
|
if (!containsLocalManga) {
|
||||||
add(CheckboxState.State.None(R.string.downloaded_chapters))
|
add(CheckboxState.State.None(MR.strings.downloaded_chapters))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -34,7 +35,7 @@ fun DeleteLibraryMangaDialog(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -48,11 +49,11 @@ fun DeleteLibraryMangaDialog(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.action_remove))
|
Text(text = stringResource(MR.strings.action_remove))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
@ -64,7 +65,7 @@ fun DeleteLibraryMangaDialog(
|
|||||||
val index = list.indexOf(state)
|
val index = list.indexOf(state)
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
val mutableList = list.toMutableList()
|
val mutableList = list.toMutableList()
|
||||||
mutableList[index] = state.next() as CheckboxState.State<Int>
|
mutableList[index] = state.next() as CheckboxState.State<StringResource>
|
||||||
list = mutableList.toList()
|
list = mutableList.toList()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13,23 +13,26 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.components.TabbedDialog
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
|
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
|
||||||
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.domain.library.model.LibrarySort
|
import tachiyomi.domain.library.model.LibrarySort
|
||||||
import tachiyomi.domain.library.model.sort
|
import tachiyomi.domain.library.model.sort
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.HeadingItem
|
||||||
import tachiyomi.presentation.core.components.SettingsChipRow
|
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
import tachiyomi.presentation.core.components.SortItem
|
import tachiyomi.presentation.core.components.SortItem
|
||||||
import tachiyomi.presentation.core.components.TriStateItem
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -40,10 +43,10 @@ fun LibrarySettingsDialog(
|
|||||||
) {
|
) {
|
||||||
TabbedDialog(
|
TabbedDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
tabTitles = listOf(
|
tabTitles = persistentListOf(
|
||||||
stringResource(R.string.action_filter),
|
stringResource(MR.strings.action_filter),
|
||||||
stringResource(R.string.action_sort),
|
stringResource(MR.strings.action_sort),
|
||||||
stringResource(R.string.action_display),
|
stringResource(MR.strings.action_display),
|
||||||
),
|
),
|
||||||
) { page ->
|
) { page ->
|
||||||
Column(
|
Column(
|
||||||
@ -73,8 +76,10 @@ private fun ColumnScope.FilterPage(
|
|||||||
) {
|
) {
|
||||||
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState()
|
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState()
|
||||||
val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState()
|
val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState()
|
||||||
|
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions().collectAsState()
|
||||||
|
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.label_downloaded),
|
label = stringResource(MR.strings.label_downloaded),
|
||||||
state = if (downloadedOnly) {
|
state = if (downloadedOnly) {
|
||||||
TriState.ENABLED_IS
|
TriState.ENABLED_IS
|
||||||
} else {
|
} else {
|
||||||
@ -85,28 +90,40 @@ private fun ColumnScope.FilterPage(
|
|||||||
)
|
)
|
||||||
val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState()
|
val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.action_filter_unread),
|
label = stringResource(MR.strings.action_filter_unread),
|
||||||
state = filterUnread,
|
state = filterUnread,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
|
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
|
||||||
)
|
)
|
||||||
val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState()
|
val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.label_started),
|
label = stringResource(MR.strings.label_started),
|
||||||
state = filterStarted,
|
state = filterStarted,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
|
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
|
||||||
)
|
)
|
||||||
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState()
|
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.action_filter_bookmarked),
|
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||||
state = filterBookmarked,
|
state = filterBookmarked,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
|
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
|
||||||
)
|
)
|
||||||
val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState()
|
val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.completed),
|
label = stringResource(MR.strings.completed),
|
||||||
state = filterCompleted,
|
state = filterCompleted,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) },
|
onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) },
|
||||||
)
|
)
|
||||||
|
// TODO: re-enable when custom intervals are ready for stable
|
||||||
|
if (
|
||||||
|
(isDevFlavor || isPreviewBuildType) &&
|
||||||
|
LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions
|
||||||
|
) {
|
||||||
|
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom().collectAsState()
|
||||||
|
TriStateItem(
|
||||||
|
label = stringResource(MR.strings.action_filter_interval_custom),
|
||||||
|
state = filterIntervalCustom,
|
||||||
|
onClick = { screenModel.toggleFilter(LibraryPreferences::filterIntervalCustom) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val trackers = remember { screenModel.trackers }
|
val trackers = remember { screenModel.trackers }
|
||||||
when (trackers.size) {
|
when (trackers.size) {
|
||||||
@ -117,13 +134,13 @@ private fun ColumnScope.FilterPage(
|
|||||||
val service = trackers[0]
|
val service = trackers[0]
|
||||||
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.action_filter_tracked),
|
label = stringResource(MR.strings.action_filter_tracked),
|
||||||
state = filterTracker,
|
state = filterTracker,
|
||||||
onClick = { screenModel.toggleTracker(service.id.toInt()) },
|
onClick = { screenModel.toggleTracker(service.id.toInt()) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
HeadingItem(R.string.action_filter_tracked)
|
HeadingItem(MR.strings.action_filter_tracked)
|
||||||
trackers.map { service ->
|
trackers.map { service ->
|
||||||
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
@ -148,18 +165,18 @@ private fun ColumnScope.SortPage(
|
|||||||
if (screenModel.trackers.isEmpty()) {
|
if (screenModel.trackers.isEmpty()) {
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
listOf(R.string.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
||||||
}
|
}
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
R.string.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
||||||
R.string.action_sort_total to LibrarySort.Type.TotalChapters,
|
MR.strings.action_sort_total to LibrarySort.Type.TotalChapters,
|
||||||
R.string.action_sort_last_read to LibrarySort.Type.LastRead,
|
MR.strings.action_sort_last_read to LibrarySort.Type.LastRead,
|
||||||
R.string.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
|
MR.strings.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
|
||||||
R.string.action_sort_unread_count to LibrarySort.Type.UnreadCount,
|
MR.strings.action_sort_unread_count to LibrarySort.Type.UnreadCount,
|
||||||
R.string.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
|
MR.strings.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
|
||||||
R.string.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
|
MR.strings.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
|
||||||
R.string.action_sort_date_added to LibrarySort.Type.DateAdded,
|
MR.strings.action_sort_date_added to LibrarySort.Type.DateAdded,
|
||||||
).plus(trackerSortOption).map { (titleRes, mode) ->
|
).plus(trackerSortOption).map { (titleRes, mode) ->
|
||||||
SortItem(
|
SortItem(
|
||||||
label = stringResource(titleRes),
|
label = stringResource(titleRes),
|
||||||
@ -167,8 +184,16 @@ private fun ColumnScope.SortPage(
|
|||||||
onClick = {
|
onClick = {
|
||||||
val isTogglingDirection = sortingMode == mode
|
val isTogglingDirection = sortingMode == mode
|
||||||
val direction = when {
|
val direction = when {
|
||||||
isTogglingDirection -> if (sortDescending) LibrarySort.Direction.Ascending else LibrarySort.Direction.Descending
|
isTogglingDirection -> if (sortDescending) {
|
||||||
else -> if (sortDescending) LibrarySort.Direction.Descending else LibrarySort.Direction.Ascending
|
LibrarySort.Direction.Ascending
|
||||||
|
} else {
|
||||||
|
LibrarySort.Direction.Descending
|
||||||
|
}
|
||||||
|
else -> if (sortDescending) {
|
||||||
|
LibrarySort.Direction.Descending
|
||||||
|
} else {
|
||||||
|
LibrarySort.Direction.Ascending
|
||||||
|
}
|
||||||
}
|
}
|
||||||
screenModel.setSort(category, mode, direction)
|
screenModel.setSort(category, mode, direction)
|
||||||
},
|
},
|
||||||
@ -177,10 +202,10 @@ private fun ColumnScope.SortPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val displayModes = listOf(
|
private val displayModes = listOf(
|
||||||
R.string.action_display_grid to LibraryDisplayMode.CompactGrid,
|
MR.strings.action_display_grid to LibraryDisplayMode.CompactGrid,
|
||||||
R.string.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid,
|
MR.strings.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid,
|
||||||
R.string.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid,
|
MR.strings.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid,
|
||||||
R.string.action_display_list to LibraryDisplayMode.List,
|
MR.strings.action_display_list to LibraryDisplayMode.List,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -188,7 +213,7 @@ private fun ColumnScope.DisplayPage(
|
|||||||
screenModel: LibrarySettingsScreenModel,
|
screenModel: LibrarySettingsScreenModel,
|
||||||
) {
|
) {
|
||||||
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
|
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
|
||||||
SettingsChipRow(R.string.action_display_mode) {
|
SettingsChipRow(MR.strings.action_display_mode) {
|
||||||
displayModes.map { (titleRes, mode) ->
|
displayModes.map { (titleRes, mode) ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = displayMode == mode,
|
selected = displayMode == mode,
|
||||||
@ -210,43 +235,43 @@ private fun ColumnScope.DisplayPage(
|
|||||||
|
|
||||||
val columns by columnPreference.collectAsState()
|
val columns by columnPreference.collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(R.string.pref_library_columns),
|
label = stringResource(MR.strings.pref_library_columns),
|
||||||
max = 10,
|
max = 10,
|
||||||
value = columns,
|
value = columns,
|
||||||
valueText = if (columns > 0) {
|
valueText = if (columns > 0) {
|
||||||
stringResource(R.string.pref_library_columns_per_row, columns)
|
stringResource(MR.strings.pref_library_columns_per_row, columns)
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.label_default)
|
stringResource(MR.strings.label_default)
|
||||||
},
|
},
|
||||||
onChange = columnPreference::set,
|
onChange = columnPreference::set,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HeadingItem(R.string.overlay_header)
|
HeadingItem(MR.strings.overlay_header)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_download_badge),
|
label = stringResource(MR.strings.action_display_download_badge),
|
||||||
pref = screenModel.libraryPreferences.downloadBadge(),
|
pref = screenModel.libraryPreferences.downloadBadge(),
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_local_badge),
|
label = stringResource(MR.strings.action_display_local_badge),
|
||||||
pref = screenModel.libraryPreferences.localBadge(),
|
pref = screenModel.libraryPreferences.localBadge(),
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_language_badge),
|
label = stringResource(MR.strings.action_display_language_badge),
|
||||||
pref = screenModel.libraryPreferences.languageBadge(),
|
pref = screenModel.libraryPreferences.languageBadge(),
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_show_continue_reading_button),
|
label = stringResource(MR.strings.action_display_show_continue_reading_button),
|
||||||
pref = screenModel.libraryPreferences.showContinueReadingButton(),
|
pref = screenModel.libraryPreferences.showContinueReadingButton(),
|
||||||
)
|
)
|
||||||
|
|
||||||
HeadingItem(R.string.tabs_header)
|
HeadingItem(MR.strings.tabs_header)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_show_tabs),
|
label = stringResource(MR.strings.action_display_show_tabs),
|
||||||
pref = screenModel.libraryPreferences.categoryTabs(),
|
pref = screenModel.libraryPreferences.categoryTabs(),
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_show_number_of_items),
|
label = stringResource(MR.strings.action_display_show_number_of_items),
|
||||||
pref = screenModel.libraryPreferences.categoryNumberOfItems(),
|
pref = screenModel.libraryPreferences.categoryNumberOfItems(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.BadgeGroup
|
import tachiyomi.presentation.core.components.BadgeGroup
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
|
|
||||||
object CommonMangaItemDefaults {
|
object CommonMangaItemDefaults {
|
||||||
@ -48,7 +50,7 @@ object CommonMangaItemDefaults {
|
|||||||
const val BrowseFavoriteCoverAlpha = 0.34f
|
const val BrowseFavoriteCoverAlpha = 0.34f
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ContinueReadingButtonSize = 32.dp
|
private val ContinueReadingButtonSize = 28.dp
|
||||||
private val ContinueReadingButtonGridPadding = 6.dp
|
private val ContinueReadingButtonGridPadding = 6.dp
|
||||||
private val ContinueReadingButtonListSpacing = 8.dp
|
private val ContinueReadingButtonListSpacing = 8.dp
|
||||||
|
|
||||||
@ -60,15 +62,15 @@ private const val GridSelectedCoverAlpha = 0.76f
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaCompactGridItem(
|
fun MangaCompactGridItem(
|
||||||
|
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
isSelected: Boolean = false,
|
isSelected: Boolean = false,
|
||||||
title: String? = null,
|
title: String? = null,
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
onClickContinueReading: (() -> Unit)? = null,
|
||||||
coverAlpha: Float = 1f,
|
coverAlpha: Float = 1f,
|
||||||
coverBadgeStart: @Composable (RowScope.() -> Unit)? = null,
|
coverBadgeStart: @Composable (RowScope.() -> Unit)? = null,
|
||||||
coverBadgeEnd: @Composable (RowScope.() -> Unit)? = null,
|
coverBadgeEnd: @Composable (RowScope.() -> Unit)? = null,
|
||||||
onLongClick: () -> Unit,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
onClickContinueReading: (() -> Unit)? = null,
|
|
||||||
) {
|
) {
|
||||||
GridItemSelectable(
|
GridItemSelectable(
|
||||||
isSelected = isSelected,
|
isSelected = isSelected,
|
||||||
@ -161,15 +163,15 @@ private fun BoxScope.CoverTextOverlay(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaComfortableGridItem(
|
fun MangaComfortableGridItem(
|
||||||
isSelected: Boolean = false,
|
|
||||||
title: String,
|
|
||||||
titleMaxLines: Int = 2,
|
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||||
|
title: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
titleMaxLines: Int = 2,
|
||||||
coverAlpha: Float = 1f,
|
coverAlpha: Float = 1f,
|
||||||
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
||||||
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
|
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
|
||||||
onLongClick: () -> Unit,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
onClickContinueReading: (() -> Unit)? = null,
|
onClickContinueReading: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
GridItemSelectable(
|
GridItemSelectable(
|
||||||
@ -251,10 +253,10 @@ private fun MangaGridCover(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GridItemTitle(
|
private fun GridItemTitle(
|
||||||
modifier: Modifier,
|
|
||||||
title: String,
|
title: String,
|
||||||
style: TextStyle,
|
style: TextStyle,
|
||||||
minLines: Int,
|
minLines: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
maxLines: Int = 2,
|
maxLines: Int = 2,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -274,10 +276,10 @@ private fun GridItemTitle(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun GridItemSelectable(
|
private fun GridItemSelectable(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
@ -314,13 +316,13 @@ private fun Modifier.selectedOutline(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaListItem(
|
fun MangaListItem(
|
||||||
isSelected: Boolean = false,
|
|
||||||
title: String,
|
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||||
coverAlpha: Float = 1f,
|
title: String,
|
||||||
badge: @Composable (RowScope.() -> Unit),
|
|
||||||
onLongClick: () -> Unit,
|
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
badge: @Composable (RowScope.() -> Unit),
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
coverAlpha: Float = 1f,
|
||||||
onClickContinueReading: (() -> Unit)? = null,
|
onClickContinueReading: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@ -376,7 +378,7 @@ private fun ContinueReadingButton(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.PlayArrow,
|
imageVector = Icons.Filled.PlayArrow,
|
||||||
contentDescription = "",
|
contentDescription = stringResource(MR.strings.action_resume),
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import eu.kanade.tachiyomi.R
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun GlobalSearchItem(
|
internal fun GlobalSearchItem(
|
||||||
@ -19,7 +19,7 @@ internal fun GlobalSearchItem(
|
|||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.action_global_search_query, searchQuery),
|
text = stringResource(MR.strings.action_global_search_query, searchQuery),
|
||||||
modifier = Modifier.zIndex(99f),
|
modifier = Modifier.zIndex(99f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.outlined.Folder
|
import androidx.compose.material.icons.outlined.Folder
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import tachiyomi.presentation.core.components.Badge
|
import tachiyomi.presentation.core.components.Badge
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun DownloadsBadge(count: Long) {
|
internal fun DownloadsBadge(count: Long) {
|
||||||
@ -47,10 +47,10 @@ internal fun LanguageBadge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun BadgePreview() {
|
private fun BadgePreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiPreviewTheme {
|
||||||
Column {
|
Column {
|
||||||
DownloadsBadge(count = 10)
|
DownloadsBadge(count = 10)
|
||||||
UnreadBadge(count = 10)
|
UnreadBadge(count = 10)
|
||||||
|
@ -93,7 +93,7 @@ fun LibraryContent(
|
|||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = notSelectionMode,
|
enabled = { notSelectionMode },
|
||||||
) {
|
) {
|
||||||
LibraryPager(
|
LibraryPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
|
@ -18,10 +18,10 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.core.preference.PreferenceMutableState
|
import eu.kanade.core.preference.PreferenceMutableState
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
@ -124,9 +124,9 @@ private fun LibraryPagerEmptyScreen(
|
|||||||
onGlobalSearchClicked: () -> Unit,
|
onGlobalSearchClicked: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val msg = when {
|
val msg = when {
|
||||||
!searchQuery.isNullOrEmpty() -> R.string.no_results_found
|
!searchQuery.isNullOrEmpty() -> MR.strings.no_results_found
|
||||||
hasActiveFilters -> R.string.error_no_match
|
hasActiveFilters -> MR.strings.error_no_match
|
||||||
else -> R.string.information_no_manga_category
|
else -> MR.strings.information_no_manga_category
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@ -146,7 +146,7 @@ private fun LibraryPagerEmptyScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = msg,
|
stringRes = msg,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,14 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ScrollableTabRow
|
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
|
||||||
import tachiyomi.presentation.core.components.material.TabText
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -20,11 +21,12 @@ internal fun LibraryTabs(
|
|||||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||||
onTabItemClick: (Int) -> Unit,
|
onTabItemClick: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(
|
||||||
ScrollableTabRow(
|
modifier = Modifier.zIndex(1f),
|
||||||
|
) {
|
||||||
|
PrimaryScrollableTabRow(
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
edgePadding = 0.dp,
|
edgePadding = 0.dp,
|
||||||
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
|
|
||||||
// TODO: use default when width is fixed upstream
|
// TODO: use default when width is fixed upstream
|
||||||
// https://issuetracker.google.com/issues/242879624
|
// https://issuetracker.google.com/issues/242879624
|
||||||
divider = {},
|
divider = {},
|
||||||
|
@ -14,14 +14,15 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.tachiyomi.R
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.Pill
|
import tachiyomi.presentation.core.components.Pill
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.theme.active
|
import tachiyomi.presentation.core.theme.active
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -95,23 +96,23 @@ private fun LibraryRegularToolbar(
|
|||||||
actions = {
|
actions = {
|
||||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
persistentListOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_filter),
|
title = stringResource(MR.strings.action_filter),
|
||||||
icon = Icons.Outlined.FilterList,
|
icon = Icons.Outlined.FilterList,
|
||||||
iconTint = filterTint,
|
iconTint = filterTint,
|
||||||
onClick = onClickFilter,
|
onClick = onClickFilter,
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_update_library),
|
title = stringResource(MR.strings.action_update_library),
|
||||||
onClick = onClickGlobalUpdate,
|
onClick = onClickGlobalUpdate,
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_update_category),
|
title = stringResource(MR.strings.action_update_category),
|
||||||
onClick = onClickRefresh,
|
onClick = onClickRefresh,
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_open_random_manga),
|
title = stringResource(MR.strings.action_open_random_manga),
|
||||||
onClick = onClickOpenRandomManga,
|
onClick = onClickOpenRandomManga,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -132,14 +133,14 @@ private fun LibrarySelectionToolbar(
|
|||||||
titleContent = { Text(text = "$selectedCount") },
|
titleContent = { Text(text = "$selectedCount") },
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
persistentListOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_select_all),
|
title = stringResource(MR.strings.action_select_all),
|
||||||
icon = Icons.Outlined.SelectAll,
|
icon = Icons.Outlined.SelectAll,
|
||||||
onClick = onClickSelectAll,
|
onClick = onClickSelectAll,
|
||||||
),
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_select_inverse),
|
title = stringResource(MR.strings.action_select_inverse),
|
||||||
icon = Icons.Outlined.FlipToBack,
|
icon = Icons.Outlined.FlipToBack,
|
||||||
onClick = onClickInvertSelection,
|
onClick = onClickInvertSelection,
|
||||||
),
|
),
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
package eu.kanade.presentation.manga
|
package eu.kanade.presentation.manga
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.PeopleAlt
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -15,20 +23,23 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.manga.model.downloadedFilter
|
import eu.kanade.domain.manga.model.downloadedFilter
|
||||||
import eu.kanade.domain.manga.model.forceDownloaded
|
import eu.kanade.domain.manga.model.forceDownloaded
|
||||||
import eu.kanade.presentation.components.TabbedDialog
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.tachiyomi.R
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.components.RadioItem
|
import tachiyomi.presentation.core.components.RadioItem
|
||||||
import tachiyomi.presentation.core.components.SortItem
|
import tachiyomi.presentation.core.components.SortItem
|
||||||
import tachiyomi.presentation.core.components.TriStateItem
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.theme.active
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterSettingsDialog(
|
fun ChapterSettingsDialog(
|
||||||
@ -37,6 +48,8 @@ fun ChapterSettingsDialog(
|
|||||||
onDownloadFilterChanged: (TriState) -> Unit,
|
onDownloadFilterChanged: (TriState) -> Unit,
|
||||||
onUnreadFilterChanged: (TriState) -> Unit,
|
onUnreadFilterChanged: (TriState) -> Unit,
|
||||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||||
|
scanlatorFilterActive: Boolean,
|
||||||
|
onScanlatorFilterClicked: (() -> Unit),
|
||||||
onSortModeChanged: (Long) -> Unit,
|
onSortModeChanged: (Long) -> Unit,
|
||||||
onDisplayModeChanged: (Long) -> Unit,
|
onDisplayModeChanged: (Long) -> Unit,
|
||||||
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
|
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
|
||||||
@ -52,21 +65,21 @@ fun ChapterSettingsDialog(
|
|||||||
|
|
||||||
TabbedDialog(
|
TabbedDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
tabTitles = listOf(
|
tabTitles = persistentListOf(
|
||||||
stringResource(R.string.action_filter),
|
stringResource(MR.strings.action_filter),
|
||||||
stringResource(R.string.action_sort),
|
stringResource(MR.strings.action_sort),
|
||||||
stringResource(R.string.action_display),
|
stringResource(MR.strings.action_display),
|
||||||
),
|
),
|
||||||
tabOverflowMenuContent = { closeMenu ->
|
tabOverflowMenuContent = { closeMenu ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.set_chapter_settings_as_default)) },
|
text = { Text(stringResource(MR.strings.set_chapter_settings_as_default)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
showSetAsDefaultDialog = true
|
showSetAsDefaultDialog = true
|
||||||
closeMenu()
|
closeMenu()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.action_reset)) },
|
text = { Text(stringResource(MR.strings.action_reset)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onResetToDefault()
|
onResetToDefault()
|
||||||
closeMenu()
|
closeMenu()
|
||||||
@ -83,11 +96,14 @@ fun ChapterSettingsDialog(
|
|||||||
0 -> {
|
0 -> {
|
||||||
FilterPage(
|
FilterPage(
|
||||||
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
||||||
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true },
|
onDownloadFilterChanged = onDownloadFilterChanged
|
||||||
|
.takeUnless { manga?.forceDownloaded() == true },
|
||||||
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
||||||
onUnreadFilterChanged = onUnreadFilterChanged,
|
onUnreadFilterChanged = onUnreadFilterChanged,
|
||||||
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
||||||
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
|
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
|
||||||
|
scanlatorFilterActive = scanlatorFilterActive,
|
||||||
|
onScanlatorFilterClicked = onScanlatorFilterClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
@ -116,22 +132,57 @@ private fun ColumnScope.FilterPage(
|
|||||||
onUnreadFilterChanged: (TriState) -> Unit,
|
onUnreadFilterChanged: (TriState) -> Unit,
|
||||||
bookmarkedFilter: TriState,
|
bookmarkedFilter: TriState,
|
||||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||||
|
scanlatorFilterActive: Boolean,
|
||||||
|
onScanlatorFilterClicked: (() -> Unit),
|
||||||
) {
|
) {
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.label_downloaded),
|
label = stringResource(MR.strings.label_downloaded),
|
||||||
state = downloadFilter,
|
state = downloadFilter,
|
||||||
onClick = onDownloadFilterChanged,
|
onClick = onDownloadFilterChanged,
|
||||||
)
|
)
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.action_filter_unread),
|
label = stringResource(MR.strings.action_filter_unread),
|
||||||
state = unreadFilter,
|
state = unreadFilter,
|
||||||
onClick = onUnreadFilterChanged,
|
onClick = onUnreadFilterChanged,
|
||||||
)
|
)
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.action_filter_bookmarked),
|
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||||
state = bookmarkedFilter,
|
state = bookmarkedFilter,
|
||||||
onClick = onBookmarkedFilterChanged,
|
onClick = onBookmarkedFilterChanged,
|
||||||
)
|
)
|
||||||
|
ScanlatorFilterItem(
|
||||||
|
active = scanlatorFilterActive,
|
||||||
|
onClick = onScanlatorFilterClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScanlatorFilterItem(
|
||||||
|
active: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.PeopleAlt,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (active) {
|
||||||
|
MaterialTheme.colorScheme.active
|
||||||
|
} else {
|
||||||
|
LocalContentColor.current
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.scanlator),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -141,10 +192,10 @@ private fun ColumnScope.SortPage(
|
|||||||
onItemSelected: (Long) -> Unit,
|
onItemSelected: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
listOf(
|
listOf(
|
||||||
R.string.sort_by_source to Manga.CHAPTER_SORTING_SOURCE,
|
MR.strings.sort_by_source to Manga.CHAPTER_SORTING_SOURCE,
|
||||||
R.string.sort_by_number to Manga.CHAPTER_SORTING_NUMBER,
|
MR.strings.sort_by_number to Manga.CHAPTER_SORTING_NUMBER,
|
||||||
R.string.sort_by_upload_date to Manga.CHAPTER_SORTING_UPLOAD_DATE,
|
MR.strings.sort_by_upload_date to Manga.CHAPTER_SORTING_UPLOAD_DATE,
|
||||||
R.string.action_sort_alpha to Manga.CHAPTER_SORTING_ALPHABET,
|
MR.strings.action_sort_alpha to Manga.CHAPTER_SORTING_ALPHABET,
|
||||||
).map { (titleRes, mode) ->
|
).map { (titleRes, mode) ->
|
||||||
SortItem(
|
SortItem(
|
||||||
label = stringResource(titleRes),
|
label = stringResource(titleRes),
|
||||||
@ -160,8 +211,8 @@ private fun ColumnScope.DisplayPage(
|
|||||||
onItemSelected: (Long) -> Unit,
|
onItemSelected: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
listOf(
|
listOf(
|
||||||
R.string.show_title to Manga.CHAPTER_DISPLAY_NAME,
|
MR.strings.show_title to Manga.CHAPTER_DISPLAY_NAME,
|
||||||
R.string.show_chapter_number to Manga.CHAPTER_DISPLAY_NUMBER,
|
MR.strings.show_chapter_number to Manga.CHAPTER_DISPLAY_NUMBER,
|
||||||
).map { (titleRes, mode) ->
|
).map { (titleRes, mode) ->
|
||||||
RadioItem(
|
RadioItem(
|
||||||
label = stringResource(titleRes),
|
label = stringResource(titleRes),
|
||||||
@ -180,15 +231,15 @@ private fun SetAsDefaultDialog(
|
|||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = { Text(text = stringResource(R.string.chapter_settings)) },
|
title = { Text(text = stringResource(MR.strings.chapter_settings)) },
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.confirm_set_chapter_settings))
|
Text(text = stringResource(MR.strings.confirm_set_chapter_settings))
|
||||||
|
|
||||||
LabeledCheckbox(
|
LabeledCheckbox(
|
||||||
label = stringResource(R.string.also_set_chapter_settings_for_library),
|
label = stringResource(MR.strings.also_set_chapter_settings_for_library),
|
||||||
checked = optionalChecked,
|
checked = optionalChecked,
|
||||||
onCheckedChange = { optionalChecked = it },
|
onCheckedChange = { optionalChecked = it },
|
||||||
)
|
)
|
||||||
@ -196,7 +247,7 @@ private fun SetAsDefaultDialog(
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -206,7 +257,7 @@ private fun SetAsDefaultDialog(
|
|||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -4,13 +4,14 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import tachiyomi.i18n.MR
|
||||||
import androidx.compose.ui.unit.dp
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import eu.kanade.tachiyomi.R
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DuplicateMangaDialog(
|
fun DuplicateMangaDialog(
|
||||||
@ -21,14 +22,14 @@ fun DuplicateMangaDialog(
|
|||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.are_you_sure))
|
Text(text = stringResource(MR.strings.are_you_sure))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
|
Text(text = stringResource(MR.strings.confirm_add_duplicate_manga))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
FlowRow(
|
FlowRow(
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -36,13 +37,13 @@ fun DuplicateMangaDialog(
|
|||||||
onOpenManga()
|
onOpenManga()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_show_manga))
|
Text(text = stringResource(MR.strings.action_show_manga))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -50,7 +51,7 @@ fun DuplicateMangaDialog(
|
|||||||
onConfirm()
|
onConfirm()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_add))
|
Text(text = stringResource(MR.strings.action_add))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,11 +5,9 @@ import androidx.compose.animation.AnimatedVisibility
|
|||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
@ -28,9 +26,7 @@ import androidx.compose.foundation.rememberScrollState
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -48,12 +44,10 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import eu.kanade.domain.manga.model.chaptersFiltered
|
import eu.kanade.presentation.components.relativeDateText
|
||||||
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.manga.components.ChapterHeader
|
import eu.kanade.presentation.manga.components.ChapterHeader
|
||||||
import eu.kanade.presentation.manga.components.ExpandableMangaDescription
|
import eu.kanade.presentation.manga.components.ExpandableMangaDescription
|
||||||
@ -62,38 +56,35 @@ import eu.kanade.presentation.manga.components.MangaBottomActionMenu
|
|||||||
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
||||||
import eu.kanade.presentation.manga.components.MangaInfoBox
|
import eu.kanade.presentation.manga.components.MangaInfoBox
|
||||||
import eu.kanade.presentation.manga.components.MangaToolbar
|
import eu.kanade.presentation.manga.components.MangaToolbar
|
||||||
|
import eu.kanade.presentation.manga.components.MissingChapterCountListItem
|
||||||
import eu.kanade.presentation.util.formatChapterNumber
|
import eu.kanade.presentation.util.formatChapterNumber
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||||
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
||||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.chapter.service.missingChaptersCount
|
import tachiyomi.domain.chapter.service.missingChaptersCount
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.source.model.StubSource
|
import tachiyomi.domain.source.model.StubSource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.TwoPanelBox
|
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||||
import tachiyomi.presentation.core.components.VerticalFastScroller
|
import tachiyomi.presentation.core.components.VerticalFastScroller
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrollingUp
|
import tachiyomi.presentation.core.util.isScrollingUp
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.source.local.isLocal
|
||||||
import java.text.DateFormat
|
import java.time.Instant
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaScreen(
|
fun MangaScreen(
|
||||||
state: MangaScreenModel.State.Success,
|
state: MangaScreenModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
fetchInterval: Int?,
|
nextUpdate: Instant?,
|
||||||
dateRelativeTime: Boolean,
|
|
||||||
dateFormat: DateFormat,
|
|
||||||
isTabletUi: Boolean,
|
isTabletUi: Boolean,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
@ -103,7 +94,7 @@ fun MangaScreen(
|
|||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
onTrackingClicked: (() -> Unit)?,
|
onTrackingClicked: () -> Unit,
|
||||||
|
|
||||||
// For tags menu
|
// For tags menu
|
||||||
onTagSearch: (String) -> Unit,
|
onTagSearch: (String) -> Unit,
|
||||||
@ -148,9 +139,7 @@ fun MangaScreen(
|
|||||||
MangaScreenSmallImpl(
|
MangaScreenSmallImpl(
|
||||||
state = state,
|
state = state,
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
dateRelativeTime = dateRelativeTime,
|
nextUpdate = nextUpdate,
|
||||||
dateFormat = dateFormat,
|
|
||||||
fetchInterval = fetchInterval,
|
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
onBackClicked = onBackClicked,
|
onBackClicked = onBackClicked,
|
||||||
@ -185,11 +174,9 @@ fun MangaScreen(
|
|||||||
MangaScreenLargeImpl(
|
MangaScreenLargeImpl(
|
||||||
state = state,
|
state = state,
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
dateRelativeTime = dateRelativeTime,
|
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
dateFormat = dateFormat,
|
nextUpdate = nextUpdate,
|
||||||
fetchInterval = fetchInterval,
|
|
||||||
onBackClicked = onBackClicked,
|
onBackClicked = onBackClicked,
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
onDownloadChapter = onDownloadChapter,
|
onDownloadChapter = onDownloadChapter,
|
||||||
@ -225,9 +212,7 @@ fun MangaScreen(
|
|||||||
private fun MangaScreenSmallImpl(
|
private fun MangaScreenSmallImpl(
|
||||||
state: MangaScreenModel.State.Success,
|
state: MangaScreenModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
dateRelativeTime: Boolean,
|
nextUpdate: Instant?,
|
||||||
dateFormat: DateFormat,
|
|
||||||
fetchInterval: Int?,
|
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
@ -236,7 +221,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
onTrackingClicked: (() -> Unit)?,
|
onTrackingClicked: () -> Unit,
|
||||||
|
|
||||||
// For tags menu
|
// For tags menu
|
||||||
onTagSearch: (String) -> Unit,
|
onTagSearch: (String) -> Unit,
|
||||||
@ -273,13 +258,12 @@ private fun MangaScreenSmallImpl(
|
|||||||
) {
|
) {
|
||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
val chapters = remember(state) { state.processedChapters }
|
val (chapters, listItem, isAnySelected) = remember(state) {
|
||||||
val listItem = remember(state) { state.chapterListItems }
|
Triple(
|
||||||
|
first = state.processedChapters,
|
||||||
val isAnySelected by remember {
|
second = state.chapterListItems,
|
||||||
derivedStateOf {
|
third = state.isAnySelected,
|
||||||
chapters.fastAny { it.selected }
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
val internalOnBackPressed = {
|
||||||
@ -314,7 +298,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
titleAlphaProvider = { animatedTitleAlpha },
|
titleAlphaProvider = { animatedTitleAlpha },
|
||||||
backgroundAlphaProvider = { animatedBgAlpha },
|
backgroundAlphaProvider = { animatedBgAlpha },
|
||||||
hasFilters = state.manga.chaptersFiltered(),
|
hasFilters = state.filterActive,
|
||||||
onBackClicked = internalOnBackPressed,
|
onBackClicked = internalOnBackPressed,
|
||||||
onClickFilter = onFilterClicked,
|
onClickFilter = onFilterClicked,
|
||||||
onClickShare = onShareClicked,
|
onClickShare = onShareClicked,
|
||||||
@ -356,7 +340,9 @@ private fun MangaScreenSmallImpl(
|
|||||||
val isReading = remember(state.chapters) {
|
val isReading = remember(state.chapters) {
|
||||||
state.chapters.fastAny { it.chapter.read }
|
state.chapters.fastAny { it.chapter.read }
|
||||||
}
|
}
|
||||||
Text(text = stringResource(if (isReading) R.string.action_resume else R.string.action_start))
|
Text(
|
||||||
|
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
@ -370,8 +356,8 @@ private fun MangaScreenSmallImpl(
|
|||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !isAnySelected,
|
enabled = { !isAnySelected },
|
||||||
indicatorPadding = WindowInsets.systemBars.only(WindowInsetsSides.Top).asPaddingValues(),
|
indicatorPadding = PaddingValues(top = topPadding),
|
||||||
) {
|
) {
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
VerticalFastScroller(
|
VerticalFastScroller(
|
||||||
@ -414,7 +400,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
MangaActionRow(
|
MangaActionRow(
|
||||||
favorite = state.manga.favorite,
|
favorite = state.manga.favorite,
|
||||||
trackingCount = state.trackingCount,
|
trackingCount = state.trackingCount,
|
||||||
fetchInterval = fetchInterval,
|
nextUpdate = nextUpdate,
|
||||||
isUserIntervalMode = state.manga.fetchInterval < 0,
|
isUserIntervalMode = state.manga.fetchInterval < 0,
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
onWebViewClicked = onWebViewClicked,
|
onWebViewClicked = onWebViewClicked,
|
||||||
@ -457,8 +443,6 @@ private fun MangaScreenSmallImpl(
|
|||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapters = listItem,
|
chapters = listItem,
|
||||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||||
dateRelativeTime = dateRelativeTime,
|
|
||||||
dateFormat = dateFormat,
|
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
@ -476,9 +460,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
fun MangaScreenLargeImpl(
|
fun MangaScreenLargeImpl(
|
||||||
state: MangaScreenModel.State.Success,
|
state: MangaScreenModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
dateRelativeTime: Boolean,
|
nextUpdate: Instant?,
|
||||||
dateFormat: DateFormat,
|
|
||||||
fetchInterval: Int?,
|
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
@ -487,7 +469,7 @@ fun MangaScreenLargeImpl(
|
|||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
onTrackingClicked: (() -> Unit)?,
|
onTrackingClicked: () -> Unit,
|
||||||
|
|
||||||
// For tags menu
|
// For tags menu
|
||||||
onTagSearch: (String) -> Unit,
|
onTagSearch: (String) -> Unit,
|
||||||
@ -525,27 +507,17 @@ fun MangaScreenLargeImpl(
|
|||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
val chapters = remember(state) { state.processedChapters }
|
val (chapters, listItem, isAnySelected) = remember(state) {
|
||||||
val listItem = remember(state) { state.chapterListItems }
|
Triple(
|
||||||
|
first = state.processedChapters,
|
||||||
val isAnySelected by remember {
|
second = state.chapterListItems,
|
||||||
derivedStateOf {
|
third = state.isAnySelected,
|
||||||
chapters.fastAny { it.selected }
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||||
var topBarHeight by remember { mutableIntStateOf(0) }
|
var topBarHeight by remember { mutableIntStateOf(0) }
|
||||||
PullRefresh(
|
|
||||||
refreshing = state.isRefreshingData,
|
|
||||||
onRefresh = onRefresh,
|
|
||||||
enabled = !isAnySelected,
|
|
||||||
indicatorPadding = PaddingValues(
|
|
||||||
start = insetPadding.calculateStartPadding(layoutDirection),
|
|
||||||
top = with(density) { topBarHeight.toDp() },
|
|
||||||
end = insetPadding.calculateEndPadding(layoutDirection),
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
val internalOnBackPressed = {
|
||||||
@ -567,7 +539,7 @@ fun MangaScreenLargeImpl(
|
|||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
|
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
|
||||||
backgroundAlphaProvider = { 1f },
|
backgroundAlphaProvider = { 1f },
|
||||||
hasFilters = state.manga.chaptersFiltered(),
|
hasFilters = state.filterActive,
|
||||||
onBackClicked = internalOnBackPressed,
|
onBackClicked = internalOnBackPressed,
|
||||||
onClickFilter = onFilterButtonClicked,
|
onClickFilter = onFilterButtonClicked,
|
||||||
onClickShare = onShareClicked,
|
onClickShare = onShareClicked,
|
||||||
@ -614,7 +586,11 @@ fun MangaScreenLargeImpl(
|
|||||||
val isReading = remember(state.chapters) {
|
val isReading = remember(state.chapters) {
|
||||||
state.chapters.fastAny { it.chapter.read }
|
state.chapters.fastAny { it.chapter.read }
|
||||||
}
|
}
|
||||||
Text(text = stringResource(if (isReading) R.string.action_resume else R.string.action_start))
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
if (isReading) MR.strings.action_resume else MR.strings.action_start,
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
@ -623,6 +599,16 @@ fun MangaScreenLargeImpl(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
|
PullRefresh(
|
||||||
|
refreshing = state.isRefreshingData,
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
enabled = { !isAnySelected },
|
||||||
|
indicatorPadding = PaddingValues(
|
||||||
|
start = insetPadding.calculateStartPadding(layoutDirection),
|
||||||
|
top = with(density) { topBarHeight.toDp() },
|
||||||
|
end = insetPadding.calculateEndPadding(layoutDirection),
|
||||||
|
),
|
||||||
|
) {
|
||||||
TwoPanelBox(
|
TwoPanelBox(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||||
@ -650,7 +636,7 @@ fun MangaScreenLargeImpl(
|
|||||||
MangaActionRow(
|
MangaActionRow(
|
||||||
favorite = state.manga.favorite,
|
favorite = state.manga.favorite,
|
||||||
trackingCount = state.trackingCount,
|
trackingCount = state.trackingCount,
|
||||||
fetchInterval = fetchInterval,
|
nextUpdate = nextUpdate,
|
||||||
isUserIntervalMode = state.manga.fetchInterval < 0,
|
isUserIntervalMode = state.manga.fetchInterval < 0,
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
onWebViewClicked = onWebViewClicked,
|
onWebViewClicked = onWebViewClicked,
|
||||||
@ -700,8 +686,6 @@ fun MangaScreenLargeImpl(
|
|||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapters = listItem,
|
chapters = listItem,
|
||||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||||
dateRelativeTime = dateRelativeTime,
|
|
||||||
dateFormat = dateFormat,
|
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
@ -720,13 +704,13 @@ fun MangaScreenLargeImpl(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SharedMangaBottomActionMenu(
|
private fun SharedMangaBottomActionMenu(
|
||||||
selected: List<ChapterList.Item>,
|
selected: List<ChapterList.Item>,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
||||||
onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
|
onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
|
||||||
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
fillFraction: Float,
|
fillFraction: Float,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
MangaBottomActionMenu(
|
MangaBottomActionMenu(
|
||||||
visible = selected.isNotEmpty(),
|
visible = selected.isNotEmpty(),
|
||||||
@ -754,7 +738,7 @@ private fun SharedMangaBottomActionMenu(
|
|||||||
onDeleteClicked = {
|
onDeleteClicked = {
|
||||||
onMultiDeleteClicked(selected.fastMap { it.chapter })
|
onMultiDeleteClicked(selected.fastMap { it.chapter })
|
||||||
}.takeIf {
|
}.takeIf {
|
||||||
onDownloadChapter != null && selected.fastAny { it.downloadState == Download.State.DOWNLOADED }
|
selected.fastAny { it.downloadState == Download.State.DOWNLOADED }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -763,8 +747,6 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapters: List<ChapterList>,
|
chapters: List<ChapterList>,
|
||||||
isAnyChapterSelected: Boolean,
|
isAnyChapterSelected: Boolean,
|
||||||
dateRelativeTime: Boolean,
|
|
||||||
dateFormat: DateFormat,
|
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
@ -783,54 +765,27 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
contentType = { MangaScreenItem.CHAPTER },
|
contentType = { MangaScreenItem.CHAPTER },
|
||||||
) { item ->
|
) { item ->
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
when (item) {
|
when (item) {
|
||||||
is ChapterList.MissingCount -> {
|
is ChapterList.MissingCount -> {
|
||||||
Row(
|
MissingChapterCountListItem(count = item.count)
|
||||||
modifier = Modifier.padding(
|
|
||||||
horizontal = MaterialTheme.padding.medium,
|
|
||||||
vertical = MaterialTheme.padding.small,
|
|
||||||
),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
|
||||||
) {
|
|
||||||
HorizontalDivider(modifier = Modifier.weight(1f))
|
|
||||||
Text(
|
|
||||||
text = pluralStringResource(
|
|
||||||
id = R.plurals.missing_chapters,
|
|
||||||
count = item.count,
|
|
||||||
item.count,
|
|
||||||
),
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
)
|
|
||||||
HorizontalDivider(modifier = Modifier.weight(1f))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is ChapterList.Item -> {
|
is ChapterList.Item -> {
|
||||||
MangaChapterListItem(
|
MangaChapterListItem(
|
||||||
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.display_mode_chapter,
|
MR.strings.display_mode_chapter,
|
||||||
formatChapterNumber(item.chapter.chapterNumber),
|
formatChapterNumber(item.chapter.chapterNumber),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
item.chapter.name
|
item.chapter.name
|
||||||
},
|
},
|
||||||
date = item.chapter.dateUpload
|
date = relativeDateText(item.chapter.dateUpload),
|
||||||
.takeIf { it > 0L }
|
|
||||||
?.let {
|
|
||||||
Date(it).toRelativeString(
|
|
||||||
context,
|
|
||||||
dateRelativeTime,
|
|
||||||
dateFormat,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
readProgress = item.chapter.lastPageRead
|
readProgress = item.chapter.lastPageRead
|
||||||
.takeIf { !item.chapter.read && it > 0L }
|
.takeIf { !item.chapter.read && it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.chapter_progress,
|
MR.strings.chapter_progress,
|
||||||
it + 1,
|
it + 1,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -838,7 +793,7 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
read = item.chapter.read,
|
read = item.chapter.read,
|
||||||
bookmark = item.chapter.bookmark,
|
bookmark = item.chapter.bookmark,
|
||||||
selected = item.selected,
|
selected = item.selected,
|
||||||
downloadIndicatorEnabled = !isAnyChapterSelected,
|
downloadIndicatorEnabled = !isAnyChapterSelected && !manga.isLocal(),
|
||||||
downloadStateProvider = { item.downloadState },
|
downloadStateProvider = { item.downloadState },
|
||||||
downloadProgressProvider = { item.downloadProgress },
|
downloadProgressProvider = { item.downloadProgress },
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
|
@ -19,8 +19,8 @@ import tachiyomi.presentation.core.components.material.padding
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BaseMangaListItem(
|
fun BaseMangaListItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
onClickItem: () -> Unit = {},
|
onClickItem: () -> Unit = {},
|
||||||
onClickCover: () -> Unit = onClickItem,
|
onClickCover: () -> Unit = onClickItem,
|
||||||
cover: @Composable RowScope.() -> Unit = { defaultCover(manga, onClickCover) },
|
cover: @Composable RowScope.() -> Unit = { defaultCover(manga, onClickCover) },
|
||||||
|
@ -29,13 +29,14 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.IconButtonTokens
|
import tachiyomi.presentation.core.components.material.IconButtonTokens
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
enum class ChapterDownloadAction {
|
enum class ChapterDownloadAction {
|
||||||
@ -48,10 +49,10 @@ enum class ChapterDownloadAction {
|
|||||||
@Composable
|
@Composable
|
||||||
fun ChapterDownloadIndicator(
|
fun ChapterDownloadIndicator(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
downloadStateProvider: () -> Download.State,
|
downloadStateProvider: () -> Download.State,
|
||||||
downloadProgressProvider: () -> Int,
|
downloadProgressProvider: () -> Int,
|
||||||
onClick: (ChapterDownloadAction) -> Unit,
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
when (val downloadState = downloadStateProvider()) {
|
when (val downloadState = downloadStateProvider()) {
|
||||||
Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(
|
Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(
|
||||||
@ -98,7 +99,7 @@ private fun NotDownloadedIndicator(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_download_chapter_24dp),
|
painter = painterResource(R.drawable.ic_download_chapter_24dp),
|
||||||
contentDescription = stringResource(R.string.manga_download),
|
contentDescription = stringResource(MR.strings.manga_download),
|
||||||
modifier = Modifier.size(IndicatorSize),
|
modifier = Modifier.size(IndicatorSize),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@ -108,10 +109,10 @@ private fun NotDownloadedIndicator(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DownloadingIndicator(
|
private fun DownloadingIndicator(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
downloadState: Download.State,
|
downloadState: Download.State,
|
||||||
downloadProgressProvider: () -> Int,
|
downloadProgressProvider: () -> Int,
|
||||||
onClick: (ChapterDownloadAction) -> Unit,
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||||
Box(
|
Box(
|
||||||
@ -148,7 +149,7 @@ private fun DownloadingIndicator(
|
|||||||
MaterialTheme.colorScheme.background
|
MaterialTheme.colorScheme.background
|
||||||
}
|
}
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
progress = animatedProgress,
|
progress = { animatedProgress },
|
||||||
modifier = IndicatorModifier,
|
modifier = IndicatorModifier,
|
||||||
color = strokeColor,
|
color = strokeColor,
|
||||||
strokeWidth = IndicatorSize / 2,
|
strokeWidth = IndicatorSize / 2,
|
||||||
@ -156,14 +157,14 @@ private fun DownloadingIndicator(
|
|||||||
}
|
}
|
||||||
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_start_downloading_now)) },
|
text = { Text(text = stringResource(MR.strings.action_start_downloading_now)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onClick(ChapterDownloadAction.START_NOW)
|
onClick(ChapterDownloadAction.START_NOW)
|
||||||
isMenuExpanded = false
|
isMenuExpanded = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_cancel)) },
|
text = { Text(text = stringResource(MR.strings.action_cancel)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onClick(ChapterDownloadAction.CANCEL)
|
onClick(ChapterDownloadAction.CANCEL)
|
||||||
isMenuExpanded = false
|
isMenuExpanded = false
|
||||||
@ -204,7 +205,7 @@ private fun DownloadedIndicator(
|
|||||||
)
|
)
|
||||||
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_delete)) },
|
text = { Text(text = stringResource(MR.strings.action_delete)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onClick(ChapterDownloadAction.DELETE)
|
onClick(ChapterDownloadAction.DELETE)
|
||||||
isMenuExpanded = false
|
isMenuExpanded = false
|
||||||
@ -232,7 +233,7 @@ private fun ErrorIndicator(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.ErrorOutline,
|
imageVector = Icons.Outlined.ErrorOutline,
|
||||||
contentDescription = stringResource(R.string.chapter_error),
|
contentDescription = stringResource(MR.strings.chapter_error),
|
||||||
modifier = Modifier.size(IndicatorSize),
|
modifier = Modifier.size(IndicatorSize),
|
||||||
tint = MaterialTheme.colorScheme.error,
|
tint = MaterialTheme.colorScheme.error,
|
||||||
)
|
)
|
||||||
|
@ -9,12 +9,13 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterHeader(
|
fun ChapterHeader(
|
||||||
@ -22,22 +23,23 @@ fun ChapterHeader(
|
|||||||
chapterCount: Int?,
|
chapterCount: Int?,
|
||||||
missingChapterCount: Int,
|
missingChapterCount: Int,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(
|
.clickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
)
|
)
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (chapterCount == null) {
|
text = if (chapterCount == null) {
|
||||||
stringResource(R.string.chapters)
|
stringResource(MR.strings.chapters)
|
||||||
} else {
|
} else {
|
||||||
pluralStringResource(id = R.plurals.manga_num_chapters, count = chapterCount, chapterCount)
|
pluralStringResource(MR.plurals.manga_num_chapters, count = chapterCount, chapterCount)
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
@ -54,7 +56,7 @@ private fun MissingChaptersWarning(count: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = pluralStringResource(id = R.plurals.missing_chapters, count = count, count),
|
text = pluralStringResource(MR.plurals.missing_chapters, count = count, count),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
@ -2,13 +2,24 @@ package eu.kanade.presentation.manga.components
|
|||||||
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DotSeparatorText() {
|
fun DotSeparatorText(
|
||||||
Text(text = " • ")
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = " • ",
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DotSeparatorNoSpaceText() {
|
fun DotSeparatorNoSpaceText(
|
||||||
Text(text = "•")
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "•",
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,12 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||||
import androidx.compose.material.icons.outlined.BookmarkAdd
|
import androidx.compose.material.icons.outlined.BookmarkAdd
|
||||||
import androidx.compose.material.icons.outlined.BookmarkRemove
|
import androidx.compose.material.icons.outlined.BookmarkRemove
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.DoneAll
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
import androidx.compose.material.icons.outlined.Download
|
import androidx.compose.material.icons.outlined.Download
|
||||||
import androidx.compose.material.icons.outlined.Label
|
|
||||||
import androidx.compose.material.icons.outlined.RemoveDone
|
import androidx.compose.material.icons.outlined.RemoveDone
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -47,7 +47,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -58,6 +57,8 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -106,7 +107,7 @@ fun MangaBottomActionMenu(
|
|||||||
) {
|
) {
|
||||||
if (onBookmarkClicked != null) {
|
if (onBookmarkClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_bookmark),
|
title = stringResource(MR.strings.action_bookmark),
|
||||||
icon = Icons.Outlined.BookmarkAdd,
|
icon = Icons.Outlined.BookmarkAdd,
|
||||||
toConfirm = confirm[0],
|
toConfirm = confirm[0],
|
||||||
onLongClick = { onLongClickItem(0) },
|
onLongClick = { onLongClickItem(0) },
|
||||||
@ -115,7 +116,7 @@ fun MangaBottomActionMenu(
|
|||||||
}
|
}
|
||||||
if (onRemoveBookmarkClicked != null) {
|
if (onRemoveBookmarkClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_remove_bookmark),
|
title = stringResource(MR.strings.action_remove_bookmark),
|
||||||
icon = Icons.Outlined.BookmarkRemove,
|
icon = Icons.Outlined.BookmarkRemove,
|
||||||
toConfirm = confirm[1],
|
toConfirm = confirm[1],
|
||||||
onLongClick = { onLongClickItem(1) },
|
onLongClick = { onLongClickItem(1) },
|
||||||
@ -124,7 +125,7 @@ fun MangaBottomActionMenu(
|
|||||||
}
|
}
|
||||||
if (onMarkAsReadClicked != null) {
|
if (onMarkAsReadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_read),
|
title = stringResource(MR.strings.action_mark_as_read),
|
||||||
icon = Icons.Outlined.DoneAll,
|
icon = Icons.Outlined.DoneAll,
|
||||||
toConfirm = confirm[2],
|
toConfirm = confirm[2],
|
||||||
onLongClick = { onLongClickItem(2) },
|
onLongClick = { onLongClickItem(2) },
|
||||||
@ -133,7 +134,7 @@ fun MangaBottomActionMenu(
|
|||||||
}
|
}
|
||||||
if (onMarkAsUnreadClicked != null) {
|
if (onMarkAsUnreadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_unread),
|
title = stringResource(MR.strings.action_mark_as_unread),
|
||||||
icon = Icons.Outlined.RemoveDone,
|
icon = Icons.Outlined.RemoveDone,
|
||||||
toConfirm = confirm[3],
|
toConfirm = confirm[3],
|
||||||
onLongClick = { onLongClickItem(3) },
|
onLongClick = { onLongClickItem(3) },
|
||||||
@ -142,7 +143,7 @@ fun MangaBottomActionMenu(
|
|||||||
}
|
}
|
||||||
if (onMarkPreviousAsReadClicked != null) {
|
if (onMarkPreviousAsReadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_previous_as_read),
|
title = stringResource(MR.strings.action_mark_previous_as_read),
|
||||||
icon = ImageVector.vectorResource(R.drawable.ic_done_prev_24dp),
|
icon = ImageVector.vectorResource(R.drawable.ic_done_prev_24dp),
|
||||||
toConfirm = confirm[4],
|
toConfirm = confirm[4],
|
||||||
onLongClick = { onLongClickItem(4) },
|
onLongClick = { onLongClickItem(4) },
|
||||||
@ -151,7 +152,7 @@ fun MangaBottomActionMenu(
|
|||||||
}
|
}
|
||||||
if (onDownloadClicked != null) {
|
if (onDownloadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_download),
|
title = stringResource(MR.strings.action_download),
|
||||||
icon = Icons.Outlined.Download,
|
icon = Icons.Outlined.Download,
|
||||||
toConfirm = confirm[5],
|
toConfirm = confirm[5],
|
||||||
onLongClick = { onLongClickItem(5) },
|
onLongClick = { onLongClickItem(5) },
|
||||||
@ -160,7 +161,7 @@ fun MangaBottomActionMenu(
|
|||||||
}
|
}
|
||||||
if (onDeleteClicked != null) {
|
if (onDeleteClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_delete),
|
title = stringResource(MR.strings.action_delete),
|
||||||
icon = Icons.Outlined.Delete,
|
icon = Icons.Outlined.Delete,
|
||||||
toConfirm = confirm[6],
|
toConfirm = confirm[6],
|
||||||
onLongClick = { onLongClickItem(6) },
|
onLongClick = { onLongClickItem(6) },
|
||||||
@ -181,7 +182,10 @@ private fun RowScope.Button(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
content: (@Composable () -> Unit)? = null,
|
content: (@Composable () -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
|
val animatedWeight by animateFloatAsState(
|
||||||
|
targetValue = if (toConfirm) 2f else 1f,
|
||||||
|
label = "weight",
|
||||||
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
@ -218,12 +222,12 @@ private fun RowScope.Button(
|
|||||||
@Composable
|
@Composable
|
||||||
fun LibraryBottomActionMenu(
|
fun LibraryBottomActionMenu(
|
||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onChangeCategoryClicked: () -> Unit,
|
onChangeCategoryClicked: () -> Unit,
|
||||||
onMarkAsReadClicked: () -> Unit,
|
onMarkAsReadClicked: () -> Unit,
|
||||||
onMarkAsUnreadClicked: () -> Unit,
|
onMarkAsUnreadClicked: () -> Unit,
|
||||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||||
onDeleteClicked: () -> Unit,
|
onDeleteClicked: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
@ -257,21 +261,21 @@ fun LibraryBottomActionMenu(
|
|||||||
.padding(horizontal = 8.dp, vertical = 12.dp),
|
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_move_category),
|
title = stringResource(MR.strings.action_move_category),
|
||||||
icon = Icons.Outlined.Label,
|
icon = Icons.AutoMirrored.Outlined.Label,
|
||||||
toConfirm = confirm[0],
|
toConfirm = confirm[0],
|
||||||
onLongClick = { onLongClickItem(0) },
|
onLongClick = { onLongClickItem(0) },
|
||||||
onClick = onChangeCategoryClicked,
|
onClick = onChangeCategoryClicked,
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_read),
|
title = stringResource(MR.strings.action_mark_as_read),
|
||||||
icon = Icons.Outlined.DoneAll,
|
icon = Icons.Outlined.DoneAll,
|
||||||
toConfirm = confirm[1],
|
toConfirm = confirm[1],
|
||||||
onLongClick = { onLongClickItem(1) },
|
onLongClick = { onLongClickItem(1) },
|
||||||
onClick = onMarkAsReadClicked,
|
onClick = onMarkAsReadClicked,
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_unread),
|
title = stringResource(MR.strings.action_mark_as_unread),
|
||||||
icon = Icons.Outlined.RemoveDone,
|
icon = Icons.Outlined.RemoveDone,
|
||||||
toConfirm = confirm[2],
|
toConfirm = confirm[2],
|
||||||
onLongClick = { onLongClickItem(2) },
|
onLongClick = { onLongClickItem(2) },
|
||||||
@ -280,7 +284,7 @@ fun LibraryBottomActionMenu(
|
|||||||
if (onDownloadClicked != null) {
|
if (onDownloadClicked != null) {
|
||||||
var downloadExpanded by remember { mutableStateOf(false) }
|
var downloadExpanded by remember { mutableStateOf(false) }
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_download),
|
title = stringResource(MR.strings.action_download),
|
||||||
icon = Icons.Outlined.Download,
|
icon = Icons.Outlined.Download,
|
||||||
toConfirm = confirm[3],
|
toConfirm = confirm[3],
|
||||||
onLongClick = { onLongClickItem(3) },
|
onLongClick = { onLongClickItem(3) },
|
||||||
@ -295,7 +299,7 @@ fun LibraryBottomActionMenu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_delete),
|
title = stringResource(MR.strings.action_delete),
|
||||||
icon = Icons.Outlined.Delete,
|
icon = Icons.Outlined.Delete,
|
||||||
toConfirm = confirm[4],
|
toConfirm = confirm[4],
|
||||||
onLongClick = { onLongClickItem(4) },
|
onLongClick = { onLongClickItem(4) },
|
||||||
|
@ -33,7 +33,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -42,23 +41,22 @@ import androidx.compose.ui.platform.LocalDensity
|
|||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||||
import androidx.compose.ui.platform.ViewConfiguration
|
import androidx.compose.ui.platform.ViewConfiguration
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import me.saket.swipe.SwipeableActionsBox
|
import me.saket.swipe.SwipeableActionsBox
|
||||||
import me.saket.swipe.rememberSwipeableActionsState
|
import me.saket.swipe.rememberSwipeableActionsState
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
||||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaChapterListItem(
|
fun MangaChapterListItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
title: String,
|
title: String,
|
||||||
date: String?,
|
date: String?,
|
||||||
readProgress: String?,
|
readProgress: String?,
|
||||||
@ -75,6 +73,7 @@ fun MangaChapterListItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
|
onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
|
||||||
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
@ -143,7 +142,7 @@ fun MangaChapterListItem(
|
|||||||
if (!read) {
|
if (!read) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Circle,
|
imageVector = Icons.Filled.Circle,
|
||||||
contentDescription = stringResource(R.string.unread),
|
contentDescription = stringResource(MR.strings.unread),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(8.dp)
|
.height(8.dp)
|
||||||
.padding(end = 4.dp),
|
.padding(end = 4.dp),
|
||||||
@ -153,7 +152,7 @@ fun MangaChapterListItem(
|
|||||||
if (bookmark) {
|
if (bookmark) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Bookmark,
|
imageVector = Icons.Filled.Bookmark,
|
||||||
contentDescription = stringResource(R.string.action_filter_bookmarked),
|
contentDescription = stringResource(MR.strings.action_filter_bookmarked),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
@ -189,7 +188,7 @@ fun MangaChapterListItem(
|
|||||||
text = readProgress,
|
text = readProgress,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.alpha(ReadItemAlpha),
|
color = LocalContentColor.current.copy(alpha = ReadItemAlpha),
|
||||||
)
|
)
|
||||||
if (scanlator != null) DotSeparatorText()
|
if (scanlator != null) DotSeparatorText()
|
||||||
}
|
}
|
||||||
@ -204,18 +203,16 @@ fun MangaChapterListItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onDownloadClick != null) {
|
|
||||||
ChapterDownloadIndicator(
|
ChapterDownloadIndicator(
|
||||||
enabled = downloadIndicatorEnabled,
|
enabled = downloadIndicatorEnabled,
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
downloadStateProvider = downloadStateProvider,
|
downloadStateProvider = downloadStateProvider,
|
||||||
downloadProgressProvider = downloadProgressProvider,
|
downloadProgressProvider = downloadProgressProvider,
|
||||||
onClick = onDownloadClick,
|
onClick = { onDownloadClick?.invoke(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSwipeAction(
|
private fun getSwipeAction(
|
||||||
|
@ -22,8 +22,8 @@ enum class MangaCover(val ratio: Float) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
data: Any?,
|
data: Any?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
contentDescription: String = "",
|
contentDescription: String = "",
|
||||||
shape: Shape = MaterialTheme.shapes.extraSmall,
|
shape: Shape = MaterialTheme.shapes.extraSmall,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.manga.components
|
|||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.os.Build
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -31,7 +32,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
@ -46,10 +46,12 @@ import eu.kanade.presentation.components.AppBar
|
|||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.presentation.manga.EditCoverAction
|
import eu.kanade.presentation.manga.EditCoverAction
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -83,29 +85,25 @@ fun MangaCoverDialog(
|
|||||||
IconButton(onClick = onDismissRequest) {
|
IconButton(onClick = onDismissRequest) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_close),
|
contentDescription = stringResource(MR.strings.action_close),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
ActionsPill {
|
ActionsPill {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = buildList {
|
actions = persistentListOf(
|
||||||
add(
|
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_share),
|
title = stringResource(MR.strings.action_share),
|
||||||
icon = Icons.Outlined.Share,
|
icon = Icons.Outlined.Share,
|
||||||
onClick = onShareClick,
|
onClick = onShareClick,
|
||||||
),
|
),
|
||||||
)
|
|
||||||
add(
|
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_save),
|
title = stringResource(MR.strings.action_save),
|
||||||
icon = Icons.Outlined.Save,
|
icon = Icons.Outlined.Save,
|
||||||
onClick = onSaveClick,
|
onClick = onSaveClick,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if (onEditClick != null) {
|
if (onEditClick != null) {
|
||||||
Box {
|
Box {
|
||||||
@ -121,7 +119,7 @@ fun MangaCoverDialog(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Edit,
|
imageVector = Icons.Outlined.Edit,
|
||||||
contentDescription = stringResource(R.string.action_edit_cover),
|
contentDescription = stringResource(MR.strings.action_edit_cover),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
@ -130,14 +128,14 @@ fun MangaCoverDialog(
|
|||||||
offset = DpOffset(8.dp, 0.dp),
|
offset = DpOffset(8.dp, 0.dp),
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_edit)) },
|
text = { Text(text = stringResource(MR.strings.action_edit)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onEditClick(EditCoverAction.EDIT)
|
onEditClick(EditCoverAction.EDIT)
|
||||||
expanded = false
|
expanded = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_delete)) },
|
text = { Text(text = stringResource(MR.strings.action_delete)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onEditClick(EditCoverAction.DELETE)
|
onEditClick(EditCoverAction.DELETE)
|
||||||
expanded = false
|
expanded = false
|
||||||
@ -175,9 +173,14 @@ fun MangaCoverDialog(
|
|||||||
// Copy bitmap in case it came from memory cache
|
// Copy bitmap in case it came from memory cache
|
||||||
// Because SSIV needs to thoroughly read the image
|
// Because SSIV needs to thoroughly read the image
|
||||||
val copy = (drawable as? BitmapDrawable)?.let {
|
val copy = (drawable as? BitmapDrawable)?.let {
|
||||||
|
val config = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
Bitmap.Config.HARDWARE
|
||||||
|
} else {
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
}
|
||||||
BitmapDrawable(
|
BitmapDrawable(
|
||||||
view.context.resources,
|
view.context.resources,
|
||||||
it.bitmap.copy(Bitmap.Config.HARDWARE, false),
|
it.bitmap.copy(config, false),
|
||||||
)
|
)
|
||||||
} ?: drawable
|
} ?: drawable
|
||||||
view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
|
view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
|
||||||
|
@ -1,23 +1,36 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeleteChaptersDialog(
|
fun DeleteChaptersDialog(
|
||||||
@ -28,7 +41,7 @@ fun DeleteChaptersDialog(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -38,14 +51,14 @@ fun DeleteChaptersDialog(
|
|||||||
onConfirm()
|
onConfirm()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.are_you_sure))
|
Text(text = stringResource(MR.strings.are_you_sure))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.confirm_delete_chapters))
|
Text(text = stringResource(MR.strings.confirm_delete_chapters))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -53,46 +66,84 @@ fun DeleteChaptersDialog(
|
|||||||
@Composable
|
@Composable
|
||||||
fun SetIntervalDialog(
|
fun SetIntervalDialog(
|
||||||
interval: Int,
|
interval: Int,
|
||||||
|
nextUpdate: Instant?,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onValueChanged: (Int) -> Unit,
|
onValueChanged: ((Int) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
var selectedInterval by rememberSaveable { mutableIntStateOf(if (interval < 0) -interval else 0) }
|
var selectedInterval by rememberSaveable { mutableIntStateOf(if (interval < 0) -interval else 0) }
|
||||||
|
|
||||||
|
val nextUpdateDays = remember(nextUpdate) {
|
||||||
|
return@remember if (nextUpdate != null) {
|
||||||
|
val now = Instant.now()
|
||||||
|
now.until(nextUpdate, ChronoUnit.DAYS).toInt().coerceAtLeast(0)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = { Text(text = stringResource(R.string.manga_modify_calculated_interval_title)) },
|
title = { Text(stringResource(MR.strings.pref_library_update_smart_update)) },
|
||||||
text = {
|
text = {
|
||||||
|
Column {
|
||||||
|
if (nextUpdateDays != null && nextUpdateDays >= 0 && interval >= 0) {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
MR.strings.manga_interval_expected_update,
|
||||||
|
pluralStringResource(
|
||||||
|
MR.plurals.day,
|
||||||
|
count = nextUpdateDays,
|
||||||
|
nextUpdateDays,
|
||||||
|
),
|
||||||
|
pluralStringResource(
|
||||||
|
MR.plurals.day,
|
||||||
|
count = interval.absoluteValue,
|
||||||
|
interval.absoluteValue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(MaterialTheme.padding.small))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) {
|
||||||
|
Text(stringResource(MR.strings.manga_interval_custom_amount))
|
||||||
|
|
||||||
BoxWithConstraints(
|
BoxWithConstraints(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
val size = DpSize(width = maxWidth / 2, height = 128.dp)
|
val size = DpSize(width = maxWidth / 2, height = 128.dp)
|
||||||
val items = (0..FetchInterval.MAX_INTERVAL).map {
|
val items = (0..FetchInterval.MAX_INTERVAL)
|
||||||
|
.map {
|
||||||
if (it == 0) {
|
if (it == 0) {
|
||||||
stringResource(R.string.label_default)
|
stringResource(MR.strings.label_default)
|
||||||
} else {
|
} else {
|
||||||
it.toString()
|
it.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.toImmutableList()
|
||||||
WheelTextPicker(
|
WheelTextPicker(
|
||||||
size = size,
|
|
||||||
items = items,
|
items = items,
|
||||||
|
size = size,
|
||||||
startIndex = selectedInterval,
|
startIndex = selectedInterval,
|
||||||
onSelectionChanged = { selectedInterval = it },
|
onSelectionChanged = { selectedInterval = it },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
onValueChanged(selectedInterval)
|
onValueChanged?.invoke(selectedInterval)
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -66,8 +67,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.Layout
|
import androidx.compose.ui.layout.Layout
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.unit.Constraints
|
||||||
@ -80,18 +79,21 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.TextButton
|
import tachiyomi.presentation.core.components.material.TextButton
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
import kotlin.math.absoluteValue
|
import java.time.Instant
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
|
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaInfoBox(
|
fun MangaInfoBox(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
isTabletUi: Boolean,
|
isTabletUi: Boolean,
|
||||||
appBarPadding: Dp,
|
appBarPadding: Dp,
|
||||||
title: String,
|
title: String,
|
||||||
@ -103,6 +105,7 @@ fun MangaInfoBox(
|
|||||||
status: Long,
|
status: Long,
|
||||||
onCoverClick: () -> Unit,
|
onCoverClick: () -> Unit,
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
// Backdrop
|
// Backdrop
|
||||||
@ -123,7 +126,7 @@ fun MangaInfoBox(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.blur(4.dp)
|
.blur(4.dp)
|
||||||
.alpha(.2f),
|
.alpha(0.2f),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manga & source info
|
// Manga & source info
|
||||||
@ -161,55 +164,69 @@ fun MangaInfoBox(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaActionRow(
|
fun MangaActionRow(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
favorite: Boolean,
|
favorite: Boolean,
|
||||||
trackingCount: Int,
|
trackingCount: Int,
|
||||||
fetchInterval: Int?,
|
nextUpdate: Instant?,
|
||||||
isUserIntervalMode: Boolean,
|
isUserIntervalMode: Boolean,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
onTrackingClicked: (() -> Unit)?,
|
onTrackingClicked: () -> Unit,
|
||||||
onEditIntervalClicked: (() -> Unit)?,
|
onEditIntervalClicked: (() -> Unit)?,
|
||||||
onEditCategory: (() -> Unit)?,
|
onEditCategory: (() -> Unit)?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
||||||
|
|
||||||
|
// TODO: show something better when using custom interval
|
||||||
|
val nextUpdateDays = remember(nextUpdate) {
|
||||||
|
return@remember if (nextUpdate != null) {
|
||||||
|
val now = Instant.now()
|
||||||
|
now.until(nextUpdate, ChronoUnit.DAYS).toInt().coerceAtLeast(0)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
|
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = if (favorite) {
|
title = if (favorite) {
|
||||||
stringResource(R.string.in_library)
|
stringResource(MR.strings.in_library)
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.add_to_library)
|
stringResource(MR.strings.add_to_library)
|
||||||
},
|
},
|
||||||
icon = if (favorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
|
icon = if (favorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
|
||||||
color = if (favorite) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
color = if (favorite) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||||
onClick = onAddToLibraryClicked,
|
onClick = onAddToLibraryClicked,
|
||||||
onLongClick = onEditCategory,
|
onLongClick = onEditCategory,
|
||||||
)
|
)
|
||||||
if (onEditIntervalClicked != null && fetchInterval != null) {
|
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = pluralStringResource(id = R.plurals.day, count = fetchInterval.absoluteValue, fetchInterval.absoluteValue),
|
title = when (nextUpdateDays) {
|
||||||
|
null -> stringResource(MR.strings.not_applicable)
|
||||||
|
0 -> stringResource(MR.strings.manga_interval_expected_update_soon)
|
||||||
|
else -> pluralStringResource(
|
||||||
|
MR.plurals.day,
|
||||||
|
count = nextUpdateDays,
|
||||||
|
nextUpdateDays,
|
||||||
|
)
|
||||||
|
},
|
||||||
icon = Icons.Default.HourglassEmpty,
|
icon = Icons.Default.HourglassEmpty,
|
||||||
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||||
onClick = onEditIntervalClicked,
|
onClick = { onEditIntervalClicked?.invoke() },
|
||||||
)
|
)
|
||||||
}
|
|
||||||
if (onTrackingClicked != null) {
|
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = if (trackingCount == 0) {
|
title = if (trackingCount == 0) {
|
||||||
stringResource(R.string.manga_tracking_tab)
|
stringResource(MR.strings.manga_tracking_tab)
|
||||||
} else {
|
} else {
|
||||||
pluralStringResource(id = R.plurals.num_trackers, count = trackingCount, trackingCount)
|
pluralStringResource(MR.plurals.num_trackers, count = trackingCount, trackingCount)
|
||||||
},
|
},
|
||||||
icon = if (trackingCount == 0) Icons.Outlined.Sync else Icons.Outlined.Done,
|
icon = if (trackingCount == 0) Icons.Outlined.Sync else Icons.Outlined.Done,
|
||||||
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
||||||
onClick = onTrackingClicked,
|
onClick = onTrackingClicked,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
if (onWebViewClicked != null) {
|
if (onWebViewClicked != null) {
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = stringResource(R.string.action_web_view),
|
title = stringResource(MR.strings.action_web_view),
|
||||||
icon = Icons.Outlined.Public,
|
icon = Icons.Outlined.Public,
|
||||||
color = defaultActionButtonColor,
|
color = defaultActionButtonColor,
|
||||||
onClick = onWebViewClicked,
|
onClick = onWebViewClicked,
|
||||||
@ -221,19 +238,19 @@ fun MangaActionRow(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExpandableMangaDescription(
|
fun ExpandableMangaDescription(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
defaultExpandState: Boolean,
|
defaultExpandState: Boolean,
|
||||||
description: String?,
|
description: String?,
|
||||||
tagsProvider: () -> List<String>?,
|
tagsProvider: () -> List<String>?,
|
||||||
onTagSearch: (String) -> Unit,
|
onTagSearch: (String) -> Unit,
|
||||||
onCopyTagToClipboard: (tag: String) -> Unit,
|
onCopyTagToClipboard: (tag: String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
val (expanded, onExpanded) = rememberSaveable {
|
val (expanded, onExpanded) = rememberSaveable {
|
||||||
mutableStateOf(defaultExpandState)
|
mutableStateOf(defaultExpandState)
|
||||||
}
|
}
|
||||||
val desc =
|
val desc =
|
||||||
description.takeIf { !it.isNullOrBlank() } ?: stringResource(R.string.description_placeholder)
|
description.takeIf { !it.isNullOrBlank() } ?: stringResource(MR.strings.description_placeholder)
|
||||||
val trimmedDescription = remember(desc) {
|
val trimmedDescription = remember(desc) {
|
||||||
desc
|
desc
|
||||||
.replace(whitespaceLineRegex, "\n")
|
.replace(whitespaceLineRegex, "\n")
|
||||||
@ -263,14 +280,14 @@ fun ExpandableMangaDescription(
|
|||||||
onDismissRequest = { showMenu = false },
|
onDismissRequest = { showMenu = false },
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_search)) },
|
text = { Text(text = stringResource(MR.strings.action_search)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onTagSearch(tagSelected)
|
onTagSearch(tagSelected)
|
||||||
showMenu = false
|
showMenu = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_copy_to_clipboard)) },
|
text = { Text(text = stringResource(MR.strings.action_copy_to_clipboard)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onCopyTagToClipboard(tagSelected)
|
onCopyTagToClipboard(tagSelected)
|
||||||
showMenu = false
|
showMenu = false
|
||||||
@ -280,7 +297,7 @@ fun ExpandableMangaDescription(
|
|||||||
if (expanded) {
|
if (expanded) {
|
||||||
FlowRow(
|
FlowRow(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
) {
|
) {
|
||||||
tags.forEach {
|
tags.forEach {
|
||||||
TagsChip(
|
TagsChip(
|
||||||
@ -296,7 +313,7 @@ fun ExpandableMangaDescription(
|
|||||||
} else {
|
} else {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
contentPadding = PaddingValues(horizontal = MaterialTheme.padding.medium),
|
contentPadding = PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
) {
|
) {
|
||||||
items(items = tags) {
|
items(items = tags) {
|
||||||
TagsChip(
|
TagsChip(
|
||||||
@ -337,7 +354,7 @@ private fun MangaAndSourceTitlesLarge(
|
|||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier.fillMaxWidth(0.65f),
|
modifier = Modifier.fillMaxWidth(0.65f),
|
||||||
data = coverDataProvider(),
|
data = coverDataProvider(),
|
||||||
contentDescription = stringResource(R.string.manga_cover),
|
contentDescription = stringResource(MR.strings.manga_cover),
|
||||||
onClick = onCoverClick,
|
onClick = onCoverClick,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
@ -379,7 +396,7 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
.sizeIn(maxWidth = 100.dp)
|
.sizeIn(maxWidth = 100.dp)
|
||||||
.align(Alignment.Top),
|
.align(Alignment.Top),
|
||||||
data = coverDataProvider(),
|
data = coverDataProvider(),
|
||||||
contentDescription = stringResource(R.string.manga_cover),
|
contentDescription = stringResource(MR.strings.manga_cover),
|
||||||
onClick = onCoverClick,
|
onClick = onCoverClick,
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
@ -399,19 +416,19 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MangaContentInfo(
|
private fun ColumnScope.MangaContentInfo(
|
||||||
title: String,
|
title: String,
|
||||||
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
author: String?,
|
author: String?,
|
||||||
artist: String?,
|
artist: String?,
|
||||||
status: Long,
|
status: Long,
|
||||||
sourceName: String,
|
sourceName: String,
|
||||||
isStubSource: Boolean,
|
isStubSource: Boolean,
|
||||||
|
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Text(
|
Text(
|
||||||
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
text = title.ifBlank { stringResource(MR.strings.unknown_title) },
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
modifier = Modifier.clickableNoIndication(
|
modifier = Modifier.clickableNoIndication(
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
@ -431,7 +448,7 @@ private fun MangaContentInfo(
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@ -441,7 +458,7 @@ private fun MangaContentInfo(
|
|||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = author?.takeIf { it.isNotBlank() }
|
text = author?.takeIf { it.isNotBlank() }
|
||||||
?: stringResource(R.string.unknown_author),
|
?: stringResource(MR.strings.unknown_author),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickableNoIndication(
|
.clickableNoIndication(
|
||||||
@ -462,7 +479,7 @@ private fun MangaContentInfo(
|
|||||||
if (!artist.isNullOrBlank() && author != artist) {
|
if (!artist.isNullOrBlank() && author != artist) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@ -507,13 +524,13 @@ private fun MangaContentInfo(
|
|||||||
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
|
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
|
||||||
Text(
|
Text(
|
||||||
text = when (status) {
|
text = when (status) {
|
||||||
SManga.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
SManga.ONGOING.toLong() -> stringResource(MR.strings.ongoing)
|
||||||
SManga.COMPLETED.toLong() -> stringResource(R.string.completed)
|
SManga.COMPLETED.toLong() -> stringResource(MR.strings.completed)
|
||||||
SManga.LICENSED.toLong() -> stringResource(R.string.licensed)
|
SManga.LICENSED.toLong() -> stringResource(MR.strings.licensed)
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(R.string.publishing_finished)
|
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(MR.strings.publishing_finished)
|
||||||
SManga.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
SManga.CANCELLED.toLong() -> stringResource(MR.strings.cancelled)
|
||||||
SManga.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
SManga.ON_HIATUS.toLong() -> stringResource(MR.strings.on_hiatus)
|
||||||
else -> stringResource(R.string.unknown)
|
else -> stringResource(MR.strings.unknown)
|
||||||
},
|
},
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
@ -551,7 +568,10 @@ private fun MangaSummary(
|
|||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val animProgress by animateFloatAsState(if (expanded) 1f else 0f)
|
val animProgress by animateFloatAsState(
|
||||||
|
targetValue = if (expanded) 1f else 0f,
|
||||||
|
label = "summary",
|
||||||
|
)
|
||||||
Layout(
|
Layout(
|
||||||
modifier = modifier.clipToBounds(),
|
modifier = modifier.clipToBounds(),
|
||||||
contents = listOf(
|
contents = listOf(
|
||||||
@ -587,7 +607,9 @@ private fun MangaSummary(
|
|||||||
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down)
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down)
|
||||||
Icon(
|
Icon(
|
||||||
painter = rememberAnimatedVectorPainter(image, !expanded),
|
painter = rememberAnimatedVectorPainter(image, !expanded),
|
||||||
contentDescription = stringResource(if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand),
|
contentDescription = stringResource(
|
||||||
|
if (expanded) MR.strings.manga_info_collapse else MR.strings.manga_info_expand,
|
||||||
|
),
|
||||||
tint = MaterialTheme.colorScheme.onBackground,
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())),
|
modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())),
|
||||||
)
|
)
|
||||||
|
@ -20,8 +20,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@ -29,15 +27,15 @@ import eu.kanade.presentation.components.AppBarActions
|
|||||||
import eu.kanade.presentation.components.DownloadDropdownMenu
|
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||||
import eu.kanade.presentation.components.UpIcon
|
import eu.kanade.presentation.components.UpIcon
|
||||||
import eu.kanade.presentation.manga.DownloadAction
|
import eu.kanade.presentation.manga.DownloadAction
|
||||||
import eu.kanade.tachiyomi.R
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.theme.active
|
import tachiyomi.presentation.core.theme.active
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaToolbar(
|
fun MangaToolbar(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
title: String,
|
title: String,
|
||||||
titleAlphaProvider: () -> Float,
|
titleAlphaProvider: () -> Float,
|
||||||
backgroundAlphaProvider: () -> Float = titleAlphaProvider,
|
|
||||||
hasFilters: Boolean,
|
hasFilters: Boolean,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onClickFilter: () -> Unit,
|
onClickFilter: () -> Unit,
|
||||||
@ -46,10 +44,14 @@ fun MangaToolbar(
|
|||||||
onClickEditCategory: (() -> Unit)?,
|
onClickEditCategory: (() -> Unit)?,
|
||||||
onClickRefresh: () -> Unit,
|
onClickRefresh: () -> Unit,
|
||||||
onClickMigrate: (() -> Unit)?,
|
onClickMigrate: (() -> Unit)?,
|
||||||
|
|
||||||
// For action mode
|
// For action mode
|
||||||
actionModeCounter: Int,
|
actionModeCounter: Int,
|
||||||
onSelectAll: () -> Unit,
|
onSelectAll: () -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
|
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
backgroundAlphaProvider: () -> Float = titleAlphaProvider,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -61,25 +63,25 @@ fun MangaToolbar(
|
|||||||
text = if (isActionMode) actionModeCounter.toString() else title,
|
text = if (isActionMode) actionModeCounter.toString() else title,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.alpha(if (isActionMode) 1f else titleAlphaProvider()),
|
color = LocalContentColor.current.copy(alpha = if (isActionMode) 1f else titleAlphaProvider()),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBackClicked) {
|
IconButton(onClick = onBackClicked) {
|
||||||
UpIcon(Icons.Outlined.Close.takeIf { isActionMode })
|
UpIcon(navigationIcon = Icons.Outlined.Close.takeIf { isActionMode })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
if (isActionMode) {
|
if (isActionMode) {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
persistentListOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_select_all),
|
title = stringResource(MR.strings.action_select_all),
|
||||||
icon = Icons.Outlined.SelectAll,
|
icon = Icons.Outlined.SelectAll,
|
||||||
onClick = onSelectAll,
|
onClick = onSelectAll,
|
||||||
),
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_select_inverse),
|
title = stringResource(MR.strings.action_select_inverse),
|
||||||
icon = Icons.Outlined.FlipToBack,
|
icon = Icons.Outlined.FlipToBack,
|
||||||
onClick = onInvertSelection,
|
onClick = onInvertSelection,
|
||||||
),
|
),
|
||||||
@ -98,11 +100,12 @@ fun MangaToolbar(
|
|||||||
|
|
||||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = buildList {
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
|
.apply {
|
||||||
if (onClickDownload != null) {
|
if (onClickDownload != null) {
|
||||||
add(
|
add(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.manga_download),
|
title = stringResource(MR.strings.manga_download),
|
||||||
icon = Icons.Outlined.Download,
|
icon = Icons.Outlined.Download,
|
||||||
onClick = { downloadExpanded = !downloadExpanded },
|
onClick = { downloadExpanded = !downloadExpanded },
|
||||||
),
|
),
|
||||||
@ -110,7 +113,7 @@ fun MangaToolbar(
|
|||||||
}
|
}
|
||||||
add(
|
add(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_filter),
|
title = stringResource(MR.strings.action_filter),
|
||||||
icon = Icons.Outlined.FilterList,
|
icon = Icons.Outlined.FilterList,
|
||||||
iconTint = filterTint,
|
iconTint = filterTint,
|
||||||
onClick = onClickFilter,
|
onClick = onClickFilter,
|
||||||
@ -118,14 +121,14 @@ fun MangaToolbar(
|
|||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_webview_refresh),
|
title = stringResource(MR.strings.action_webview_refresh),
|
||||||
onClick = onClickRefresh,
|
onClick = onClickRefresh,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (onClickEditCategory != null) {
|
if (onClickEditCategory != null) {
|
||||||
add(
|
add(
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_edit_categories),
|
title = stringResource(MR.strings.action_edit_categories),
|
||||||
onClick = onClickEditCategory,
|
onClick = onClickEditCategory,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -133,7 +136,7 @@ fun MangaToolbar(
|
|||||||
if (onClickMigrate != null) {
|
if (onClickMigrate != null) {
|
||||||
add(
|
add(
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_migrate),
|
title = stringResource(MR.strings.action_migrate),
|
||||||
onClick = onClickMigrate,
|
onClick = onClickMigrate,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -141,12 +144,13 @@ fun MangaToolbar(
|
|||||||
if (onClickShare != null) {
|
if (onClickShare != null) {
|
||||||
add(
|
add(
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_share),
|
title = stringResource(MR.strings.action_share),
|
||||||
onClick = onClickShare,
|
onClick = onClickShare,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
.build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MissingChapterCountListItem(
|
||||||
|
count: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
)
|
||||||
|
.secondaryItemAlpha(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = pluralStringResource(MR.plurals.missing_chapters, count = count, count),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
)
|
||||||
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewLightDark
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TachiyomiPreviewTheme {
|
||||||
|
Surface {
|
||||||
|
MissingChapterCountListItem(count = 42)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
||||||
|
import androidx.compose.material.icons.rounded.DisabledByDefault
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.TextButton
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
|
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScanlatorFilterDialog(
|
||||||
|
availableScanlators: Set<String>,
|
||||||
|
excludedScanlators: Set<String>,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onConfirm: (Set<String>) -> Unit,
|
||||||
|
) {
|
||||||
|
val sortedAvailableScanlators = remember(availableScanlators) {
|
||||||
|
availableScanlators.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it })
|
||||||
|
}
|
||||||
|
val mutableExcludedScanlators = remember(excludedScanlators) { excludedScanlators.toMutableStateList() }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(MR.strings.exclude_scanlators)) },
|
||||||
|
text = textFunc@{
|
||||||
|
if (sortedAvailableScanlators.isEmpty()) {
|
||||||
|
Text(text = stringResource(MR.strings.no_scanlators_found))
|
||||||
|
return@textFunc
|
||||||
|
}
|
||||||
|
Box {
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
LazyColumn(state = state) {
|
||||||
|
sortedAvailableScanlators.forEach { scanlator ->
|
||||||
|
item {
|
||||||
|
val isExcluded = mutableExcludedScanlators.contains(scanlator)
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
if (isExcluded) {
|
||||||
|
mutableExcludedScanlators.remove(scanlator)
|
||||||
|
} else {
|
||||||
|
mutableExcludedScanlators.add(scanlator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = MaterialTheme.padding.small),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isExcluded) {
|
||||||
|
Icons.Rounded.DisabledByDefault
|
||||||
|
} else {
|
||||||
|
Icons.Rounded.CheckBoxOutlineBlank
|
||||||
|
},
|
||||||
|
tint = if (isExcluded) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
LocalContentColor.current
|
||||||
|
},
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = scanlator,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(start = 24.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
|
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
properties = DialogProperties(
|
||||||
|
usePlatformDefaultWidth = true,
|
||||||
|
),
|
||||||
|
confirmButton = {
|
||||||
|
if (sortedAvailableScanlators.isEmpty()) {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FlowRow {
|
||||||
|
TextButton(onClick = mutableExcludedScanlators::clear) {
|
||||||
|
Text(text = stringResource(MR.strings.action_reset))
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onConfirm(mutableExcludedScanlators.toSet())
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.more
|
package eu.kanade.presentation.more
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
@ -9,11 +8,11 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.material.icons.Icons
|
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.CloudOff
|
import androidx.compose.material.icons.outlined.CloudOff
|
||||||
import androidx.compose.material.icons.outlined.GetApp
|
import androidx.compose.material.icons.outlined.GetApp
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material.icons.outlined.Label
|
|
||||||
import androidx.compose.material.icons.outlined.QueryStats
|
import androidx.compose.material.icons.outlined.QueryStats
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.Storage
|
import androidx.compose.material.icons.outlined.Storage
|
||||||
@ -22,17 +21,17 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import eu.kanade.presentation.components.WarningBanner
|
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
||||||
import tachiyomi.core.Constants
|
import tachiyomi.core.Constants
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MoreScreen(
|
fun MoreScreen(
|
||||||
@ -59,12 +58,7 @@ fun MoreScreen(
|
|||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
if (isFDroid) {
|
if (isFDroid) {
|
||||||
WarningBanner(
|
// Don't really care about slow updaters now
|
||||||
textRes = R.string.fdroid_warning,
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
uriHandler.openUri("https://tachiyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -77,8 +71,8 @@ fun MoreScreen(
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
title = stringResource(R.string.label_downloaded_only),
|
title = stringResource(MR.strings.label_downloaded_only),
|
||||||
subtitle = stringResource(R.string.downloaded_only_summary),
|
subtitle = stringResource(MR.strings.downloaded_only_summary),
|
||||||
icon = Icons.Outlined.CloudOff,
|
icon = Icons.Outlined.CloudOff,
|
||||||
checked = downloadedOnly,
|
checked = downloadedOnly,
|
||||||
onCheckedChanged = onDownloadedOnlyChange,
|
onCheckedChanged = onDownloadedOnlyChange,
|
||||||
@ -86,8 +80,8 @@ fun MoreScreen(
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
title = stringResource(R.string.pref_incognito_mode),
|
title = stringResource(MR.strings.pref_incognito_mode),
|
||||||
subtitle = stringResource(R.string.pref_incognito_mode_summary),
|
subtitle = stringResource(MR.strings.pref_incognito_mode_summary),
|
||||||
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
|
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
|
||||||
checked = incognitoMode,
|
checked = incognitoMode,
|
||||||
onCheckedChanged = onIncognitoModeChange,
|
onCheckedChanged = onIncognitoModeChange,
|
||||||
@ -99,17 +93,17 @@ fun MoreScreen(
|
|||||||
item {
|
item {
|
||||||
val downloadQueueState = downloadQueueStateProvider()
|
val downloadQueueState = downloadQueueStateProvider()
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_download_queue),
|
title = stringResource(MR.strings.label_download_queue),
|
||||||
subtitle = when (downloadQueueState) {
|
subtitle = when (downloadQueueState) {
|
||||||
DownloadQueueState.Stopped -> null
|
DownloadQueueState.Stopped -> null
|
||||||
is DownloadQueueState.Paused -> {
|
is DownloadQueueState.Paused -> {
|
||||||
val pending = downloadQueueState.pending
|
val pending = downloadQueueState.pending
|
||||||
if (pending == 0) {
|
if (pending == 0) {
|
||||||
stringResource(R.string.paused)
|
stringResource(MR.strings.paused)
|
||||||
} else {
|
} else {
|
||||||
"${stringResource(R.string.paused)} • ${
|
"${stringResource(MR.strings.paused)} • ${
|
||||||
pluralStringResource(
|
pluralStringResource(
|
||||||
id = R.plurals.download_queue_summary,
|
MR.plurals.download_queue_summary,
|
||||||
count = pending,
|
count = pending,
|
||||||
pending,
|
pending,
|
||||||
)
|
)
|
||||||
@ -118,7 +112,7 @@ fun MoreScreen(
|
|||||||
}
|
}
|
||||||
is DownloadQueueState.Downloading -> {
|
is DownloadQueueState.Downloading -> {
|
||||||
val pending = downloadQueueState.pending
|
val pending = downloadQueueState.pending
|
||||||
pluralStringResource(id = R.plurals.download_queue_summary, count = pending, pending)
|
pluralStringResource(MR.plurals.download_queue_summary, count = pending, pending)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon = Icons.Outlined.GetApp,
|
icon = Icons.Outlined.GetApp,
|
||||||
@ -127,21 +121,21 @@ fun MoreScreen(
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.categories),
|
title = stringResource(MR.strings.categories),
|
||||||
icon = Icons.Outlined.Label,
|
icon = Icons.AutoMirrored.Outlined.Label,
|
||||||
onPreferenceClick = onClickCategories,
|
onPreferenceClick = onClickCategories,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_stats),
|
title = stringResource(MR.strings.label_stats),
|
||||||
icon = Icons.Outlined.QueryStats,
|
icon = Icons.Outlined.QueryStats,
|
||||||
onPreferenceClick = onClickStats,
|
onPreferenceClick = onClickStats,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_data_storage),
|
title = stringResource(MR.strings.label_data_storage),
|
||||||
icon = Icons.Outlined.Storage,
|
icon = Icons.Outlined.Storage,
|
||||||
onPreferenceClick = onClickDataAndStorage,
|
onPreferenceClick = onClickDataAndStorage,
|
||||||
)
|
)
|
||||||
@ -151,22 +145,22 @@ fun MoreScreen(
|
|||||||
|
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_settings),
|
title = stringResource(MR.strings.label_settings),
|
||||||
icon = Icons.Outlined.Settings,
|
icon = Icons.Outlined.Settings,
|
||||||
onPreferenceClick = onClickSettings,
|
onPreferenceClick = onClickSettings,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.pref_category_about),
|
title = stringResource(MR.strings.pref_category_about),
|
||||||
icon = Icons.Outlined.Info,
|
icon = Icons.Outlined.Info,
|
||||||
onPreferenceClick = onClickAbout,
|
onPreferenceClick = onClickAbout,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_help),
|
title = stringResource(MR.strings.label_help),
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user