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,4 +1,5 @@
|
||||
[*.{kt,kts}]
|
||||
max_line_length = 120
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
|
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@ -3,10 +3,10 @@
|
||||
I acknowledge that:
|
||||
|
||||
- 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
|
||||
- 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 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
|
||||
contact_links:
|
||||
- name: ⚠️ Extension/source issue
|
||||
url: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
||||
about: Issues and requests for extensions and sources should be opened in the tachiyomi-extensions repository instead
|
||||
url: https://github.com/tachiyomiorg/extensions/issues/new/choose
|
||||
about: Issues and requests for official extensions and sources should be opened in the extensions repository instead
|
||||
- name: 📦 Tachiyomi extensions
|
||||
url: https://tachiyomi.org/extensions/
|
||||
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
|
||||
description: You can find your Tachiyomi version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "0.14.7"
|
||||
Example: "0.15.3"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@ -94,11 +94,11 @@ body:
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
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
|
||||
- label: I have gone through the [FAQ](https://tachiyomi.org/docs/faq/general) and [troubleshooting guide](https://tachiyomi.org/docs/guides/troubleshooting/).
|
||||
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
|
||||
- label: I have updated all installed extensions.
|
||||
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
|
||||
- label: I have written a short but informative title.
|
||||
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
|
||||
- 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
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
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:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'i18n/src/main/res/**/strings.xml'
|
||||
- 'i18n/src/commonMain/resources/**/strings.xml'
|
||||
- 'i18n/src/commonMain/resources/**/plurals.xml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
@ -28,7 +29,7 @@ jobs:
|
||||
uses: actions/dependency-review-action@v3
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
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
|
||||
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
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: adopt
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '2'
|
||||
|
@ -59,8 +59,7 @@ representative at an online or offline event.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community moderators responsible for enforcement at
|
||||
the [Tachiyomi Discord server](https://discord.gg/tachiyomi).
|
||||
reported to the community moderators via issues.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
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
|
||||
|
||||
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
||||
No support is currently provided.
|
||||
|
||||
# Translations
|
||||
|
||||
|
13
README.md
13
README.md
@ -1,7 +1,6 @@
|
||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
||||
|-------|----------|---------|------------|---------|
|
||||
| [![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) |
|
||||
|
||||
| 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) |
|
||||
|
||||
# ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
|
||||
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>
|
||||
|
||||
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).**
|
||||
2. If you are unsure, ask here: [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi)
|
||||
**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).**
|
||||
|
||||
</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"
|
||||
* 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><summary>Contributing</summary>
|
||||
@ -71,7 +69,6 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
||||
## FAQ
|
||||
|
||||
[See our website.](https://tachiyomi.org/)
|
||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
||||
|
||||
## License
|
||||
|
||||
|
@ -8,10 +8,6 @@ plugins {
|
||||
id("com.github.zellius.shortcut-helper")
|
||||
}
|
||||
|
||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
||||
}
|
||||
|
||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||
|
||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
@ -22,8 +18,8 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "eu.kanade.tachiyomi"
|
||||
|
||||
versionCode = 108
|
||||
versionName = "0.14.7"
|
||||
versionCode = 119
|
||||
versionName = "0.15.3"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
@ -31,9 +27,6 @@ android {
|
||||
buildConfigField("boolean", "INCLUDE_UPDATER", "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 {
|
||||
abiFilters += SUPPORTED_ABIS
|
||||
}
|
||||
@ -97,7 +90,7 @@ android {
|
||||
}
|
||||
create("dev") {
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
@ -123,6 +116,7 @@ android {
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
compose = true
|
||||
buildConfig = true
|
||||
|
||||
// Disable some unused things
|
||||
aidl = false
|
||||
@ -164,9 +158,8 @@ dependencies {
|
||||
implementation(compose.ui.tooling.preview)
|
||||
implementation(compose.ui.util)
|
||||
implementation(compose.accompanist.webview)
|
||||
implementation(compose.accompanist.permissions)
|
||||
implementation(compose.accompanist.themeadapter)
|
||||
implementation(compose.accompanist.systemuicontroller)
|
||||
lintChecks(compose.lintchecks)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
@ -174,6 +167,7 @@ dependencies {
|
||||
implementation(libs.bundles.sqlite)
|
||||
|
||||
implementation(kotlinx.reflect)
|
||||
implementation(kotlinx.immutables)
|
||||
|
||||
implementation(platform(kotlinx.coroutines.bom))
|
||||
implementation(kotlinx.bundles.coroutines)
|
||||
@ -196,7 +190,6 @@ dependencies {
|
||||
|
||||
// RxJava
|
||||
implementation(libs.rxjava)
|
||||
implementation(libs.flowreactivenetwork)
|
||||
|
||||
// Networking
|
||||
implementation(libs.bundles.okhttp)
|
||||
@ -245,10 +238,6 @@ dependencies {
|
||||
// Logging
|
||||
implementation(libs.logcat)
|
||||
|
||||
// Crash reports/analytics
|
||||
implementation(libs.acra.http)
|
||||
"standardImplementation"(libs.firebase.analytics)
|
||||
|
||||
// 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 ----------
|
||||
-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
|
||||
-keepclassmembers class kotlinx.serialization.json.** {
|
||||
@ -71,7 +71,3 @@
|
||||
|
||||
# XmlUtil
|
||||
-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" />
|
||||
|
||||
<!-- 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 -->
|
||||
<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.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||
<!-- 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.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="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
@ -39,6 +43,7 @@
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:preserveLegacyExternalStorage="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
@ -46,13 +51,53 @@
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
||||
android:exported="true">
|
||||
android:theme="@style/Theme.Tachiyomi.SplashScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</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 -->
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
@ -60,16 +105,16 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:process=":error_handler"
|
||||
android:name=".crash.CrashActivity"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:process=":error_handler" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.deeplink.DeepLinkActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:exported="true"
|
||||
android:label="@string/action_search"
|
||||
android:exported="true">
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||
@ -93,20 +138,21 @@
|
||||
|
||||
<activity
|
||||
android:name=".ui.reader.ReaderActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false">
|
||||
android:exported="false"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||
<meta-data
|
||||
android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||
android:resource="@xml/s_pen_actions" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.security.UnlockActivity"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Tachiyomi" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.webview.WebViewActivity"
|
||||
@ -115,25 +161,25 @@
|
||||
|
||||
<activity
|
||||
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
|
||||
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>
|
||||
<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="anilist-auth" />
|
||||
<data android:host="bangumi-auth" />
|
||||
<data android:host="myanimelist-auth" />
|
||||
<data android:host="shikimori-auth" />
|
||||
|
||||
<data android:scheme="tachiyomi"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
@ -141,13 +187,10 @@
|
||||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.download.DownloadService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".extension.util.ExtensionInstallService"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="shortService" />
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
@ -158,6 +201,11 @@
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
tools:node="merge" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
@ -171,9 +219,9 @@
|
||||
<provider
|
||||
android:name="rikka.shizuku.ShizukuProvider"
|
||||
android:authorities="${applicationId}.shizuku"
|
||||
android:multiprocess="false"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:multiprocess="false"
|
||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
|
||||
<meta-data
|
||||
@ -183,11 +231,6 @@
|
||||
android:name="android.webkit.WebView.MetricsOptOut"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Disable advertising ID collection for Firebase -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -1,11 +1,18 @@
|
||||
package eu.kanade.domain
|
||||
|
||||
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
|
||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
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.GetExtensionRepos
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||
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.UpdateManga
|
||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||
@ -112,6 +119,8 @@ class DomainModule : InjektModule {
|
||||
addFactory { NetworkToLocalManga(get()) }
|
||||
addFactory { UpdateManga(get(), get()) }
|
||||
addFactory { SetMangaCategories(get()) }
|
||||
addFactory { GetExcludedScanlators(get()) }
|
||||
addFactory { SetExcludedScanlators(get()) }
|
||||
|
||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||
addFactory { GetApplicationRelease(get(), get()) }
|
||||
@ -133,7 +142,8 @@ class DomainModule : InjektModule {
|
||||
addFactory { UpdateChapter(get()) }
|
||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||
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()) }
|
||||
addFactory { GetHistory(get()) }
|
||||
@ -161,5 +171,10 @@ class DomainModule : InjektModule {
|
||||
addFactory { ToggleLanguage(get()) }
|
||||
addFactory { ToggleSource(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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.i18n.MR
|
||||
|
||||
class BasePreferences(
|
||||
val context: Context,
|
||||
@ -22,12 +20,12 @@ class BasePreferences(
|
||||
|
||||
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) {
|
||||
LEGACY(R.string.ext_installer_legacy),
|
||||
PACKAGEINSTALLER(R.string.ext_installer_packageinstaller),
|
||||
SHIZUKU(R.string.ext_installer_shizuku),
|
||||
PRIVATE(R.string.ext_installer_private),
|
||||
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
|
||||
LEGACY(MR.strings.ext_installer_legacy, true),
|
||||
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller, true),
|
||||
SHIZUKU(MR.strings.ext_installer_shizuku, false),
|
||||
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.toSChapter
|
||||
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
@ -22,7 +23,6 @@ import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.lang.Long.max
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
import java.util.TreeSet
|
||||
|
||||
class SyncChaptersWithSource(
|
||||
@ -33,6 +33,7 @@ class SyncChaptersWithSource(
|
||||
private val updateManga: UpdateManga,
|
||||
private val updateChapter: UpdateChapter,
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
private val getExcludedScanlators: GetExcludedScanlators,
|
||||
) {
|
||||
|
||||
/**
|
||||
@ -55,6 +56,7 @@ class SyncChaptersWithSource(
|
||||
}
|
||||
|
||||
val now = ZonedDateTime.now()
|
||||
val nowMillis = now.toInstant().toEpochMilli()
|
||||
|
||||
val sourceChapters = rawSourceChapters
|
||||
.distinctBy { it.url }
|
||||
@ -65,36 +67,27 @@ class SyncChaptersWithSource(
|
||||
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
||||
}
|
||||
|
||||
// Chapters from db.
|
||||
val dbChapters = getChaptersByMangaId.await(manga.id)
|
||||
|
||||
// Chapters from the source not in db.
|
||||
val toAdd = mutableListOf<Chapter>()
|
||||
|
||||
// Chapters whose metadata have changed.
|
||||
val toChange = mutableListOf<Chapter>()
|
||||
|
||||
// Chapters from the db not in source.
|
||||
val toDelete = dbChapters.filterNot { dbChapter ->
|
||||
val newChapters = mutableListOf<Chapter>()
|
||||
val updatedChapters = mutableListOf<Chapter>()
|
||||
val removedChapters = dbChapters.filterNot { dbChapter ->
|
||||
sourceChapters.any { sourceChapter ->
|
||||
dbChapter.url == sourceChapter.url
|
||||
}
|
||||
}
|
||||
|
||||
val rightNow = Date().time
|
||||
|
||||
// Used to not set upload date of older chapters
|
||||
// to a higher value than newer chapters
|
||||
var maxSeenUploadDate = 0L
|
||||
|
||||
val sManga = manga.toSManga()
|
||||
for (sourceChapter in sourceChapters) {
|
||||
var chapter = sourceChapter
|
||||
|
||||
// Update metadata from source if necessary.
|
||||
if (source is HttpSource) {
|
||||
val sChapter = chapter.toSChapter()
|
||||
source.prepareNewChapter(sChapter, sManga)
|
||||
source.prepareNewChapter(sChapter, manga.toSManga())
|
||||
chapter = chapter.copyFromSChapter(sChapter)
|
||||
}
|
||||
|
||||
@ -106,17 +99,19 @@ class SyncChaptersWithSource(
|
||||
|
||||
if (dbChapter == null) {
|
||||
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)
|
||||
} else {
|
||||
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
||||
chapter
|
||||
}
|
||||
toAdd.add(toAddChapter)
|
||||
newChapters.add(toAddChapter)
|
||||
} else {
|
||||
if (shouldUpdateDbChapter.await(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) {
|
||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||
@ -130,13 +125,13 @@ class SyncChaptersWithSource(
|
||||
if (chapter.dateUpload != 0L) {
|
||||
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.
|
||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||
// Return if there's nothing to add, delete, or update to avoid unnecessary db transactions.
|
||||
if (newChapters.isEmpty() && removedChapters.isEmpty() && updatedChapters.isEmpty()) {
|
||||
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||
updateManga.awaitUpdateFetchInterval(
|
||||
manga,
|
||||
@ -153,20 +148,20 @@ class SyncChaptersWithSource(
|
||||
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||
|
||||
toDelete.forEach { chapter ->
|
||||
removedChapters.forEach { chapter ->
|
||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||
if (chapter.bookmark) deletedBookmarkedChapterNumbers.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 }
|
||||
|
||||
// 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.
|
||||
var itemCount = toAdd.size
|
||||
var updatedToAdd = toAdd.map { toAddItem ->
|
||||
var chapter = toAddItem.copy(dateFetch = rightNow + itemCount--)
|
||||
var itemCount = newChapters.size
|
||||
var updatedToAdd = newChapters.map { toAddItem ->
|
||||
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||
|
||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||
|
||||
@ -185,8 +180,8 @@ class SyncChaptersWithSource(
|
||||
chapter
|
||||
}
|
||||
|
||||
if (toDelete.isNotEmpty()) {
|
||||
val toDeleteIds = toDelete.map { it.id }
|
||||
if (removedChapters.isNotEmpty()) {
|
||||
val toDeleteIds = removedChapters.map { it.id }
|
||||
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
||||
}
|
||||
|
||||
@ -194,8 +189,8 @@ class SyncChaptersWithSource(
|
||||
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
||||
}
|
||||
|
||||
if (toChange.isNotEmpty()) {
|
||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
||||
if (updatedChapters.isNotEmpty()) {
|
||||
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() }
|
||||
updateChapter.awaitAll(chapterUpdates)
|
||||
}
|
||||
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||
@ -206,6 +201,10 @@ class SyncChaptersWithSource(
|
||||
|
||||
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,
|
||||
dateUpload = sChapter.date_upload,
|
||||
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(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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
@ -14,7 +14,7 @@ class SetMangaViewerFlags(
|
||||
mangaRepository.update(
|
||||
MangaUpdate(
|
||||
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(
|
||||
MangaUpdate(
|
||||
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 uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
|
||||
class UpdateManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
@ -46,14 +46,14 @@ class UpdateManga(
|
||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||
localManga.isLocal() -> Date().time
|
||||
localManga.isLocal() -> Instant.now().toEpochMilli()
|
||||
localManga.hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
Date().time
|
||||
Instant.now().toEpochMilli()
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,22 +81,22 @@ class UpdateManga(
|
||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
||||
): Boolean {
|
||||
return fetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
|
||||
?.let { mangaRepository.update(it) }
|
||||
?: false
|
||||
return mangaRepository.update(
|
||||
fetchInterval.toMangaUpdate(manga, dateTime, window),
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
val dateAdded = when (favorite) {
|
||||
true -> Date().time
|
||||
true -> Instant.now().toEpochMilli()
|
||||
false -> 0
|
||||
}
|
||||
return mangaRepository.update(
|
||||
|
@ -3,8 +3,8 @@ package eu.kanade.domain.manga.model
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
||||
import tachiyomi.core.preference.TriState
|
||||
@ -14,11 +14,11 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
// TODO: move these into the domain model
|
||||
val Manga.readingModeType: Long
|
||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
||||
val Manga.readingMode: Long
|
||||
get() = viewerFlags and ReadingMode.MASK.toLong()
|
||||
|
||||
val Manga.orientationType: Long
|
||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
||||
val Manga.readerOrientation: Long
|
||||
get() = viewerFlags and ReaderOrientation.MASK.toLong()
|
||||
|
||||
val Manga.downloadedFilter: TriState
|
||||
get() {
|
||||
|
@ -11,7 +11,12 @@ class SourcePreferences(
|
||||
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())
|
||||
|
||||
@ -28,11 +33,19 @@ class SourcePreferences(
|
||||
|
||||
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 trustedSignatures() = preferenceStore.getStringSet(Preference.appStateKey("trusted_signatures"), emptySet())
|
||||
|
||||
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||
fun trustedExtensions() = preferenceStore.getStringSet(
|
||||
Preference.appStateKey("trusted_extensions"),
|
||||
emptySet(),
|
||||
)
|
||||
}
|
||||
|
@ -61,7 +61,10 @@ class AddTracks(
|
||||
?.readAt
|
||||
|
||||
firstReadChapterDate?.let {
|
||||
val startDate = firstReadChapterDate.time.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
|
||||
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
||||
ZoneOffset.systemDefault(),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
track = track.copy(
|
||||
startDate = startDate,
|
||||
)
|
||||
|
@ -25,7 +25,7 @@ class RefreshTracks(
|
||||
suspend fun await(mangaId: Long): List<Pair<Tracker?, Throwable>> {
|
||||
return supervisorScope {
|
||||
return@supervisorScope getTracks.await(mangaId)
|
||||
.map { it to trackerManager.get(it.syncId) }
|
||||
.map { it to trackerManager.get(it.trackerId) }
|
||||
.filter { (_, service) -> service?.isLoggedIn == true }
|
||||
.map { (track, service) ->
|
||||
async {
|
||||
|
@ -27,7 +27,7 @@ class TrackChapter(
|
||||
if (tracks.isEmpty()) return@withNonCancellableContext
|
||||
|
||||
tracks.mapNotNull { track ->
|
||||
val service = trackerManager.get(track.syncId)
|
||||
val service = trackerManager.get(track.trackerId)
|
||||
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
|
||||
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.manga_id = mangaId
|
||||
it.media_id = remoteId
|
||||
it.remote_id = remoteId
|
||||
it.library_id = libraryId
|
||||
it.title = title
|
||||
it.last_chapter_read = lastChapterRead.toFloat()
|
||||
@ -33,14 +33,16 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
||||
return Track(
|
||||
id = trackId,
|
||||
mangaId = manga_id,
|
||||
syncId = sync_id.toLong(),
|
||||
remoteId = media_id,
|
||||
trackerId = tracker_id.toLong(),
|
||||
remoteId = remote_id,
|
||||
libraryId = library_id,
|
||||
title = title,
|
||||
lastChapterRead = last_chapter_read.toDouble(),
|
||||
totalChapters = total_chapters.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,
|
||||
startDate = started_reading_date,
|
||||
finishDate = finished_reading_date,
|
||||
|
@ -17,8 +17,7 @@ import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.track.interactor.GetTracks
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.toJavaDuration
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
@ -43,7 +42,9 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
|
||||
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||
}
|
||||
.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)
|
||||
}
|
||||
}
|
||||
@ -61,7 +62,7 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
|
||||
|
||||
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
|
||||
.setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5.minutes.toJavaDuration())
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
|
@ -9,26 +9,24 @@ class TrackPreferences(
|
||||
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) {
|
||||
trackUsername(sync).set(username)
|
||||
trackPassword(sync).set(password)
|
||||
fun setCredentials(tracker: Tracker, username: String, password: String) {
|
||||
trackUsername(tracker).set(username)
|
||||
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 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
|
||||
|
||||
import android.os.Build
|
||||
import eu.kanade.domain.ui.model.AppTheme
|
||||
import eu.kanade.domain.ui.model.TabletUiMode
|
||||
import eu.kanade.domain.ui.model.ThemeMode
|
||||
@ -15,7 +16,10 @@ class UiPreferences(
|
||||
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(
|
||||
"pref_app_theme",
|
||||
|
@ -1,19 +1,25 @@
|
||||
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?) {
|
||||
DEFAULT(R.string.label_default),
|
||||
MONET(R.string.theme_monet),
|
||||
GREEN_APPLE(R.string.theme_greenapple),
|
||||
LAVENDER(R.string.theme_lavender),
|
||||
MIDNIGHT_DUSK(R.string.theme_midnightdusk),
|
||||
STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
|
||||
TAKO(R.string.theme_tako),
|
||||
TEALTURQUOISE(R.string.theme_tealturquoise),
|
||||
TIDAL_WAVE(R.string.theme_tidalwave),
|
||||
YINYANG(R.string.theme_yinyang),
|
||||
YOTSUBA(R.string.theme_yotsuba),
|
||||
enum class AppTheme(val titleRes: StringResource?) {
|
||||
DEFAULT(MR.strings.label_default),
|
||||
MONET(MR.strings.theme_monet),
|
||||
GREEN_APPLE(MR.strings.theme_greenapple),
|
||||
LAVENDER(MR.strings.theme_lavender),
|
||||
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
||||
|
||||
// TODO: re-enable for preview
|
||||
NORD(MR.strings.theme_nord.takeIf { isDevFlavor || isPreviewBuildType }),
|
||||
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
||||
TAKO(MR.strings.theme_tako),
|
||||
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
||||
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
||||
YINYANG(MR.strings.theme_yinyang),
|
||||
YOTSUBA(MR.strings.theme_yotsuba),
|
||||
|
||||
// Deprecated
|
||||
DARK_BLUE(null),
|
||||
|
@ -1,10 +1,11 @@
|
||||
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) {
|
||||
AUTOMATIC(R.string.automatic_background),
|
||||
ALWAYS(R.string.lock_always),
|
||||
LANDSCAPE(R.string.landscape),
|
||||
NEVER(R.string.lock_never),
|
||||
enum class TabletUiMode(val titleRes: StringResource) {
|
||||
AUTOMATIC(MR.strings.automatic_background),
|
||||
ALWAYS(MR.strings.lock_always),
|
||||
LANDSCAPE(MR.strings.landscape),
|
||||
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.lazy.grid.GridCells
|
||||
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.Refresh
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
@ -14,7 +14,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
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.components.AppBar
|
||||
import eu.kanade.presentation.util.formattedMessage
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
@ -61,7 +63,7 @@ fun BrowseSourceContent(
|
||||
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = getErrorMessage(errorState),
|
||||
actionLabel = context.getString(R.string.action_retry),
|
||||
actionLabel = context.stringResource(MR.strings.action_retry),
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
when (result) {
|
||||
@ -76,28 +78,28 @@ fun BrowseSourceContent(
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
message = getErrorMessage(errorState),
|
||||
actions = if (source is LocalSource) {
|
||||
listOf(
|
||||
persistentListOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.local_source_help_guide,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
stringRes = MR.strings.local_source_help_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = onLocalSourceHelpClick,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
persistentListOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.action_retry,
|
||||
stringRes = MR.strings.action_retry,
|
||||
icon = Icons.Outlined.Refresh,
|
||||
onClick = mangaList::refresh,
|
||||
),
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.action_open_in_web_view,
|
||||
stringRes = MR.strings.action_open_in_web_view,
|
||||
icon = Icons.Outlined.Public,
|
||||
onClick = onWebViewClick,
|
||||
),
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.label_help,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
stringRes = MR.strings.label_help,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
@ -145,7 +147,7 @@ fun BrowseSourceContent(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MissingSourceScreen(
|
||||
internal fun MissingSourceScreen(
|
||||
source: StubSource,
|
||||
navigateUp: () -> Unit,
|
||||
) {
|
||||
@ -159,7 +161,7 @@ fun MissingSourceScreen(
|
||||
},
|
||||
) { paddingValues ->
|
||||
EmptyScreen(
|
||||
message = stringResource(R.string.source_not_installed, source.toString()),
|
||||
message = stringResource(MR.strings.source_not_installed, source.toString()),
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
}
|
||||
|
@ -16,8 +16,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.History
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
@ -38,7 +37,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.font.FontWeight
|
||||
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.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
||||
import eu.kanade.tachiyomi.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.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
||||
@Composable
|
||||
@ -65,55 +67,61 @@ fun ExtensionDetailsScreen(
|
||||
navigateUp: () -> Unit,
|
||||
state: ExtensionDetailsScreenModel.State,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickWhatsNew: () -> Unit,
|
||||
onClickReadme: () -> Unit,
|
||||
onClickEnableAll: () -> Unit,
|
||||
onClickDisableAll: () -> Unit,
|
||||
onClickClearCookies: () -> Unit,
|
||||
onClickUninstall: () -> 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(
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
title = stringResource(R.string.label_extension_info),
|
||||
title = stringResource(MR.strings.label_extension_info),
|
||||
navigateUp = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
actions = buildList {
|
||||
if (state.extension?.isUnofficial == false) {
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
if (url != null) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.whats_new),
|
||||
icon = Icons.Outlined.History,
|
||||
onClick = onClickWhatsNew,
|
||||
),
|
||||
)
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_faq_and_guides),
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = onClickReadme,
|
||||
title = stringResource(MR.strings.action_open_repo),
|
||||
icon = Icons.AutoMirrored.Outlined.Launch,
|
||||
onClick = {
|
||||
uriHandler.openUri(url)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
addAll(
|
||||
listOf(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_enable_all),
|
||||
title = stringResource(MR.strings.action_enable_all),
|
||||
onClick = onClickEnableAll,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_disable_all),
|
||||
title = stringResource(MR.strings.action_disable_all),
|
||||
onClick = onClickDisableAll,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.pref_clear_cookies),
|
||||
title = stringResource(MR.strings.pref_clear_cookies),
|
||||
onClick = onClickClearCookies,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
@ -122,7 +130,7 @@ fun ExtensionDetailsScreen(
|
||||
) { paddingValues ->
|
||||
if (state.extension == null) {
|
||||
EmptyScreen(
|
||||
textResource = R.string.empty_screen,
|
||||
MR.strings.empty_screen,
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
return@Scaffold
|
||||
@ -143,7 +151,7 @@ fun ExtensionDetailsScreen(
|
||||
private fun ExtensionDetails(
|
||||
contentPadding: PaddingValues,
|
||||
extension: Extension.Installed,
|
||||
sources: List<ExtensionSourceItem>,
|
||||
sources: ImmutableList<ExtensionSourceItem>,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickUninstall: () -> Unit,
|
||||
onClickSource: (sourceId: Long) -> Unit,
|
||||
@ -154,14 +162,9 @@ private fun ExtensionDetails(
|
||||
ScrollbarLazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
when {
|
||||
extension.isUnofficial ->
|
||||
if (extension.isObsolete) {
|
||||
item {
|
||||
WarningBanner(R.string.unofficial_extension_message)
|
||||
}
|
||||
extension.isObsolete ->
|
||||
item {
|
||||
WarningBanner(R.string.obsolete_extension_message)
|
||||
WarningBanner(MR.strings.obsolete_extension_message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,7 +261,7 @@ private fun DetailsHeader(
|
||||
InfoText(
|
||||
modifier = Modifier.weight(1f),
|
||||
primaryText = extension.versionName,
|
||||
secondaryText = stringResource(R.string.ext_info_version),
|
||||
secondaryText = stringResource(MR.strings.ext_info_version),
|
||||
)
|
||||
|
||||
InfoDivider()
|
||||
@ -266,7 +269,7 @@ private fun DetailsHeader(
|
||||
InfoText(
|
||||
modifier = Modifier.weight(if (extension.isNsfw) 1.5f else 1f),
|
||||
primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context),
|
||||
secondaryText = stringResource(R.string.ext_info_language),
|
||||
secondaryText = stringResource(MR.strings.ext_info_language),
|
||||
)
|
||||
|
||||
if (extension.isNsfw) {
|
||||
@ -274,12 +277,12 @@ private fun DetailsHeader(
|
||||
|
||||
InfoText(
|
||||
modifier = Modifier.weight(1f),
|
||||
primaryText = stringResource(R.string.ext_nsfw_short),
|
||||
primaryText = stringResource(MR.strings.ext_nsfw_short),
|
||||
primaryTextStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
secondaryText = stringResource(R.string.ext_info_age_rating),
|
||||
secondaryText = stringResource(MR.strings.ext_info_age_rating),
|
||||
onClick = onClickAgeRating,
|
||||
)
|
||||
}
|
||||
@ -292,13 +295,13 @@ private fun DetailsHeader(
|
||||
top = MaterialTheme.padding.small,
|
||||
bottom = MaterialTheme.padding.medium,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||
) {
|
||||
OutlinedButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = onClickUninstall,
|
||||
) {
|
||||
Text(stringResource(R.string.ext_uninstall))
|
||||
Text(stringResource(MR.strings.ext_uninstall))
|
||||
}
|
||||
|
||||
if (onClickAppInfo != null) {
|
||||
@ -307,7 +310,7 @@ private fun DetailsHeader(
|
||||
onClick = onClickAppInfo,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.ext_app_info),
|
||||
text = stringResource(MR.strings.ext_app_info),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
}
|
||||
@ -320,10 +323,10 @@ private fun DetailsHeader(
|
||||
|
||||
@Composable
|
||||
private fun InfoText(
|
||||
modifier: Modifier,
|
||||
primaryText: String,
|
||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
secondaryText: String,
|
||||
modifier: Modifier = Modifier,
|
||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
@ -363,10 +366,10 @@ private fun InfoDivider() {
|
||||
|
||||
@Composable
|
||||
private fun SourceSwitchPreference(
|
||||
modifier: Modifier = Modifier,
|
||||
source: ExtensionSourceItem,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickSource: (sourceId: Long) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@ -385,7 +388,7 @@ private fun SourceSwitchPreference(
|
||||
IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Settings,
|
||||
contentDescription = stringResource(R.string.label_settings),
|
||||
contentDescription = stringResource(MR.strings.label_settings),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
@ -408,11 +411,11 @@ private fun NsfwWarningDialog(
|
||||
) {
|
||||
AlertDialog(
|
||||
text = {
|
||||
Text(text = stringResource(R.string.ext_nsfw_warning))
|
||||
Text(text = stringResource(MR.strings.ext_nsfw_warning))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onClickConfirm) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
onDismissRequest = onClickConfirm,
|
||||
|
@ -7,13 +7,13 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
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.util.system.LocaleHelper
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
||||
@Composable
|
||||
@ -25,7 +25,7 @@ fun ExtensionFilterScreen(
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
title = stringResource(R.string.label_extensions),
|
||||
title = stringResource(MR.strings.label_extensions),
|
||||
navigateUp = navigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
@ -33,7 +33,7 @@ fun ExtensionFilterScreen(
|
||||
) { contentPadding ->
|
||||
if (state.isEmpty) {
|
||||
EmptyScreen(
|
||||
textResource = R.string.empty_screen,
|
||||
stringRes = MR.strings.empty_screen,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
return@Scaffold
|
||||
|
@ -1,7 +1,7 @@
|
||||
package eu.kanade.presentation.browse
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.material.icons.Icons
|
||||
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.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@ -33,23 +38,32 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
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.ExtensionIcon
|
||||
import eu.kanade.presentation.components.WarningBanner
|
||||
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.InstallStep
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import tachiyomi.presentation.core.theme.header
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
@ -62,6 +76,7 @@ fun ExtensionScreen(
|
||||
searchQuery: String?,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
onClickItemCancel: (Extension) -> Unit,
|
||||
onOpenWebView: (Extension.Available) -> Unit,
|
||||
onInstallExtension: (Extension.Available) -> Unit,
|
||||
onUninstallExtension: (Extension) -> Unit,
|
||||
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||
@ -70,22 +85,31 @@ fun ExtensionScreen(
|
||||
onClickUpdateAll: () -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
) {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
PullRefresh(
|
||||
refreshing = state.isRefreshing,
|
||||
onRefresh = onRefresh,
|
||||
enabled = !state.isLoading,
|
||||
enabled = { !state.isLoading },
|
||||
) {
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
state.isEmpty -> {
|
||||
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||
R.string.no_results_found
|
||||
MR.strings.no_results_found
|
||||
} else {
|
||||
R.string.empty_screen
|
||||
MR.strings.empty_screen
|
||||
}
|
||||
EmptyScreen(
|
||||
textResource = msg,
|
||||
stringRes = msg,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
actions = persistentListOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.label_extension_repos,
|
||||
icon = Icons.Outlined.Settings,
|
||||
onClick = { navigator.push(ExtensionReposScreen()) },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
@ -94,6 +118,7 @@ fun ExtensionScreen(
|
||||
contentPadding = contentPadding,
|
||||
onLongClickItem = onLongClickItem,
|
||||
onClickItemCancel = onClickItemCancel,
|
||||
onOpenWebView = onOpenWebView,
|
||||
onInstallExtension = onInstallExtension,
|
||||
onUninstallExtension = onUninstallExtension,
|
||||
onUpdateExtension = onUpdateExtension,
|
||||
@ -112,6 +137,7 @@ private fun ExtensionContent(
|
||||
contentPadding: PaddingValues,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
onClickItemCancel: (Extension) -> Unit,
|
||||
onOpenWebView: (Extension.Available) -> Unit,
|
||||
onInstallExtension: (Extension.Available) -> Unit,
|
||||
onUninstallExtension: (Extension) -> Unit,
|
||||
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||
@ -119,11 +145,24 @@ private fun ExtensionContent(
|
||||
onOpenExtension: (Extension.Installed) -> Unit,
|
||||
onClickUpdateAll: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
||||
val installGranted = rememberRequestPackageInstallsPermissionState(initialValue = true)
|
||||
|
||||
FastScrollLazyColumn(
|
||||
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) ->
|
||||
item(
|
||||
contentType = "header",
|
||||
@ -132,11 +171,11 @@ private fun ExtensionContent(
|
||||
when (header) {
|
||||
is ExtensionUiModel.Header.Resource -> {
|
||||
val action: @Composable RowScope.() -> Unit =
|
||||
if (header.textRes == R.string.ext_updates_pending) {
|
||||
if (header.textRes == MR.strings.ext_updates_pending) {
|
||||
{
|
||||
Button(onClick = { onClickUpdateAll() }) {
|
||||
Text(
|
||||
text = stringResource(R.string.ext_update_all),
|
||||
text = stringResource(MR.strings.ext_update_all),
|
||||
style = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
),
|
||||
@ -177,6 +216,13 @@ private fun ExtensionContent(
|
||||
}
|
||||
},
|
||||
onLongClickItem = onLongClickItem,
|
||||
onClickItemSecondaryAction = {
|
||||
when (it) {
|
||||
is Extension.Available -> onOpenWebView(it)
|
||||
is Extension.Installed -> onOpenExtension(it)
|
||||
else -> {}
|
||||
}
|
||||
},
|
||||
onClickItemCancel = onClickItemCancel,
|
||||
onClickItemAction = {
|
||||
when (it) {
|
||||
@ -214,12 +260,13 @@ private fun ExtensionContent(
|
||||
|
||||
@Composable
|
||||
private fun ExtensionItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: ExtensionUiModel.Item,
|
||||
onClickItem: (Extension) -> Unit,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
onClickItemCancel: (Extension) -> Unit,
|
||||
onClickItemAction: (Extension) -> Unit,
|
||||
onClickItemSecondaryAction: (Extension) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val (extension, installStep) = item
|
||||
BaseBrowseItem(
|
||||
@ -262,6 +309,7 @@ private fun ExtensionItem(
|
||||
installStep = installStep,
|
||||
onClickItemCancel = onClickItemCancel,
|
||||
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
|
||||
FlowRow(
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
) {
|
||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
||||
@ -307,10 +355,9 @@ private fun ExtensionItemContent(
|
||||
}
|
||||
|
||||
val warning = when {
|
||||
extension is Extension.Untrusted -> R.string.ext_untrusted
|
||||
extension is Extension.Installed && extension.isUnofficial -> R.string.ext_unofficial
|
||||
extension is Extension.Installed && extension.isObsolete -> R.string.ext_obsolete
|
||||
extension.isNsfw -> R.string.ext_nsfw_short
|
||||
extension is Extension.Untrusted -> MR.strings.ext_untrusted
|
||||
extension is Extension.Installed && extension.isObsolete -> MR.strings.ext_obsolete
|
||||
extension.isNsfw -> MR.strings.ext_nsfw_short
|
||||
else -> null
|
||||
}
|
||||
if (warning != null) {
|
||||
@ -326,9 +373,9 @@ private fun ExtensionItemContent(
|
||||
DotSeparatorNoSpaceText()
|
||||
Text(
|
||||
text = when (installStep) {
|
||||
InstallStep.Pending -> stringResource(R.string.ext_pending)
|
||||
InstallStep.Downloading -> stringResource(R.string.ext_downloading)
|
||||
InstallStep.Installing -> stringResource(R.string.ext_installing)
|
||||
InstallStep.Pending -> stringResource(MR.strings.ext_pending)
|
||||
InstallStep.Downloading -> stringResource(MR.strings.ext_downloading)
|
||||
InstallStep.Installing -> stringResource(MR.strings.ext_installing)
|
||||
else -> error("Must not show non-install process text")
|
||||
},
|
||||
)
|
||||
@ -345,48 +392,86 @@ private fun ExtensionItemActions(
|
||||
modifier: Modifier = Modifier,
|
||||
onClickItemCancel: (Extension) -> Unit = {},
|
||||
onClickItemAction: (Extension) -> Unit = {},
|
||||
onClickItemSecondaryAction: (Extension) -> Unit = {},
|
||||
) {
|
||||
val isIdle = installStep.isCompleted()
|
||||
Row(modifier = modifier) {
|
||||
if (isIdle) {
|
||||
TextButton(
|
||||
onClick = { onClickItemAction(extension) },
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
) {
|
||||
Text(
|
||||
text = when (installStep) {
|
||||
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 {
|
||||
when {
|
||||
!isIdle -> {
|
||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||
Icon(
|
||||
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
|
||||
private fun ExtensionHeader(
|
||||
@StringRes textRes: Int,
|
||||
textRes: StringResource,
|
||||
modifier: Modifier = Modifier,
|
||||
action: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
@ -426,19 +511,19 @@ private fun ExtensionTrustDialog(
|
||||
) {
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = stringResource(R.string.untrusted_extension))
|
||||
Text(text = stringResource(MR.strings.untrusted_extension))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.untrusted_extension_message))
|
||||
Text(text = stringResource(MR.strings.untrusted_extension_message))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onClickConfirm) {
|
||||
Text(text = stringResource(R.string.ext_trust))
|
||||
Text(text = stringResource(MR.strings.ext_trust))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onClickDismiss) {
|
||||
Text(text = stringResource(R.string.ext_uninstall))
|
||||
Text(text = stringResource(MR.strings.ext_uninstall))
|
||||
}
|
||||
},
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
@ -60,13 +60,13 @@ fun GlobalSearchScreen(
|
||||
|
||||
@Composable
|
||||
internal fun GlobalSearchContent(
|
||||
fromSourceId: Long? = null,
|
||||
items: Map<CatalogueSource, SearchItemResult>,
|
||||
contentPadding: PaddingValues,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
fromSourceId: Long? = null,
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
@ -74,8 +74,10 @@ internal fun GlobalSearchContent(
|
||||
items.forEach { (source, result) ->
|
||||
item(key = source.id) {
|
||||
GlobalSearchResultItem(
|
||||
title = fromSourceId?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
|
||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||
title = fromSourceId?.let {
|
||||
"▶ ${source.name}".takeIf { source.id == fromSourceId }
|
||||
} ?: source.name,
|
||||
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||
onClick = { onClickSource(source) },
|
||||
) {
|
||||
when (result) {
|
||||
|
@ -7,9 +7,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
@ -33,7 +33,7 @@ fun MigrateMangaScreen(
|
||||
) { contentPadding ->
|
||||
if (state.isEmpty) {
|
||||
EmptyScreen(
|
||||
textResource = R.string.empty_screen,
|
||||
stringRes = MR.strings.empty_screen,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
return@Scaffold
|
||||
@ -70,10 +70,10 @@ private fun MigrateMangaContent(
|
||||
|
||||
@Composable
|
||||
private fun MigrateMangaItem(
|
||||
modifier: Modifier = Modifier,
|
||||
manga: Manga,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onClickCover: (Manga) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseMangaListItem(
|
||||
modifier = modifier,
|
||||
|
@ -20,21 +20,22 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||
import eu.kanade.presentation.browse.components.SourceIcon
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.Badge
|
||||
import tachiyomi.presentation.core.components.BadgeGroup
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import tachiyomi.presentation.core.theme.header
|
||||
@ -53,7 +54,7 @@ fun MigrateSourceScreen(
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
state.isEmpty -> EmptyScreen(
|
||||
textResource = R.string.information_empty_library,
|
||||
stringRes = MR.strings.information_empty_library,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
else ->
|
||||
@ -75,7 +76,7 @@ fun MigrateSourceScreen(
|
||||
|
||||
@Composable
|
||||
private fun MigrateSourceList(
|
||||
list: List<Pair<Source, Long>>,
|
||||
list: ImmutableList<Pair<Source, Long>>,
|
||||
contentPadding: PaddingValues,
|
||||
onClickItem: (Source) -> Unit,
|
||||
onLongClickItem: (Source) -> Unit,
|
||||
@ -95,21 +96,33 @@ private fun MigrateSourceList(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.migration_selection_prompt),
|
||||
text = stringResource(MR.strings.migration_selection_prompt),
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.header,
|
||||
)
|
||||
|
||||
IconButton(onClick = onToggleSortingMode) {
|
||||
when (sortingMode) {
|
||||
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(Icons.Outlined.SortByAlpha, contentDescription = stringResource(R.string.action_sort_alpha))
|
||||
SetMigrateSorting.Mode.TOTAL -> Icon(Icons.Outlined.Numbers, contentDescription = stringResource(R.string.action_sort_count))
|
||||
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(
|
||||
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) {
|
||||
when (sortingDirection) {
|
||||
SetMigrateSorting.Direction.ASCENDING -> Icon(Icons.Outlined.ArrowUpward, contentDescription = stringResource(R.string.action_asc))
|
||||
SetMigrateSorting.Direction.DESCENDING -> Icon(Icons.Outlined.ArrowDownward, contentDescription = stringResource(R.string.action_desc))
|
||||
SetMigrateSorting.Direction.ASCENDING -> Icon(
|
||||
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
|
||||
private fun MigrateSourceItem(
|
||||
modifier: Modifier = Modifier,
|
||||
source: Source,
|
||||
count: Long,
|
||||
onClickItem: () -> Unit,
|
||||
onLongClickItem: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseSourceItem(
|
||||
modifier = modifier,
|
||||
@ -178,7 +191,7 @@ private fun MigrateSourceItem(
|
||||
if (source.isStub) {
|
||||
Text(
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
text = stringResource(R.string.not_installed),
|
||||
text = stringResource(MR.strings.not_installed),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
|
@ -7,16 +7,16 @@ import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
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.util.system.LocaleHelper
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
||||
@Composable
|
||||
@ -29,7 +29,7 @@ fun SourcesFilterScreen(
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
title = stringResource(R.string.label_sources),
|
||||
title = stringResource(MR.strings.label_sources),
|
||||
navigateUp = navigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
@ -37,7 +37,7 @@ fun SourcesFilterScreen(
|
||||
) { contentPadding ->
|
||||
if (state.isEmpty) {
|
||||
EmptyScreen(
|
||||
textResource = R.string.source_filter_empty_screen,
|
||||
stringRes = MR.strings.source_filter_empty_screen,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
return@Scaffold
|
||||
@ -94,10 +94,10 @@ private fun SourcesFilterContent(
|
||||
|
||||
@Composable
|
||||
private fun SourcesFilterHeader(
|
||||
modifier: Modifier,
|
||||
language: String,
|
||||
enabled: Boolean,
|
||||
onClickItem: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SwitchPreferenceWidget(
|
||||
modifier = modifier,
|
||||
@ -109,10 +109,10 @@ private fun SourcesFilterHeader(
|
||||
|
||||
@Composable
|
||||
private fun SourcesFilterItem(
|
||||
modifier: Modifier,
|
||||
source: Source,
|
||||
enabled: Boolean,
|
||||
onClickItem: (Source) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseSourceItem(
|
||||
modifier = modifier,
|
||||
|
@ -19,19 +19,19 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.browse.BrowseSourceScreenModel.Listing
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.source.model.Pin
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import tachiyomi.presentation.core.theme.header
|
||||
@ -49,7 +49,7 @@ fun SourcesScreen(
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
state.isEmpty -> EmptyScreen(
|
||||
textResource = R.string.source_empty_screen,
|
||||
stringRes = MR.strings.source_empty_screen,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
else -> {
|
||||
@ -94,8 +94,8 @@ fun SourcesScreen(
|
||||
|
||||
@Composable
|
||||
private fun SourceHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
language: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Text(
|
||||
@ -108,11 +108,11 @@ private fun SourceHeader(
|
||||
|
||||
@Composable
|
||||
private fun SourceItem(
|
||||
modifier: Modifier = Modifier,
|
||||
source: Source,
|
||||
onClickItem: (Source, Listing) -> Unit,
|
||||
onLongClickItem: (Source) -> Unit,
|
||||
onClickPin: (Source) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseSourceItem(
|
||||
modifier = modifier,
|
||||
@ -123,7 +123,7 @@ private fun SourceItem(
|
||||
if (source.supportsLatest) {
|
||||
TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
|
||||
Text(
|
||||
text = stringResource(R.string.latest),
|
||||
text = stringResource(MR.strings.latest),
|
||||
style = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
@ -144,8 +144,14 @@ private fun SourcePinButton(
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
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 description = if (isPinned) R.string.action_unpin else R.string.action_pin
|
||||
val tint = if (isPinned) {
|
||||
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) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
@ -168,7 +174,7 @@ fun SourceOptionsDialog(
|
||||
},
|
||||
text = {
|
||||
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 = stringResource(textId),
|
||||
modifier = Modifier
|
||||
@ -178,7 +184,7 @@ fun SourceOptionsDialog(
|
||||
)
|
||||
if (!source.isLocal()) {
|
||||
Text(
|
||||
text = stringResource(R.string.action_disable),
|
||||
text = stringResource(MR.strings.action_disable),
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClickDisable)
|
||||
.fillMaxWidth()
|
||||
|
@ -16,8 +16,8 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
|
||||
@Composable
|
||||
fun BaseSourceItem(
|
||||
modifier: Modifier = Modifier,
|
||||
source: Source,
|
||||
modifier: Modifier = Modifier,
|
||||
showLanguageInContent: Boolean = true,
|
||||
onClickItem: () -> Unit = {},
|
||||
onLongClickItem: () -> Unit = {},
|
||||
@ -25,7 +25,9 @@ fun BaseSourceItem(
|
||||
action: @Composable RowScope.(Source) -> Unit = {},
|
||||
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(
|
||||
modifier = modifier,
|
||||
onClickItem = onClickItem,
|
||||
|
@ -4,11 +4,9 @@ import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
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.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun RemoveMangaDialog(
|
||||
@ -20,7 +18,7 @@ fun RemoveMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
@ -30,14 +28,14 @@ fun RemoveMangaDialog(
|
||||
onConfirm()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_remove))
|
||||
Text(text = stringResource(MR.strings.action_remove))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.are_you_sure))
|
||||
Text(text = stringResource(MR.strings.are_you_sure))
|
||||
},
|
||||
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
|
||||
|
||||
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.material3.Text
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
@ -10,17 +10,18 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.presentation.components.RadioMenuItem
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.source.local.LocalSource
|
||||
|
||||
@Composable
|
||||
@ -53,50 +54,66 @@ fun BrowseSourceToolbar(
|
||||
onClickCloseSearch = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
actions = listOfNotNull(
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_display_mode),
|
||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
|
||||
title = stringResource(MR.strings.action_display_mode),
|
||||
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||
Icons.AutoMirrored.Filled.ViewList
|
||||
} else {
|
||||
Icons.Filled.ViewModule
|
||||
},
|
||||
onClick = { selectingDisplayMode = true },
|
||||
),
|
||||
)
|
||||
if (isLocalSource) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.label_help),
|
||||
title = stringResource(MR.strings.label_help),
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_open_in_web_view),
|
||||
title = stringResource(MR.strings.action_open_in_web_view),
|
||||
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(
|
||||
expanded = selectingDisplayMode,
|
||||
onDismissRequest = { selectingDisplayMode = false },
|
||||
) {
|
||||
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,
|
||||
) {
|
||||
selectingDisplayMode = false
|
||||
onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
|
||||
}
|
||||
RadioMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_display_grid)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_display_grid)) },
|
||||
isChecked = displayMode == LibraryDisplayMode.CompactGrid,
|
||||
) {
|
||||
selectingDisplayMode = false
|
||||
onDisplayModeChange(LibraryDisplayMode.CompactGrid)
|
||||
}
|
||||
RadioMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_display_list)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_display_list)) },
|
||||
isChecked = displayMode == LibraryDisplayMode.List,
|
||||
) {
|
||||
selectingDisplayMode = false
|
||||
|
@ -13,15 +13,15 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||
import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaCover
|
||||
import tachiyomi.domain.manga.model.asMangaCover
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchCardRow(
|
||||
@ -37,7 +37,7 @@ fun GlobalSearchCardRow(
|
||||
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
) {
|
||||
items(titles) {
|
||||
val title by getManga(it)
|
||||
@ -78,7 +78,7 @@ private fun MangaItem(
|
||||
@Composable
|
||||
private fun EmptyResultItem() {
|
||||
Text(
|
||||
text = stringResource(R.string.no_results_found),
|
||||
text = stringResource(MR.strings.no_results_found),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
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.size
|
||||
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.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
@ -21,11 +21,11 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchResultItem(
|
||||
@ -39,7 +39,7 @@ fun GlobalSearchResultItem(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = MaterialTheme.padding.medium,
|
||||
end = MaterialTheme.padding.tiny,
|
||||
end = MaterialTheme.padding.extraSmall,
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
@ -54,7 +54,7 @@ fun GlobalSearchResultItem(
|
||||
Text(text = subtitle)
|
||||
}
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null)
|
||||
}
|
||||
}
|
||||
content()
|
||||
@ -92,7 +92,7 @@ fun GlobalSearchErrorResultItem(message: String?) {
|
||||
Icon(imageVector = Icons.Outlined.Error, contentDescription = null)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = message ?: stringResource(R.string.unknown_error),
|
||||
text = message ?: stringResource(MR.strings.unknown_error),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchToolbar(
|
||||
@ -58,7 +58,7 @@ fun GlobalSearchToolbar(
|
||||
)
|
||||
if (progress in 1..<total) {
|
||||
LinearProgressIndicator(
|
||||
progress = progress / total.toFloat(),
|
||||
progress = { progress / total.toFloat() },
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.fillMaxWidth(),
|
||||
@ -85,7 +85,7 @@ fun GlobalSearchToolbar(
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.pinned_sources))
|
||||
Text(text = stringResource(MR.strings.pinned_sources))
|
||||
},
|
||||
)
|
||||
FilterChip(
|
||||
@ -100,7 +100,7 @@ fun GlobalSearchToolbar(
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.all))
|
||||
Text(text = stringResource(MR.strings.all))
|
||||
},
|
||||
)
|
||||
|
||||
@ -118,7 +118,7 @@ fun GlobalSearchToolbar(
|
||||
)
|
||||
},
|
||||
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 androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
val Category.visualName: String
|
||||
@Composable
|
||||
get() = when {
|
||||
isSystemCategory -> stringResource(R.string.label_default)
|
||||
isSystemCategory -> stringResource(MR.strings.label_default)
|
||||
else -> name
|
||||
}
|
||||
|
||||
fun Category.visualName(context: Context): String =
|
||||
when {
|
||||
isSystemCategory -> context.getString(R.string.label_default)
|
||||
isSystemCategory -> context.stringResource(MR.strings.label_default)
|
||||
else -> name
|
||||
}
|
||||
|
@ -12,17 +12,18 @@ import androidx.compose.material.icons.outlined.SortByAlpha
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
||||
import eu.kanade.presentation.category.components.CategoryListItem
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreenState
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
|
||||
@ -41,13 +42,13 @@ fun CategoryScreen(
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
title = stringResource(R.string.action_edit_categories),
|
||||
title = stringResource(MR.strings.action_edit_categories),
|
||||
navigateUp = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
listOf(
|
||||
persistentListOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_sort),
|
||||
title = stringResource(MR.strings.action_sort),
|
||||
icon = Icons.Outlined.SortByAlpha,
|
||||
onClick = onClickSortAlphabetically,
|
||||
),
|
||||
@ -66,7 +67,7 @@ fun CategoryScreen(
|
||||
) { paddingValues ->
|
||||
if (state.isEmpty) {
|
||||
EmptyScreen(
|
||||
textResource = R.string.information_empty_category,
|
||||
stringRes = MR.strings.information_empty_category,
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
return@Scaffold
|
||||
@ -75,7 +76,9 @@ fun CategoryScreen(
|
||||
CategoryContent(
|
||||
categories = state.categories,
|
||||
lazyListState = lazyListState,
|
||||
paddingValues = paddingValues + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||
paddingValues = paddingValues +
|
||||
topSmallPaddingValues +
|
||||
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||
onClickRename = onClickRename,
|
||||
onClickDelete = onClickDelete,
|
||||
onMoveUp = onClickMoveUp,
|
||||
|
@ -25,26 +25,28 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.presentation.category.visualName
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import tachiyomi.core.preference.CheckboxState
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
fun CategoryCreateDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: (String) -> Unit,
|
||||
categories: List<Category>,
|
||||
categories: ImmutableList<String>,
|
||||
) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val nameAlreadyExists = remember(name) { categories.anyWithName(name) }
|
||||
val nameAlreadyExists = remember(name) { categories.contains(name) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
@ -56,25 +58,32 @@ fun CategoryCreateDialog(
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_add))
|
||||
Text(text = stringResource(MR.strings.action_add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_add_category))
|
||||
Text(text = stringResource(MR.strings.action_add_category))
|
||||
},
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text(text = stringResource(R.string.name)) },
|
||||
label = {
|
||||
Text(text = stringResource(MR.strings.name))
|
||||
},
|
||||
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))
|
||||
},
|
||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||
@ -94,14 +103,14 @@ fun CategoryCreateDialog(
|
||||
fun CategoryRenameDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onRename: (String) -> Unit,
|
||||
categories: List<Category>,
|
||||
category: Category,
|
||||
categories: ImmutableList<String>,
|
||||
category: String,
|
||||
) {
|
||||
var name by remember { mutableStateOf(category.name) }
|
||||
var name by remember { mutableStateOf(category) }
|
||||
var valueHasChanged by remember { mutableStateOf(false) }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val nameAlreadyExists = remember(name) { categories.anyWithName(name) }
|
||||
val nameAlreadyExists = remember(name) { categories.contains(name) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
@ -113,16 +122,16 @@ fun CategoryRenameDialog(
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_rename_category))
|
||||
Text(text = stringResource(MR.strings.action_rename_category))
|
||||
},
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
@ -132,9 +141,13 @@ fun CategoryRenameDialog(
|
||||
valueHasChanged = name != it
|
||||
name = it
|
||||
},
|
||||
label = { Text(text = stringResource(R.string.name)) },
|
||||
label = { Text(text = stringResource(MR.strings.name)) },
|
||||
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))
|
||||
},
|
||||
isError = valueHasChanged && nameAlreadyExists,
|
||||
@ -154,7 +167,7 @@ fun CategoryRenameDialog(
|
||||
fun CategoryDeleteDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
category: Category,
|
||||
category: String,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
@ -163,19 +176,19 @@ fun CategoryDeleteDialog(
|
||||
onDelete()
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.delete_category))
|
||||
Text(text = stringResource(MR.strings.delete_category))
|
||||
},
|
||||
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()
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_sort_category))
|
||||
Text(text = stringResource(MR.strings.action_sort_category))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.sort_category_confirmation))
|
||||
Text(text = stringResource(MR.strings.sort_category_confirmation))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChangeCategoryDialog(
|
||||
initialSelection: List<CheckboxState<Category>>,
|
||||
initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onEditCategories: () -> Unit,
|
||||
onConfirm: (List<Long>, List<Long>) -> Unit,
|
||||
@ -226,14 +239,14 @@ fun ChangeCategoryDialog(
|
||||
onEditCategories()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_edit_categories))
|
||||
Text(text = stringResource(MR.strings.action_edit_categories))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_move_category))
|
||||
Text(text = stringResource(MR.strings.action_move_category))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.information_empty_category_dialog))
|
||||
Text(text = stringResource(MR.strings.information_empty_category_dialog))
|
||||
},
|
||||
)
|
||||
return
|
||||
@ -247,27 +260,31 @@ fun ChangeCategoryDialog(
|
||||
onDismissRequest()
|
||||
onEditCategories()
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_edit))
|
||||
Text(text = stringResource(MR.strings.action_edit))
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
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(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onConfirm(
|
||||
selection.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 },
|
||||
selection
|
||||
.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 = {
|
||||
Text(text = stringResource(R.string.action_move_category))
|
||||
Text(text = stringResource(MR.strings.action_move_category))
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
@ -279,7 +296,7 @@ fun ChangeCategoryDialog(
|
||||
if (index != -1) {
|
||||
val mutableList = selection.toMutableList()
|
||||
mutableList[index] = it.next()
|
||||
selection = mutableList.toList()
|
||||
selection = mutableList.toList().toImmutableList()
|
||||
}
|
||||
}
|
||||
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.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.tachiyomi.R
|
||||
import androidx.compose.ui.Modifier
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrollingUp
|
||||
|
||||
@ -16,11 +17,13 @@ import tachiyomi.presentation.core.util.isScrollingUp
|
||||
fun CategoryFloatingActionButton(
|
||||
lazyListState: LazyListState,
|
||||
onCreate: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(R.string.action_add)) },
|
||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") },
|
||||
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||
onClick = onCreate,
|
||||
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.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||
import androidx.compose.material.icons.outlined.ArrowDropDown
|
||||
import androidx.compose.material.icons.outlined.ArrowDropUp
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.Edit
|
||||
import androidx.compose.material.icons.outlined.Label
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@ -19,14 +19,13 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun CategoryListItem(
|
||||
modifier: Modifier = Modifier,
|
||||
category: Category,
|
||||
canMoveUp: Boolean,
|
||||
canMoveDown: Boolean,
|
||||
@ -34,6 +33,7 @@ fun CategoryListItem(
|
||||
onMoveDown: (Category) -> Unit,
|
||||
onRename: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ElevatedCard(
|
||||
modifier = modifier,
|
||||
@ -49,7 +49,7 @@ fun CategoryListItem(
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(imageVector = Icons.Outlined.Label, contentDescription = "")
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
||||
Text(
|
||||
text = category.name,
|
||||
modifier = Modifier
|
||||
@ -61,20 +61,23 @@ fun CategoryListItem(
|
||||
onClick = { onMoveUp(category) },
|
||||
enabled = canMoveUp,
|
||||
) {
|
||||
Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = "")
|
||||
Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = null)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { onMoveDown(category) },
|
||||
enabled = canMoveDown,
|
||||
) {
|
||||
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = "")
|
||||
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = null)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
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) {
|
||||
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
|
||||
fun AdaptiveSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
tonalElevation: Dp = 1.dp,
|
||||
enableSwipeDismiss: Boolean = true,
|
||||
onDismissRequest: () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val isTabletUi = isTabletUi()
|
||||
|
@ -10,8 +10,7 @@ import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.ArrowForward
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
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.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PlainTooltipBox
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
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.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.runOnEnterKeyPressed
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
@ -60,10 +61,11 @@ const val SEARCH_DEBOUNCE_MILLIS = 250L
|
||||
|
||||
@Composable
|
||||
fun AppBar(
|
||||
title: String?,
|
||||
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color? = null,
|
||||
// Text
|
||||
title: String?,
|
||||
subtitle: String? = null,
|
||||
// Up button
|
||||
navigateUp: (() -> Unit)? = null,
|
||||
@ -88,7 +90,7 @@ fun AppBar(
|
||||
if (isActionMode) {
|
||||
AppBarTitle(actionModeCounter.toString())
|
||||
} else {
|
||||
AppBarTitle(title, subtitle)
|
||||
AppBarTitle(title, subtitle = subtitle)
|
||||
}
|
||||
},
|
||||
navigateUp = navigateUp,
|
||||
@ -108,10 +110,11 @@ fun AppBar(
|
||||
|
||||
@Composable
|
||||
fun AppBar(
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color? = null,
|
||||
// Title
|
||||
titleContent: @Composable () -> Unit,
|
||||
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color? = null,
|
||||
// Up button
|
||||
navigateUp: (() -> Unit)? = null,
|
||||
navigationIcon: ImageVector? = null,
|
||||
@ -132,13 +135,13 @@ fun AppBar(
|
||||
IconButton(onClick = onCancelActionMode) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.action_cancel),
|
||||
contentDescription = stringResource(MR.strings.action_cancel),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
navigateUp?.let {
|
||||
IconButton(onClick = it) {
|
||||
UpIcon(navigationIcon)
|
||||
UpIcon(navigationIcon = navigationIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,9 +161,10 @@ fun AppBar(
|
||||
@Composable
|
||||
fun AppBarTitle(
|
||||
title: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
subtitle: String? = null,
|
||||
) {
|
||||
Column {
|
||||
Column(modifier = modifier) {
|
||||
title?.let {
|
||||
Text(
|
||||
text = it,
|
||||
@ -184,18 +188,23 @@ fun AppBarTitle(
|
||||
|
||||
@Composable
|
||||
fun AppBarActions(
|
||||
actions: List<AppBar.AppBarAction>,
|
||||
actions: ImmutableList<AppBar.AppBarAction>,
|
||||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
actions.filterIsInstance<AppBar.Action>().map {
|
||||
PlainTooltipBox(
|
||||
tooltip = { Text(it.title) },
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(it.title)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = it.onClick,
|
||||
enabled = it.enabled,
|
||||
modifier = Modifier.tooltipTrigger(),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = it.icon,
|
||||
@ -208,16 +217,21 @@ fun AppBarActions(
|
||||
|
||||
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
|
||||
if (overflowActions.isNotEmpty()) {
|
||||
PlainTooltipBox(
|
||||
tooltip = { Text(stringResource(R.string.abc_action_menu_overflow_description)) },
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(stringResource(MR.strings.action_menu_overflow_description))
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { showMenu = !showMenu },
|
||||
modifier = Modifier.tooltipTrigger(),
|
||||
) {
|
||||
Icon(
|
||||
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 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
|
||||
fun SearchToolbar(
|
||||
searchQuery: String?,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
titleContent: @Composable () -> Unit = {},
|
||||
navigateUp: (() -> Unit)? = null,
|
||||
searchEnabled: Boolean = true,
|
||||
searchQuery: String?,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
placeholderText: String? = null,
|
||||
onSearch: (String) -> Unit = {},
|
||||
onClickCloseSearch: () -> Unit = { onChangeSearchQuery(null) },
|
||||
@ -262,6 +277,7 @@ fun SearchToolbar(
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
AppBar(
|
||||
modifier = modifier,
|
||||
titleContent = {
|
||||
if (searchQuery == null) return@AppBar titleContent()
|
||||
|
||||
@ -306,7 +322,7 @@ fun SearchToolbar(
|
||||
placeholder = {
|
||||
Text(
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
text = (placeholderText ?: stringResource(R.string.action_search_hint)),
|
||||
text = (placeholderText ?: stringResource(MR.strings.action_search_hint)),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
@ -327,33 +343,43 @@ fun SearchToolbar(
|
||||
if (!searchEnabled) {
|
||||
// Don't show search action
|
||||
} else if (searchQuery == null) {
|
||||
PlainTooltipBox(
|
||||
tooltip = { Text(stringResource(R.string.action_search)) },
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(stringResource(MR.strings.action_search))
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.tooltipTrigger(),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Search,
|
||||
contentDescription = stringResource(R.string.action_search),
|
||||
contentDescription = stringResource(MR.strings.action_search),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (searchQuery.isNotEmpty()) {
|
||||
PlainTooltipBox(
|
||||
tooltip = { Text(stringResource(R.string.action_reset)) },
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(stringResource(MR.strings.action_reset))
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onClick()
|
||||
focusRequester.requestFocus()
|
||||
},
|
||||
modifier = Modifier.tooltipTrigger(),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.action_reset),
|
||||
contentDescription = stringResource(MR.strings.action_reset),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -368,12 +394,16 @@ fun SearchToolbar(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpIcon(navigationIcon: ImageVector? = null) {
|
||||
fun UpIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
navigationIcon: ImageVector? = null,
|
||||
) {
|
||||
val icon = navigationIcon
|
||||
?: if (LocalLayoutDirection.current == LayoutDirection.Ltr) Icons.Outlined.ArrowBack else Icons.Outlined.ArrowForward
|
||||
?: Icons.AutoMirrored.Outlined.ArrowBack
|
||||
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
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
@ -26,13 +25,14 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastMap
|
||||
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
|
||||
@Composable get() = MaterialTheme.colorScheme.tertiary
|
||||
@ -43,7 +43,7 @@ val IndexingBannerBackgroundColor
|
||||
|
||||
@Composable
|
||||
fun WarningBanner(
|
||||
@StringRes textRes: Int,
|
||||
textRes: StringResource,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
@ -127,7 +127,7 @@ fun AppStateBanners(
|
||||
@Composable
|
||||
private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = stringResource(R.string.label_downloaded_only),
|
||||
text = stringResource(MR.strings.label_downloaded_only),
|
||||
modifier = Modifier
|
||||
.background(DownloadedOnlyBannerBackgroundColor)
|
||||
.fillMaxWidth()
|
||||
@ -142,7 +142,7 @@ private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
|
||||
@Composable
|
||||
private fun IncognitoModeBanner(modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = stringResource(R.string.pref_incognito_mode),
|
||||
text = stringResource(MR.strings.pref_incognito_mode),
|
||||
modifier = Modifier
|
||||
.background(IncognitoModeBannerBackgroundColor)
|
||||
.fillMaxWidth()
|
||||
@ -173,7 +173,7 @@ private fun IndexingDownloadBanner(modifier: Modifier = Modifier) {
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.download_notifier_cache_renewal),
|
||||
text = stringResource(MR.strings.download_notifier_cache_renewal),
|
||||
color = MaterialTheme.colorScheme.onSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
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.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.Modifier
|
||||
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
|
||||
fun DownloadDropdownMenu(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> 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(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
) {
|
||||
listOfNotNull(
|
||||
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) ->
|
||||
options.map { (downloadAction, string) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = string) },
|
||||
onClick = {
|
||||
|
@ -1,10 +1,12 @@
|
||||
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.sizeIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowLeft
|
||||
import androidx.compose.material.icons.outlined.ArrowRight
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
|
||||
import androidx.compose.material.icons.outlined.RadioButtonChecked
|
||||
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
@ -16,21 +18,24 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
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.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
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
|
||||
|
||||
/**
|
||||
* DropdownMenu but overlaps anchor and has width constraints to better
|
||||
* match non-Compose implementation.
|
||||
*/
|
||||
@Composable
|
||||
fun DropdownMenu(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
offset: DpOffset = DpOffset(8.dp, (-56).dp),
|
||||
scrollState: ScrollState = rememberScrollState(),
|
||||
properties: PopupProperties = PopupProperties(focusable = true),
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
@ -39,6 +44,7 @@ fun DropdownMenu(
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp),
|
||||
offset = offset,
|
||||
scrollState = scrollState,
|
||||
properties = properties,
|
||||
content = content,
|
||||
)
|
||||
@ -48,6 +54,7 @@ fun DropdownMenu(
|
||||
fun RadioMenuItem(
|
||||
text: @Composable () -> Unit,
|
||||
isChecked: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
@ -57,16 +64,17 @@ fun RadioMenuItem(
|
||||
if (isChecked) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.RadioButtonChecked,
|
||||
contentDescription = stringResource(R.string.selected),
|
||||
contentDescription = stringResource(MR.strings.selected),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
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(
|
||||
text: @Composable () -> Unit,
|
||||
children: @Composable ColumnScope.(() -> Unit) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var nestedExpanded by remember { mutableStateOf(false) }
|
||||
val closeMenu = { nestedExpanded = false }
|
||||
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||
|
||||
Box {
|
||||
DropdownMenuItem(
|
||||
text = text,
|
||||
onClick = { nestedExpanded = true },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
imageVector = if (isLtr) Icons.Outlined.ArrowRight else Icons.Outlined.ArrowLeft,
|
||||
imageVector = Icons.AutoMirrored.Outlined.ArrowRight,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
@ -93,7 +102,9 @@ fun NestedMenuItem(
|
||||
DropdownMenu(
|
||||
expanded = nestedExpanded,
|
||||
onDismissRequest = closeMenu,
|
||||
modifier = modifier,
|
||||
) {
|
||||
children(closeMenu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,44 +1,45 @@
|
||||
package eu.kanade.presentation.components
|
||||
|
||||
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.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
|
||||
@ThemePreviews
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun NoActionPreview() {
|
||||
TachiyomiTheme {
|
||||
TachiyomiPreviewTheme {
|
||||
Surface {
|
||||
EmptyScreen(
|
||||
textResource = R.string.empty_screen,
|
||||
stringRes = MR.strings.empty_screen,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun WithActionPreview() {
|
||||
TachiyomiTheme {
|
||||
TachiyomiPreviewTheme {
|
||||
Surface {
|
||||
EmptyScreen(
|
||||
textResource = R.string.empty_screen,
|
||||
actions = listOf(
|
||||
stringRes = MR.strings.empty_screen,
|
||||
actions = persistentListOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.action_retry,
|
||||
stringRes = MR.strings.action_retry,
|
||||
icon = Icons.Outlined.Refresh,
|
||||
onClick = {},
|
||||
),
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.getting_started_guide,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
stringRes = MR.strings.getting_started_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
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.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -24,14 +24,14 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.HorizontalPager
|
||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
||||
import tachiyomi.presentation.core.components.material.TabText
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
object TabbedDialogPaddings {
|
||||
val Horizontal = 24.dp
|
||||
@ -40,9 +40,9 @@ object TabbedDialogPaddings {
|
||||
|
||||
@Composable
|
||||
fun TabbedDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
onDismissRequest: () -> Unit,
|
||||
tabTitles: List<String>,
|
||||
tabTitles: ImmutableList<String>,
|
||||
modifier: Modifier = Modifier,
|
||||
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
|
||||
pagerState: PagerState = rememberPagerState { tabTitles.size },
|
||||
content: @Composable (Int) -> Unit,
|
||||
@ -55,10 +55,9 @@ fun TabbedDialog(
|
||||
|
||||
Column {
|
||||
Row {
|
||||
TabRow(
|
||||
PrimaryTabRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
|
||||
divider = {},
|
||||
) {
|
||||
tabTitles.fastForEachIndexed { index, tab ->
|
||||
@ -95,7 +94,7 @@ private fun MoreMenu(
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = stringResource(R.string.label_more),
|
||||
contentDescription = stringResource(MR.strings.label_more),
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
|
@ -1,6 +1,5 @@
|
||||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.pager.rememberPagerState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
@ -20,17 +19,20 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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 tachiyomi.presentation.core.components.HorizontalPager
|
||||
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.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun TabbedScreen(
|
||||
@StringRes titleRes: Int,
|
||||
tabs: List<TabContent>,
|
||||
titleRes: StringResource,
|
||||
tabs: ImmutableList<TabContent>,
|
||||
startIndex: Int? = null,
|
||||
searchQuery: String? = null,
|
||||
onChangeSearchQuery: (String?) -> Unit = {},
|
||||
@ -67,9 +69,9 @@ fun TabbedScreen(
|
||||
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||
),
|
||||
) {
|
||||
TabRow(
|
||||
PrimaryTabRow(
|
||||
selectedTabIndex = state.currentPage,
|
||||
indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) },
|
||||
modifier = Modifier.zIndex(1f),
|
||||
) {
|
||||
tabs.forEachIndexed { index, tab ->
|
||||
Tab(
|
||||
@ -96,9 +98,9 @@ fun TabbedScreen(
|
||||
}
|
||||
|
||||
data class TabContent(
|
||||
@StringRes val titleRes: Int,
|
||||
val titleRes: StringResource,
|
||||
val badgeNumber: Int? = null,
|
||||
val searchEnabled: Boolean = false,
|
||||
val actions: List<AppBar.Action> = emptyList(),
|
||||
val actions: ImmutableList<AppBar.AppBarAction> = persistentListOf(),
|
||||
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.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.InfoScreen
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
|
||||
@Composable
|
||||
fun CrashScreen(
|
||||
@ -32,22 +32,22 @@ fun CrashScreen(
|
||||
|
||||
InfoScreen(
|
||||
icon = Icons.Outlined.BugReport,
|
||||
headingText = stringResource(R.string.crash_screen_title),
|
||||
subtitleText = stringResource(R.string.crash_screen_description, stringResource(R.string.app_name)),
|
||||
acceptText = stringResource(R.string.pref_dump_crash_logs),
|
||||
headingText = stringResource(MR.strings.crash_screen_title),
|
||||
subtitleText = stringResource(MR.strings.crash_screen_description, stringResource(MR.strings.app_name)),
|
||||
acceptText = stringResource(MR.strings.pref_dump_crash_logs),
|
||||
onAcceptClick = {
|
||||
scope.launch {
|
||||
CrashLogUtil(context).dumpLogs()
|
||||
}
|
||||
},
|
||||
rejectText = stringResource(R.string.crash_screen_restart_application),
|
||||
rejectText = stringResource(MR.strings.crash_screen_restart_application),
|
||||
onRejectClick = onRestartClick,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = MaterialTheme.padding.small)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.fillMaxWidth()
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
) {
|
||||
Text(
|
||||
@ -60,10 +60,10 @@ fun CrashScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun CrashScreenPreview() {
|
||||
TachiyomiTheme {
|
||||
TachiyomiPreviewTheme {
|
||||
CrashScreen(exception = RuntimeException("Dummy")) {}
|
||||
}
|
||||
}
|
||||
|
@ -8,29 +8,26 @@ import androidx.compose.material.icons.outlined.DeleteSweep
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
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 eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
import eu.kanade.presentation.components.RelativeDateHeader
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.presentation.components.relativeDateText
|
||||
import eu.kanade.presentation.history.components.HistoryItem
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
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.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.ListGroupHeader
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
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
|
||||
|
||||
@Composable
|
||||
@ -41,19 +38,18 @@ fun HistoryScreen(
|
||||
onClickCover: (mangaId: Long) -> Unit,
|
||||
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
||||
preferences: UiPreferences = Injekt.get(),
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
SearchToolbar(
|
||||
titleContent = { AppBarTitle(stringResource(R.string.history)) },
|
||||
titleContent = { AppBarTitle(stringResource(MR.strings.history)) },
|
||||
searchQuery = state.searchQuery,
|
||||
onChangeSearchQuery = onSearchQueryChange,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
listOf(
|
||||
persistentListOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.pref_clear_history),
|
||||
title = stringResource(MR.strings.pref_clear_history),
|
||||
icon = Icons.Outlined.DeleteSweep,
|
||||
onClick = {
|
||||
onDialogChange(HistoryScreenModel.Dialog.DeleteAll)
|
||||
@ -72,12 +68,12 @@ fun HistoryScreen(
|
||||
LoadingScreen(Modifier.padding(contentPadding))
|
||||
} else if (it.isEmpty()) {
|
||||
val msg = if (!state.searchQuery.isNullOrEmpty()) {
|
||||
R.string.no_results_found
|
||||
MR.strings.no_results_found
|
||||
} else {
|
||||
R.string.information_no_recent_manga
|
||||
MR.strings.information_no_recent_manga
|
||||
}
|
||||
EmptyScreen(
|
||||
textResource = msg,
|
||||
stringRes = msg,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
} else {
|
||||
@ -87,7 +83,6 @@ fun HistoryScreen(
|
||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
||||
preferences = preferences,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -101,11 +96,7 @@ private fun HistoryScreenContent(
|
||||
onClickCover: (HistoryWithRelations) -> Unit,
|
||||
onClickResume: (HistoryWithRelations) -> Unit,
|
||||
onClickDelete: (HistoryWithRelations) -> Unit,
|
||||
preferences: UiPreferences,
|
||||
) {
|
||||
val relativeTime = remember { preferences.relativeTime().get() }
|
||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
||||
|
||||
FastScrollLazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
@ -121,11 +112,9 @@ private fun HistoryScreenContent(
|
||||
) { item ->
|
||||
when (item) {
|
||||
is HistoryUiModel.Header -> {
|
||||
RelativeDateHeader(
|
||||
ListGroupHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
date = item.date,
|
||||
relativeTime = relativeTime,
|
||||
dateFormat = dateFormat,
|
||||
text = relativeDateText(item.date),
|
||||
)
|
||||
}
|
||||
is HistoryUiModel.Item -> {
|
||||
@ -148,13 +137,13 @@ sealed interface HistoryUiModel {
|
||||
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
internal fun HistoryScreenPreviews(
|
||||
@PreviewParameter(HistoryScreenModelStateProvider::class)
|
||||
historyState: HistoryScreenModel.State,
|
||||
) {
|
||||
TachiyomiTheme {
|
||||
TachiyomiPreviewTheme {
|
||||
HistoryScreen(
|
||||
state = historyState,
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
@ -162,17 +151,6 @@ internal fun HistoryScreenPreviews(
|
||||
onClickCover = {},
|
||||
onClickResume = { _, _ -> run {} },
|
||||
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.Column
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -10,12 +11,12 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import tachiyomi.i18n.MR
|
||||
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
|
||||
fun HistoryDeleteDialog(
|
||||
@ -26,16 +27,16 @@ fun HistoryDeleteDialog(
|
||||
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_remove))
|
||||
Text(text = stringResource(MR.strings.action_remove))
|
||||
},
|
||||
text = {
|
||||
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(
|
||||
label = stringResource(R.string.dialog_with_checkbox_reset),
|
||||
label = stringResource(MR.strings.dialog_with_checkbox_reset),
|
||||
checked = removeEverything,
|
||||
onCheckedChange = { removeEverything = it },
|
||||
)
|
||||
@ -47,12 +48,12 @@ fun HistoryDeleteDialog(
|
||||
onDelete(removeEverything)
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_remove))
|
||||
Text(text = stringResource(MR.strings.action_remove))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -65,10 +66,10 @@ fun HistoryDeleteAllDialog(
|
||||
) {
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_remove_everything))
|
||||
Text(text = stringResource(MR.strings.action_remove_everything))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.clear_history_confirmation))
|
||||
Text(text = stringResource(MR.strings.clear_history_confirmation))
|
||||
},
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
@ -76,21 +77,21 @@ fun HistoryDeleteAllDialog(
|
||||
onDelete()
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun HistoryDeleteDialogPreview() {
|
||||
TachiyomiTheme {
|
||||
TachiyomiPreviewTheme {
|
||||
HistoryDeleteDialog(
|
||||
onDismissRequest = {},
|
||||
onDelete = {},
|
||||
|
@ -11,34 +11,35 @@ import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
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.unit.dp
|
||||
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.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
private val HistoryItemHeight = 96.dp
|
||||
|
||||
@Composable
|
||||
fun HistoryItem(
|
||||
modifier: Modifier = Modifier,
|
||||
history: HistoryWithRelations,
|
||||
onClickCover: () -> Unit,
|
||||
onClickResume: () -> Unit,
|
||||
onClickDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
@ -69,7 +70,7 @@ fun HistoryItem(
|
||||
Text(
|
||||
text = if (history.chapterNumber > -1) {
|
||||
stringResource(
|
||||
R.string.recent_manga_time,
|
||||
MR.strings.recent_manga_time,
|
||||
formatChapterNumber(history.chapterNumber),
|
||||
readAt,
|
||||
)
|
||||
@ -84,20 +85,21 @@ fun HistoryItem(
|
||||
IconButton(onClick = onClickDelete) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Delete,
|
||||
contentDescription = stringResource(R.string.action_delete),
|
||||
contentDescription = stringResource(MR.strings.action_delete),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun HistoryItemPreviews(
|
||||
@PreviewParameter(HistoryWithRelationsProvider::class)
|
||||
historyWithRelations: HistoryWithRelations,
|
||||
) {
|
||||
TachiyomiTheme {
|
||||
TachiyomiPreviewTheme {
|
||||
Surface {
|
||||
HistoryItem(
|
||||
history = historyWithRelations,
|
||||
onClickCover = {},
|
||||
@ -106,3 +108,4 @@ private fun HistoryItemPreviews(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,11 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.tachiyomi.R
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import tachiyomi.core.preference.CheckboxState
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun DeleteLibraryMangaDialog(
|
||||
@ -22,10 +23,10 @@ fun DeleteLibraryMangaDialog(
|
||||
) {
|
||||
var list by remember {
|
||||
mutableStateOf(
|
||||
buildList<CheckboxState.State<Int>> {
|
||||
add(CheckboxState.State.None(R.string.manga_from_library))
|
||||
buildList<CheckboxState.State<StringResource>> {
|
||||
add(CheckboxState.State.None(MR.strings.manga_from_library))
|
||||
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,
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
@ -48,11 +49,11 @@ fun DeleteLibraryMangaDialog(
|
||||
)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_remove))
|
||||
Text(text = stringResource(MR.strings.action_remove))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
@ -64,7 +65,7 @@ fun DeleteLibraryMangaDialog(
|
||||
val index = list.indexOf(state)
|
||||
if (index != -1) {
|
||||
val mutableList = list.toMutableList()
|
||||
mutableList[index] = state.next() as CheckboxState.State<Int>
|
||||
mutableList[index] = state.next() as CheckboxState.State<StringResource>
|
||||
list = mutableList.toList()
|
||||
}
|
||||
},
|
||||
|
@ -13,23 +13,26 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.domain.category.model.Category
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.library.model.LibrarySort
|
||||
import tachiyomi.domain.library.model.sort
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.HeadingItem
|
||||
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||
import tachiyomi.presentation.core.components.SliderItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TriStateItem
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
|
||||
@Composable
|
||||
@ -40,10 +43,10 @@ fun LibrarySettingsDialog(
|
||||
) {
|
||||
TabbedDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
tabTitles = listOf(
|
||||
stringResource(R.string.action_filter),
|
||||
stringResource(R.string.action_sort),
|
||||
stringResource(R.string.action_display),
|
||||
tabTitles = persistentListOf(
|
||||
stringResource(MR.strings.action_filter),
|
||||
stringResource(MR.strings.action_sort),
|
||||
stringResource(MR.strings.action_display),
|
||||
),
|
||||
) { page ->
|
||||
Column(
|
||||
@ -73,8 +76,10 @@ private fun ColumnScope.FilterPage(
|
||||
) {
|
||||
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState()
|
||||
val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState()
|
||||
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions().collectAsState()
|
||||
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.label_downloaded),
|
||||
label = stringResource(MR.strings.label_downloaded),
|
||||
state = if (downloadedOnly) {
|
||||
TriState.ENABLED_IS
|
||||
} else {
|
||||
@ -85,28 +90,40 @@ private fun ColumnScope.FilterPage(
|
||||
)
|
||||
val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.action_filter_unread),
|
||||
label = stringResource(MR.strings.action_filter_unread),
|
||||
state = filterUnread,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
|
||||
)
|
||||
val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.label_started),
|
||||
label = stringResource(MR.strings.label_started),
|
||||
state = filterStarted,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
|
||||
)
|
||||
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.action_filter_bookmarked),
|
||||
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||
state = filterBookmarked,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
|
||||
)
|
||||
val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.completed),
|
||||
label = stringResource(MR.strings.completed),
|
||||
state = 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 }
|
||||
when (trackers.size) {
|
||||
@ -117,13 +134,13 @@ private fun ColumnScope.FilterPage(
|
||||
val service = trackers[0]
|
||||
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.action_filter_tracked),
|
||||
label = stringResource(MR.strings.action_filter_tracked),
|
||||
state = filterTracker,
|
||||
onClick = { screenModel.toggleTracker(service.id.toInt()) },
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
HeadingItem(R.string.action_filter_tracked)
|
||||
HeadingItem(MR.strings.action_filter_tracked)
|
||||
trackers.map { service ->
|
||||
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||
TriStateItem(
|
||||
@ -148,18 +165,18 @@ private fun ColumnScope.SortPage(
|
||||
if (screenModel.trackers.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
listOf(R.string.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
||||
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
||||
}
|
||||
|
||||
listOf(
|
||||
R.string.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
||||
R.string.action_sort_total to LibrarySort.Type.TotalChapters,
|
||||
R.string.action_sort_last_read to LibrarySort.Type.LastRead,
|
||||
R.string.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
|
||||
R.string.action_sort_unread_count to LibrarySort.Type.UnreadCount,
|
||||
R.string.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
|
||||
R.string.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
|
||||
R.string.action_sort_date_added to LibrarySort.Type.DateAdded,
|
||||
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
||||
MR.strings.action_sort_total to LibrarySort.Type.TotalChapters,
|
||||
MR.strings.action_sort_last_read to LibrarySort.Type.LastRead,
|
||||
MR.strings.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
|
||||
MR.strings.action_sort_unread_count to LibrarySort.Type.UnreadCount,
|
||||
MR.strings.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
|
||||
MR.strings.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
|
||||
MR.strings.action_sort_date_added to LibrarySort.Type.DateAdded,
|
||||
).plus(trackerSortOption).map { (titleRes, mode) ->
|
||||
SortItem(
|
||||
label = stringResource(titleRes),
|
||||
@ -167,8 +184,16 @@ private fun ColumnScope.SortPage(
|
||||
onClick = {
|
||||
val isTogglingDirection = sortingMode == mode
|
||||
val direction = when {
|
||||
isTogglingDirection -> if (sortDescending) LibrarySort.Direction.Ascending else LibrarySort.Direction.Descending
|
||||
else -> if (sortDescending) LibrarySort.Direction.Descending else LibrarySort.Direction.Ascending
|
||||
isTogglingDirection -> if (sortDescending) {
|
||||
LibrarySort.Direction.Ascending
|
||||
} else {
|
||||
LibrarySort.Direction.Descending
|
||||
}
|
||||
else -> if (sortDescending) {
|
||||
LibrarySort.Direction.Descending
|
||||
} else {
|
||||
LibrarySort.Direction.Ascending
|
||||
}
|
||||
}
|
||||
screenModel.setSort(category, mode, direction)
|
||||
},
|
||||
@ -177,10 +202,10 @@ private fun ColumnScope.SortPage(
|
||||
}
|
||||
|
||||
private val displayModes = listOf(
|
||||
R.string.action_display_grid to LibraryDisplayMode.CompactGrid,
|
||||
R.string.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid,
|
||||
R.string.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid,
|
||||
R.string.action_display_list to LibraryDisplayMode.List,
|
||||
MR.strings.action_display_grid to LibraryDisplayMode.CompactGrid,
|
||||
MR.strings.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid,
|
||||
MR.strings.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid,
|
||||
MR.strings.action_display_list to LibraryDisplayMode.List,
|
||||
)
|
||||
|
||||
@Composable
|
||||
@ -188,7 +213,7 @@ private fun ColumnScope.DisplayPage(
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
) {
|
||||
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
|
||||
SettingsChipRow(R.string.action_display_mode) {
|
||||
SettingsChipRow(MR.strings.action_display_mode) {
|
||||
displayModes.map { (titleRes, mode) ->
|
||||
FilterChip(
|
||||
selected = displayMode == mode,
|
||||
@ -210,43 +235,43 @@ private fun ColumnScope.DisplayPage(
|
||||
|
||||
val columns by columnPreference.collectAsState()
|
||||
SliderItem(
|
||||
label = stringResource(R.string.pref_library_columns),
|
||||
label = stringResource(MR.strings.pref_library_columns),
|
||||
max = 10,
|
||||
value = columns,
|
||||
valueText = if (columns > 0) {
|
||||
stringResource(R.string.pref_library_columns_per_row, columns)
|
||||
stringResource(MR.strings.pref_library_columns_per_row, columns)
|
||||
} else {
|
||||
stringResource(R.string.label_default)
|
||||
stringResource(MR.strings.label_default)
|
||||
},
|
||||
onChange = columnPreference::set,
|
||||
)
|
||||
}
|
||||
|
||||
HeadingItem(R.string.overlay_header)
|
||||
HeadingItem(MR.strings.overlay_header)
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.action_display_download_badge),
|
||||
label = stringResource(MR.strings.action_display_download_badge),
|
||||
pref = screenModel.libraryPreferences.downloadBadge(),
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.action_display_local_badge),
|
||||
label = stringResource(MR.strings.action_display_local_badge),
|
||||
pref = screenModel.libraryPreferences.localBadge(),
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.action_display_language_badge),
|
||||
label = stringResource(MR.strings.action_display_language_badge),
|
||||
pref = screenModel.libraryPreferences.languageBadge(),
|
||||
)
|
||||
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(),
|
||||
)
|
||||
|
||||
HeadingItem(R.string.tabs_header)
|
||||
HeadingItem(MR.strings.tabs_header)
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.action_display_show_tabs),
|
||||
label = stringResource(MR.strings.action_display_show_tabs),
|
||||
pref = screenModel.libraryPreferences.categoryTabs(),
|
||||
)
|
||||
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(),
|
||||
)
|
||||
}
|
||||
|
@ -38,7 +38,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.manga.components.MangaCover
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.BadgeGroup
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.selectedBackground
|
||||
|
||||
object CommonMangaItemDefaults {
|
||||
@ -48,7 +50,7 @@ object CommonMangaItemDefaults {
|
||||
const val BrowseFavoriteCoverAlpha = 0.34f
|
||||
}
|
||||
|
||||
private val ContinueReadingButtonSize = 32.dp
|
||||
private val ContinueReadingButtonSize = 28.dp
|
||||
private val ContinueReadingButtonGridPadding = 6.dp
|
||||
private val ContinueReadingButtonListSpacing = 8.dp
|
||||
|
||||
@ -60,15 +62,15 @@ private const val GridSelectedCoverAlpha = 0.76f
|
||||
*/
|
||||
@Composable
|
||||
fun MangaCompactGridItem(
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
isSelected: Boolean = false,
|
||||
title: String? = null,
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
onClickContinueReading: (() -> Unit)? = null,
|
||||
coverAlpha: Float = 1f,
|
||||
coverBadgeStart: @Composable (RowScope.() -> Unit)? = null,
|
||||
coverBadgeEnd: @Composable (RowScope.() -> Unit)? = null,
|
||||
onLongClick: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
onClickContinueReading: (() -> Unit)? = null,
|
||||
) {
|
||||
GridItemSelectable(
|
||||
isSelected = isSelected,
|
||||
@ -161,15 +163,15 @@ private fun BoxScope.CoverTextOverlay(
|
||||
*/
|
||||
@Composable
|
||||
fun MangaComfortableGridItem(
|
||||
isSelected: Boolean = false,
|
||||
title: String,
|
||||
titleMaxLines: Int = 2,
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
title: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
isSelected: Boolean = false,
|
||||
titleMaxLines: Int = 2,
|
||||
coverAlpha: Float = 1f,
|
||||
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
||||
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
|
||||
onLongClick: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
onClickContinueReading: (() -> Unit)? = null,
|
||||
) {
|
||||
GridItemSelectable(
|
||||
@ -251,10 +253,10 @@ private fun MangaGridCover(
|
||||
|
||||
@Composable
|
||||
private fun GridItemTitle(
|
||||
modifier: Modifier,
|
||||
title: String,
|
||||
style: TextStyle,
|
||||
minLines: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = 2,
|
||||
) {
|
||||
Text(
|
||||
@ -274,10 +276,10 @@ private fun GridItemTitle(
|
||||
*/
|
||||
@Composable
|
||||
private fun GridItemSelectable(
|
||||
modifier: Modifier = Modifier,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
@ -314,13 +316,13 @@ private fun Modifier.selectedOutline(
|
||||
*/
|
||||
@Composable
|
||||
fun MangaListItem(
|
||||
isSelected: Boolean = false,
|
||||
title: String,
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
coverAlpha: Float = 1f,
|
||||
badge: @Composable (RowScope.() -> Unit),
|
||||
onLongClick: () -> Unit,
|
||||
title: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
badge: @Composable (RowScope.() -> Unit),
|
||||
isSelected: Boolean = false,
|
||||
coverAlpha: Float = 1f,
|
||||
onClickContinueReading: (() -> Unit)? = null,
|
||||
) {
|
||||
Row(
|
||||
@ -376,7 +378,7 @@ private fun ContinueReadingButton(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = "",
|
||||
contentDescription = stringResource(MR.strings.action_resume),
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.zIndex
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
internal fun GlobalSearchItem(
|
||||
@ -19,7 +19,7 @@ internal fun GlobalSearchItem(
|
||||
onClick = onClick,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.action_global_search_query, searchQuery),
|
||||
text = stringResource(MR.strings.action_global_search_query, searchQuery),
|
||||
modifier = Modifier.zIndex(99f),
|
||||
)
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Folder
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
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.util.ThemePreviews
|
||||
|
||||
@Composable
|
||||
internal fun DownloadsBadge(count: Long) {
|
||||
@ -47,10 +47,10 @@ internal fun LanguageBadge(
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun BadgePreview() {
|
||||
TachiyomiTheme {
|
||||
TachiyomiPreviewTheme {
|
||||
Column {
|
||||
DownloadsBadge(count = 10)
|
||||
UnreadBadge(count = 10)
|
||||
|
@ -93,7 +93,7 @@ fun LibraryContent(
|
||||
isRefreshing = false
|
||||
}
|
||||
},
|
||||
enabled = notSelectionMode,
|
||||
enabled = { notSelectionMode },
|
||||
) {
|
||||
LibraryPager(
|
||||
state = pagerState,
|
||||
|
@ -18,10 +18,10 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.core.preference.PreferenceMutableState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.HorizontalPager
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
@ -124,9 +124,9 @@ private fun LibraryPagerEmptyScreen(
|
||||
onGlobalSearchClicked: () -> Unit,
|
||||
) {
|
||||
val msg = when {
|
||||
!searchQuery.isNullOrEmpty() -> R.string.no_results_found
|
||||
hasActiveFilters -> R.string.error_no_match
|
||||
else -> R.string.information_no_manga_category
|
||||
!searchQuery.isNullOrEmpty() -> MR.strings.no_results_found
|
||||
hasActiveFilters -> MR.strings.error_no_match
|
||||
else -> MR.strings.information_no_manga_category
|
||||
}
|
||||
|
||||
Column(
|
||||
@ -146,7 +146,7 @@ private fun LibraryPagerEmptyScreen(
|
||||
}
|
||||
|
||||
EmptyScreen(
|
||||
textResource = msg,
|
||||
stringRes = msg,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
@ -4,13 +4,14 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
||||
import tachiyomi.presentation.core.components.material.TabText
|
||||
|
||||
@Composable
|
||||
@ -20,11 +21,12 @@ internal fun LibraryTabs(
|
||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||
onTabItemClick: (Int) -> Unit,
|
||||
) {
|
||||
Column {
|
||||
ScrollableTabRow(
|
||||
Column(
|
||||
modifier = Modifier.zIndex(1f),
|
||||
) {
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
edgePadding = 0.dp,
|
||||
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
|
||||
// TODO: use default when width is fixed upstream
|
||||
// https://issuetracker.google.com/issues/242879624
|
||||
divider = {},
|
||||
|
@ -14,14 +14,15 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.Pill
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.theme.active
|
||||
|
||||
@Composable
|
||||
@ -95,23 +96,23 @@ private fun LibraryRegularToolbar(
|
||||
actions = {
|
||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||
AppBarActions(
|
||||
listOf(
|
||||
persistentListOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_filter),
|
||||
title = stringResource(MR.strings.action_filter),
|
||||
icon = Icons.Outlined.FilterList,
|
||||
iconTint = filterTint,
|
||||
onClick = onClickFilter,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_update_library),
|
||||
title = stringResource(MR.strings.action_update_library),
|
||||
onClick = onClickGlobalUpdate,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_update_category),
|
||||
title = stringResource(MR.strings.action_update_category),
|
||||
onClick = onClickRefresh,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_open_random_manga),
|
||||
title = stringResource(MR.strings.action_open_random_manga),
|
||||
onClick = onClickOpenRandomManga,
|
||||
),
|
||||
),
|
||||
@ -132,14 +133,14 @@ private fun LibrarySelectionToolbar(
|
||||
titleContent = { Text(text = "$selectedCount") },
|
||||
actions = {
|
||||
AppBarActions(
|
||||
listOf(
|
||||
persistentListOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_select_all),
|
||||
title = stringResource(MR.strings.action_select_all),
|
||||
icon = Icons.Outlined.SelectAll,
|
||||
onClick = onClickSelectAll,
|
||||
),
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_select_inverse),
|
||||
title = stringResource(MR.strings.action_select_inverse),
|
||||
icon = Icons.Outlined.FlipToBack,
|
||||
onClick = onClickInvertSelection,
|
||||
),
|
||||
|
@ -1,13 +1,21 @@
|
||||
package eu.kanade.presentation.manga
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.PeopleAlt
|
||||
import androidx.compose.material3.AlertDialog
|
||||
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.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -15,20 +23,23 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.manga.model.downloadedFilter
|
||||
import eu.kanade.domain.manga.model.forceDownloaded
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.components.RadioItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TriStateItem
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.theme.active
|
||||
|
||||
@Composable
|
||||
fun ChapterSettingsDialog(
|
||||
@ -37,6 +48,8 @@ fun ChapterSettingsDialog(
|
||||
onDownloadFilterChanged: (TriState) -> Unit,
|
||||
onUnreadFilterChanged: (TriState) -> Unit,
|
||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||
scanlatorFilterActive: Boolean,
|
||||
onScanlatorFilterClicked: (() -> Unit),
|
||||
onSortModeChanged: (Long) -> Unit,
|
||||
onDisplayModeChanged: (Long) -> Unit,
|
||||
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
|
||||
@ -52,21 +65,21 @@ fun ChapterSettingsDialog(
|
||||
|
||||
TabbedDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
tabTitles = listOf(
|
||||
stringResource(R.string.action_filter),
|
||||
stringResource(R.string.action_sort),
|
||||
stringResource(R.string.action_display),
|
||||
tabTitles = persistentListOf(
|
||||
stringResource(MR.strings.action_filter),
|
||||
stringResource(MR.strings.action_sort),
|
||||
stringResource(MR.strings.action_display),
|
||||
),
|
||||
tabOverflowMenuContent = { closeMenu ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.set_chapter_settings_as_default)) },
|
||||
text = { Text(stringResource(MR.strings.set_chapter_settings_as_default)) },
|
||||
onClick = {
|
||||
showSetAsDefaultDialog = true
|
||||
closeMenu()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.action_reset)) },
|
||||
text = { Text(stringResource(MR.strings.action_reset)) },
|
||||
onClick = {
|
||||
onResetToDefault()
|
||||
closeMenu()
|
||||
@ -83,11 +96,14 @@ fun ChapterSettingsDialog(
|
||||
0 -> {
|
||||
FilterPage(
|
||||
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
||||
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true },
|
||||
onDownloadFilterChanged = onDownloadFilterChanged
|
||||
.takeUnless { manga?.forceDownloaded() == true },
|
||||
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
||||
onUnreadFilterChanged = onUnreadFilterChanged,
|
||||
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
||||
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
|
||||
scanlatorFilterActive = scanlatorFilterActive,
|
||||
onScanlatorFilterClicked = onScanlatorFilterClicked,
|
||||
)
|
||||
}
|
||||
1 -> {
|
||||
@ -116,22 +132,57 @@ private fun ColumnScope.FilterPage(
|
||||
onUnreadFilterChanged: (TriState) -> Unit,
|
||||
bookmarkedFilter: TriState,
|
||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||
scanlatorFilterActive: Boolean,
|
||||
onScanlatorFilterClicked: (() -> Unit),
|
||||
) {
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.label_downloaded),
|
||||
label = stringResource(MR.strings.label_downloaded),
|
||||
state = downloadFilter,
|
||||
onClick = onDownloadFilterChanged,
|
||||
)
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.action_filter_unread),
|
||||
label = stringResource(MR.strings.action_filter_unread),
|
||||
state = unreadFilter,
|
||||
onClick = onUnreadFilterChanged,
|
||||
)
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.action_filter_bookmarked),
|
||||
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||
state = bookmarkedFilter,
|
||||
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
|
||||
@ -141,10 +192,10 @@ private fun ColumnScope.SortPage(
|
||||
onItemSelected: (Long) -> Unit,
|
||||
) {
|
||||
listOf(
|
||||
R.string.sort_by_source to Manga.CHAPTER_SORTING_SOURCE,
|
||||
R.string.sort_by_number to Manga.CHAPTER_SORTING_NUMBER,
|
||||
R.string.sort_by_upload_date to Manga.CHAPTER_SORTING_UPLOAD_DATE,
|
||||
R.string.action_sort_alpha to Manga.CHAPTER_SORTING_ALPHABET,
|
||||
MR.strings.sort_by_source to Manga.CHAPTER_SORTING_SOURCE,
|
||||
MR.strings.sort_by_number to Manga.CHAPTER_SORTING_NUMBER,
|
||||
MR.strings.sort_by_upload_date to Manga.CHAPTER_SORTING_UPLOAD_DATE,
|
||||
MR.strings.action_sort_alpha to Manga.CHAPTER_SORTING_ALPHABET,
|
||||
).map { (titleRes, mode) ->
|
||||
SortItem(
|
||||
label = stringResource(titleRes),
|
||||
@ -160,8 +211,8 @@ private fun ColumnScope.DisplayPage(
|
||||
onItemSelected: (Long) -> Unit,
|
||||
) {
|
||||
listOf(
|
||||
R.string.show_title to Manga.CHAPTER_DISPLAY_NAME,
|
||||
R.string.show_chapter_number to Manga.CHAPTER_DISPLAY_NUMBER,
|
||||
MR.strings.show_title to Manga.CHAPTER_DISPLAY_NAME,
|
||||
MR.strings.show_chapter_number to Manga.CHAPTER_DISPLAY_NUMBER,
|
||||
).map { (titleRes, mode) ->
|
||||
RadioItem(
|
||||
label = stringResource(titleRes),
|
||||
@ -180,15 +231,15 @@ private fun SetAsDefaultDialog(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(text = stringResource(R.string.chapter_settings)) },
|
||||
title = { Text(text = stringResource(MR.strings.chapter_settings)) },
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(text = stringResource(R.string.confirm_set_chapter_settings))
|
||||
Text(text = stringResource(MR.strings.confirm_set_chapter_settings))
|
||||
|
||||
LabeledCheckbox(
|
||||
label = stringResource(R.string.also_set_chapter_settings_for_library),
|
||||
label = stringResource(MR.strings.also_set_chapter_settings_for_library),
|
||||
checked = optionalChecked,
|
||||
onCheckedChange = { optionalChecked = it },
|
||||
)
|
||||
@ -196,7 +247,7 @@ private fun SetAsDefaultDialog(
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
@ -206,7 +257,7 @@ private fun SetAsDefaultDialog(
|
||||
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.Spacer
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun DuplicateMangaDialog(
|
||||
@ -21,14 +22,14 @@ fun DuplicateMangaDialog(
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = {
|
||||
Text(text = stringResource(R.string.are_you_sure))
|
||||
Text(text = stringResource(MR.strings.are_you_sure))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
|
||||
Text(text = stringResource(MR.strings.confirm_add_duplicate_manga))
|
||||
},
|
||||
confirmButton = {
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
@ -36,13 +37,13 @@ fun DuplicateMangaDialog(
|
||||
onOpenManga()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_show_manga))
|
||||
Text(text = stringResource(MR.strings.action_show_manga))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
@ -50,7 +51,7 @@ fun DuplicateMangaDialog(
|
||||
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.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
@ -28,9 +26,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
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.LocalHapticFeedback
|
||||
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.fastAny
|
||||
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.ChapterHeader
|
||||
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.MangaInfoBox
|
||||
import eu.kanade.presentation.manga.components.MangaToolbar
|
||||
import eu.kanade.presentation.manga.components.MissingChapterCountListItem
|
||||
import eu.kanade.presentation.util.formatChapterNumber
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.service.missingChaptersCount
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||
import tachiyomi.presentation.core.components.VerticalFastScroller
|
||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
import tachiyomi.presentation.core.components.material.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.isScrollingUp
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.time.Instant
|
||||
|
||||
@Composable
|
||||
fun MangaScreen(
|
||||
state: MangaScreenModel.State.Success,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
fetchInterval: Int?,
|
||||
dateRelativeTime: Boolean,
|
||||
dateFormat: DateFormat,
|
||||
nextUpdate: Instant?,
|
||||
isTabletUi: Boolean,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
@ -103,7 +94,7 @@ fun MangaScreen(
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onTrackingClicked: () -> Unit,
|
||||
|
||||
// For tags menu
|
||||
onTagSearch: (String) -> Unit,
|
||||
@ -148,9 +139,7 @@ fun MangaScreen(
|
||||
MangaScreenSmallImpl(
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
dateRelativeTime = dateRelativeTime,
|
||||
dateFormat = dateFormat,
|
||||
fetchInterval = fetchInterval,
|
||||
nextUpdate = nextUpdate,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
onBackClicked = onBackClicked,
|
||||
@ -185,11 +174,9 @@ fun MangaScreen(
|
||||
MangaScreenLargeImpl(
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
dateRelativeTime = dateRelativeTime,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
dateFormat = dateFormat,
|
||||
fetchInterval = fetchInterval,
|
||||
nextUpdate = nextUpdate,
|
||||
onBackClicked = onBackClicked,
|
||||
onChapterClicked = onChapterClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
@ -225,9 +212,7 @@ fun MangaScreen(
|
||||
private fun MangaScreenSmallImpl(
|
||||
state: MangaScreenModel.State.Success,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
dateRelativeTime: Boolean,
|
||||
dateFormat: DateFormat,
|
||||
fetchInterval: Int?,
|
||||
nextUpdate: Instant?,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onBackClicked: () -> Unit,
|
||||
@ -236,7 +221,7 @@ private fun MangaScreenSmallImpl(
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onTrackingClicked: () -> Unit,
|
||||
|
||||
// For tags menu
|
||||
onTagSearch: (String) -> Unit,
|
||||
@ -273,13 +258,12 @@ private fun MangaScreenSmallImpl(
|
||||
) {
|
||||
val chapterListState = rememberLazyListState()
|
||||
|
||||
val chapters = remember(state) { state.processedChapters }
|
||||
val listItem = remember(state) { state.chapterListItems }
|
||||
|
||||
val isAnySelected by remember {
|
||||
derivedStateOf {
|
||||
chapters.fastAny { it.selected }
|
||||
}
|
||||
val (chapters, listItem, isAnySelected) = remember(state) {
|
||||
Triple(
|
||||
first = state.processedChapters,
|
||||
second = state.chapterListItems,
|
||||
third = state.isAnySelected,
|
||||
)
|
||||
}
|
||||
|
||||
val internalOnBackPressed = {
|
||||
@ -314,7 +298,7 @@ private fun MangaScreenSmallImpl(
|
||||
title = state.manga.title,
|
||||
titleAlphaProvider = { animatedTitleAlpha },
|
||||
backgroundAlphaProvider = { animatedBgAlpha },
|
||||
hasFilters = state.manga.chaptersFiltered(),
|
||||
hasFilters = state.filterActive,
|
||||
onBackClicked = internalOnBackPressed,
|
||||
onClickFilter = onFilterClicked,
|
||||
onClickShare = onShareClicked,
|
||||
@ -356,7 +340,9 @@ private fun MangaScreenSmallImpl(
|
||||
val isReading = remember(state.chapters) {
|
||||
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) },
|
||||
onClick = onContinueReading,
|
||||
@ -370,8 +356,8 @@ private fun MangaScreenSmallImpl(
|
||||
PullRefresh(
|
||||
refreshing = state.isRefreshingData,
|
||||
onRefresh = onRefresh,
|
||||
enabled = !isAnySelected,
|
||||
indicatorPadding = WindowInsets.systemBars.only(WindowInsetsSides.Top).asPaddingValues(),
|
||||
enabled = { !isAnySelected },
|
||||
indicatorPadding = PaddingValues(top = topPadding),
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
VerticalFastScroller(
|
||||
@ -414,7 +400,7 @@ private fun MangaScreenSmallImpl(
|
||||
MangaActionRow(
|
||||
favorite = state.manga.favorite,
|
||||
trackingCount = state.trackingCount,
|
||||
fetchInterval = fetchInterval,
|
||||
nextUpdate = nextUpdate,
|
||||
isUserIntervalMode = state.manga.fetchInterval < 0,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
@ -457,8 +443,6 @@ private fun MangaScreenSmallImpl(
|
||||
manga = state.manga,
|
||||
chapters = listItem,
|
||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||
dateRelativeTime = dateRelativeTime,
|
||||
dateFormat = dateFormat,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
onChapterClicked = onChapterClicked,
|
||||
@ -476,9 +460,7 @@ private fun MangaScreenSmallImpl(
|
||||
fun MangaScreenLargeImpl(
|
||||
state: MangaScreenModel.State.Success,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
dateRelativeTime: Boolean,
|
||||
dateFormat: DateFormat,
|
||||
fetchInterval: Int?,
|
||||
nextUpdate: Instant?,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onBackClicked: () -> Unit,
|
||||
@ -487,7 +469,7 @@ fun MangaScreenLargeImpl(
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onTrackingClicked: () -> Unit,
|
||||
|
||||
// For tags menu
|
||||
onTagSearch: (String) -> Unit,
|
||||
@ -525,27 +507,17 @@ fun MangaScreenLargeImpl(
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val density = LocalDensity.current
|
||||
|
||||
val chapters = remember(state) { state.processedChapters }
|
||||
val listItem = remember(state) { state.chapterListItems }
|
||||
|
||||
val isAnySelected by remember {
|
||||
derivedStateOf {
|
||||
chapters.fastAny { it.selected }
|
||||
}
|
||||
val (chapters, listItem, isAnySelected) = remember(state) {
|
||||
Triple(
|
||||
first = state.processedChapters,
|
||||
second = state.chapterListItems,
|
||||
third = state.isAnySelected,
|
||||
)
|
||||
}
|
||||
|
||||
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||
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 internalOnBackPressed = {
|
||||
@ -567,7 +539,7 @@ fun MangaScreenLargeImpl(
|
||||
title = state.manga.title,
|
||||
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
|
||||
backgroundAlphaProvider = { 1f },
|
||||
hasFilters = state.manga.chaptersFiltered(),
|
||||
hasFilters = state.filterActive,
|
||||
onBackClicked = internalOnBackPressed,
|
||||
onClickFilter = onFilterButtonClicked,
|
||||
onClickShare = onShareClicked,
|
||||
@ -614,7 +586,11 @@ fun MangaScreenLargeImpl(
|
||||
val isReading = remember(state.chapters) {
|
||||
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) },
|
||||
onClick = onContinueReading,
|
||||
@ -623,6 +599,16 @@ fun MangaScreenLargeImpl(
|
||||
}
|
||||
},
|
||||
) { 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(
|
||||
modifier = Modifier.padding(
|
||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||
@ -650,7 +636,7 @@ fun MangaScreenLargeImpl(
|
||||
MangaActionRow(
|
||||
favorite = state.manga.favorite,
|
||||
trackingCount = state.trackingCount,
|
||||
fetchInterval = fetchInterval,
|
||||
nextUpdate = nextUpdate,
|
||||
isUserIntervalMode = state.manga.fetchInterval < 0,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
@ -700,8 +686,6 @@ fun MangaScreenLargeImpl(
|
||||
manga = state.manga,
|
||||
chapters = listItem,
|
||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||
dateRelativeTime = dateRelativeTime,
|
||||
dateFormat = dateFormat,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
onChapterClicked = onChapterClicked,
|
||||
@ -720,13 +704,13 @@ fun MangaScreenLargeImpl(
|
||||
@Composable
|
||||
private fun SharedMangaBottomActionMenu(
|
||||
selected: List<ChapterList.Item>,
|
||||
modifier: Modifier = Modifier,
|
||||
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
||||
onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
|
||||
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||
fillFraction: Float,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MangaBottomActionMenu(
|
||||
visible = selected.isNotEmpty(),
|
||||
@ -754,7 +738,7 @@ private fun SharedMangaBottomActionMenu(
|
||||
onDeleteClicked = {
|
||||
onMultiDeleteClicked(selected.fastMap { it.chapter })
|
||||
}.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,
|
||||
chapters: List<ChapterList>,
|
||||
isAnyChapterSelected: Boolean,
|
||||
dateRelativeTime: Boolean,
|
||||
dateFormat: DateFormat,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
@ -783,54 +765,27 @@ private fun LazyListScope.sharedChapterItems(
|
||||
contentType = { MangaScreenItem.CHAPTER },
|
||||
) { item ->
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val context = LocalContext.current
|
||||
|
||||
when (item) {
|
||||
is ChapterList.MissingCount -> {
|
||||
Row(
|
||||
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))
|
||||
}
|
||||
MissingChapterCountListItem(count = item.count)
|
||||
}
|
||||
is ChapterList.Item -> {
|
||||
MangaChapterListItem(
|
||||
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
||||
stringResource(
|
||||
R.string.display_mode_chapter,
|
||||
MR.strings.display_mode_chapter,
|
||||
formatChapterNumber(item.chapter.chapterNumber),
|
||||
)
|
||||
} else {
|
||||
item.chapter.name
|
||||
},
|
||||
date = item.chapter.dateUpload
|
||||
.takeIf { it > 0L }
|
||||
?.let {
|
||||
Date(it).toRelativeString(
|
||||
context,
|
||||
dateRelativeTime,
|
||||
dateFormat,
|
||||
)
|
||||
},
|
||||
date = relativeDateText(item.chapter.dateUpload),
|
||||
readProgress = item.chapter.lastPageRead
|
||||
.takeIf { !item.chapter.read && it > 0L }
|
||||
?.let {
|
||||
stringResource(
|
||||
R.string.chapter_progress,
|
||||
MR.strings.chapter_progress,
|
||||
it + 1,
|
||||
)
|
||||
},
|
||||
@ -838,7 +793,7 @@ private fun LazyListScope.sharedChapterItems(
|
||||
read = item.chapter.read,
|
||||
bookmark = item.chapter.bookmark,
|
||||
selected = item.selected,
|
||||
downloadIndicatorEnabled = !isAnyChapterSelected,
|
||||
downloadIndicatorEnabled = !isAnyChapterSelected && !manga.isLocal(),
|
||||
downloadStateProvider = { item.downloadState },
|
||||
downloadProgressProvider = { item.downloadProgress },
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
|
@ -19,8 +19,8 @@ import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
fun BaseMangaListItem(
|
||||
modifier: Modifier = Modifier,
|
||||
manga: Manga,
|
||||
modifier: Modifier = Modifier,
|
||||
onClickItem: () -> Unit = {},
|
||||
onClickCover: () -> Unit = onClickItem,
|
||||
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.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.IconButtonTokens
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
|
||||
enum class ChapterDownloadAction {
|
||||
@ -48,10 +49,10 @@ enum class ChapterDownloadAction {
|
||||
@Composable
|
||||
fun ChapterDownloadIndicator(
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
downloadStateProvider: () -> Download.State,
|
||||
downloadProgressProvider: () -> Int,
|
||||
onClick: (ChapterDownloadAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (val downloadState = downloadStateProvider()) {
|
||||
Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(
|
||||
@ -98,7 +99,7 @@ private fun NotDownloadedIndicator(
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_download_chapter_24dp),
|
||||
contentDescription = stringResource(R.string.manga_download),
|
||||
contentDescription = stringResource(MR.strings.manga_download),
|
||||
modifier = Modifier.size(IndicatorSize),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@ -108,10 +109,10 @@ private fun NotDownloadedIndicator(
|
||||
@Composable
|
||||
private fun DownloadingIndicator(
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
downloadState: Download.State,
|
||||
downloadProgressProvider: () -> Int,
|
||||
onClick: (ChapterDownloadAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
Box(
|
||||
@ -148,7 +149,7 @@ private fun DownloadingIndicator(
|
||||
MaterialTheme.colorScheme.background
|
||||
}
|
||||
CircularProgressIndicator(
|
||||
progress = animatedProgress,
|
||||
progress = { animatedProgress },
|
||||
modifier = IndicatorModifier,
|
||||
color = strokeColor,
|
||||
strokeWidth = IndicatorSize / 2,
|
||||
@ -156,14 +157,14 @@ private fun DownloadingIndicator(
|
||||
}
|
||||
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_start_downloading_now)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_start_downloading_now)) },
|
||||
onClick = {
|
||||
onClick(ChapterDownloadAction.START_NOW)
|
||||
isMenuExpanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_cancel)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_cancel)) },
|
||||
onClick = {
|
||||
onClick(ChapterDownloadAction.CANCEL)
|
||||
isMenuExpanded = false
|
||||
@ -204,7 +205,7 @@ private fun DownloadedIndicator(
|
||||
)
|
||||
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_delete)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_delete)) },
|
||||
onClick = {
|
||||
onClick(ChapterDownloadAction.DELETE)
|
||||
isMenuExpanded = false
|
||||
@ -232,7 +233,7 @@ private fun ErrorIndicator(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.ErrorOutline,
|
||||
contentDescription = stringResource(R.string.chapter_error),
|
||||
contentDescription = stringResource(MR.strings.chapter_error),
|
||||
modifier = Modifier.size(IndicatorSize),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
|
@ -9,12 +9,13 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.unit.dp
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.i18n.MR
|
||||
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
|
||||
fun ChapterHeader(
|
||||
@ -22,22 +23,23 @@ fun ChapterHeader(
|
||||
chapterCount: Int?,
|
||||
missingChapterCount: Int,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
) {
|
||||
Text(
|
||||
text = if (chapterCount == null) {
|
||||
stringResource(R.string.chapters)
|
||||
stringResource(MR.strings.chapters)
|
||||
} else {
|
||||
pluralStringResource(id = R.plurals.manga_num_chapters, count = chapterCount, chapterCount)
|
||||
pluralStringResource(MR.plurals.manga_num_chapters, count = chapterCount, chapterCount)
|
||||
},
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
@ -54,7 +56,7 @@ private fun MissingChaptersWarning(count: Int) {
|
||||
}
|
||||
|
||||
Text(
|
||||
text = pluralStringResource(id = R.plurals.missing_chapters, count = count, count),
|
||||
text = pluralStringResource(MR.plurals.missing_chapters, count = count, count),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
|
@ -2,13 +2,24 @@ package eu.kanade.presentation.manga.components
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun DotSeparatorText() {
|
||||
Text(text = " • ")
|
||||
fun DotSeparatorText(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
text = " • ",
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotSeparatorNoSpaceText() {
|
||||
Text(text = "•")
|
||||
fun DotSeparatorNoSpaceText(
|
||||
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.shape.ZeroCornerSize
|
||||
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.BookmarkRemove
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.DoneAll
|
||||
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.ripple.rememberRipple
|
||||
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.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -58,6 +57,8 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
@ -106,7 +107,7 @@ fun MangaBottomActionMenu(
|
||||
) {
|
||||
if (onBookmarkClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_bookmark),
|
||||
title = stringResource(MR.strings.action_bookmark),
|
||||
icon = Icons.Outlined.BookmarkAdd,
|
||||
toConfirm = confirm[0],
|
||||
onLongClick = { onLongClickItem(0) },
|
||||
@ -115,7 +116,7 @@ fun MangaBottomActionMenu(
|
||||
}
|
||||
if (onRemoveBookmarkClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_remove_bookmark),
|
||||
title = stringResource(MR.strings.action_remove_bookmark),
|
||||
icon = Icons.Outlined.BookmarkRemove,
|
||||
toConfirm = confirm[1],
|
||||
onLongClick = { onLongClickItem(1) },
|
||||
@ -124,7 +125,7 @@ fun MangaBottomActionMenu(
|
||||
}
|
||||
if (onMarkAsReadClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_mark_as_read),
|
||||
title = stringResource(MR.strings.action_mark_as_read),
|
||||
icon = Icons.Outlined.DoneAll,
|
||||
toConfirm = confirm[2],
|
||||
onLongClick = { onLongClickItem(2) },
|
||||
@ -133,7 +134,7 @@ fun MangaBottomActionMenu(
|
||||
}
|
||||
if (onMarkAsUnreadClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_mark_as_unread),
|
||||
title = stringResource(MR.strings.action_mark_as_unread),
|
||||
icon = Icons.Outlined.RemoveDone,
|
||||
toConfirm = confirm[3],
|
||||
onLongClick = { onLongClickItem(3) },
|
||||
@ -142,7 +143,7 @@ fun MangaBottomActionMenu(
|
||||
}
|
||||
if (onMarkPreviousAsReadClicked != null) {
|
||||
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),
|
||||
toConfirm = confirm[4],
|
||||
onLongClick = { onLongClickItem(4) },
|
||||
@ -151,7 +152,7 @@ fun MangaBottomActionMenu(
|
||||
}
|
||||
if (onDownloadClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_download),
|
||||
title = stringResource(MR.strings.action_download),
|
||||
icon = Icons.Outlined.Download,
|
||||
toConfirm = confirm[5],
|
||||
onLongClick = { onLongClickItem(5) },
|
||||
@ -160,7 +161,7 @@ fun MangaBottomActionMenu(
|
||||
}
|
||||
if (onDeleteClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_delete),
|
||||
title = stringResource(MR.strings.action_delete),
|
||||
icon = Icons.Outlined.Delete,
|
||||
toConfirm = confirm[6],
|
||||
onLongClick = { onLongClickItem(6) },
|
||||
@ -181,7 +182,10 @@ private fun RowScope.Button(
|
||||
onClick: () -> Unit,
|
||||
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(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
@ -218,12 +222,12 @@ private fun RowScope.Button(
|
||||
@Composable
|
||||
fun LibraryBottomActionMenu(
|
||||
visible: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onChangeCategoryClicked: () -> Unit,
|
||||
onMarkAsReadClicked: () -> Unit,
|
||||
onMarkAsUnreadClicked: () -> Unit,
|
||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onDeleteClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
@ -257,21 +261,21 @@ fun LibraryBottomActionMenu(
|
||||
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||
) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_move_category),
|
||||
icon = Icons.Outlined.Label,
|
||||
title = stringResource(MR.strings.action_move_category),
|
||||
icon = Icons.AutoMirrored.Outlined.Label,
|
||||
toConfirm = confirm[0],
|
||||
onLongClick = { onLongClickItem(0) },
|
||||
onClick = onChangeCategoryClicked,
|
||||
)
|
||||
Button(
|
||||
title = stringResource(R.string.action_mark_as_read),
|
||||
title = stringResource(MR.strings.action_mark_as_read),
|
||||
icon = Icons.Outlined.DoneAll,
|
||||
toConfirm = confirm[1],
|
||||
onLongClick = { onLongClickItem(1) },
|
||||
onClick = onMarkAsReadClicked,
|
||||
)
|
||||
Button(
|
||||
title = stringResource(R.string.action_mark_as_unread),
|
||||
title = stringResource(MR.strings.action_mark_as_unread),
|
||||
icon = Icons.Outlined.RemoveDone,
|
||||
toConfirm = confirm[2],
|
||||
onLongClick = { onLongClickItem(2) },
|
||||
@ -280,7 +284,7 @@ fun LibraryBottomActionMenu(
|
||||
if (onDownloadClicked != null) {
|
||||
var downloadExpanded by remember { mutableStateOf(false) }
|
||||
Button(
|
||||
title = stringResource(R.string.action_download),
|
||||
title = stringResource(MR.strings.action_download),
|
||||
icon = Icons.Outlined.Download,
|
||||
toConfirm = confirm[3],
|
||||
onLongClick = { onLongClickItem(3) },
|
||||
@ -295,7 +299,7 @@ fun LibraryBottomActionMenu(
|
||||
}
|
||||
}
|
||||
Button(
|
||||
title = stringResource(R.string.action_delete),
|
||||
title = stringResource(MR.strings.action_delete),
|
||||
icon = Icons.Outlined.Delete,
|
||||
toConfirm = confirm[4],
|
||||
onLongClick = { onLongClickItem(4) },
|
||||
|
@ -33,7 +33,6 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.LocalViewConfiguration
|
||||
import androidx.compose.ui.platform.ViewConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import me.saket.swipe.SwipeableActionsBox
|
||||
import me.saket.swipe.rememberSwipeableActionsState
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.selectedBackground
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@Composable
|
||||
fun MangaChapterListItem(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
date: String?,
|
||||
readProgress: String?,
|
||||
@ -75,6 +73,7 @@ fun MangaChapterListItem(
|
||||
onClick: () -> Unit,
|
||||
onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
|
||||
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val density = LocalDensity.current
|
||||
@ -143,7 +142,7 @@ fun MangaChapterListItem(
|
||||
if (!read) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Circle,
|
||||
contentDescription = stringResource(R.string.unread),
|
||||
contentDescription = stringResource(MR.strings.unread),
|
||||
modifier = Modifier
|
||||
.height(8.dp)
|
||||
.padding(end = 4.dp),
|
||||
@ -153,7 +152,7 @@ fun MangaChapterListItem(
|
||||
if (bookmark) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Bookmark,
|
||||
contentDescription = stringResource(R.string.action_filter_bookmarked),
|
||||
contentDescription = stringResource(MR.strings.action_filter_bookmarked),
|
||||
modifier = Modifier
|
||||
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
@ -189,7 +188,7 @@ fun MangaChapterListItem(
|
||||
text = readProgress,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.alpha(ReadItemAlpha),
|
||||
color = LocalContentColor.current.copy(alpha = ReadItemAlpha),
|
||||
)
|
||||
if (scanlator != null) DotSeparatorText()
|
||||
}
|
||||
@ -204,19 +203,17 @@ fun MangaChapterListItem(
|
||||
}
|
||||
}
|
||||
|
||||
if (onDownloadClick != null) {
|
||||
ChapterDownloadIndicator(
|
||||
enabled = downloadIndicatorEnabled,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
downloadStateProvider = downloadStateProvider,
|
||||
downloadProgressProvider = downloadProgressProvider,
|
||||
onClick = onDownloadClick,
|
||||
onClick = { onDownloadClick?.invoke(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSwipeAction(
|
||||
action: LibraryPreferences.ChapterSwipeAction,
|
||||
|
@ -22,8 +22,8 @@ enum class MangaCover(val ratio: Float) {
|
||||
|
||||
@Composable
|
||||
operator fun invoke(
|
||||
modifier: Modifier = Modifier,
|
||||
data: Any?,
|
||||
modifier: Modifier = Modifier,
|
||||
contentDescription: String = "",
|
||||
shape: Shape = MaterialTheme.shapes.extraSmall,
|
||||
onClick: (() -> Unit)? = null,
|
||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.manga.components
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.DropdownMenu
|
||||
import eu.kanade.presentation.manga.EditCoverAction
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||
|
||||
@Composable
|
||||
@ -83,29 +85,25 @@ fun MangaCoverDialog(
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = stringResource(R.string.action_close),
|
||||
contentDescription = stringResource(MR.strings.action_close),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
ActionsPill {
|
||||
AppBarActions(
|
||||
actions = buildList {
|
||||
add(
|
||||
actions = persistentListOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_share),
|
||||
title = stringResource(MR.strings.action_share),
|
||||
icon = Icons.Outlined.Share,
|
||||
onClick = onShareClick,
|
||||
),
|
||||
)
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_save),
|
||||
title = stringResource(MR.strings.action_save),
|
||||
icon = Icons.Outlined.Save,
|
||||
onClick = onSaveClick,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
if (onEditClick != null) {
|
||||
Box {
|
||||
@ -121,7 +119,7 @@ fun MangaCoverDialog(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Edit,
|
||||
contentDescription = stringResource(R.string.action_edit_cover),
|
||||
contentDescription = stringResource(MR.strings.action_edit_cover),
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
@ -130,14 +128,14 @@ fun MangaCoverDialog(
|
||||
offset = DpOffset(8.dp, 0.dp),
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_edit)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_edit)) },
|
||||
onClick = {
|
||||
onEditClick(EditCoverAction.EDIT)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_delete)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_delete)) },
|
||||
onClick = {
|
||||
onEditClick(EditCoverAction.DELETE)
|
||||
expanded = false
|
||||
@ -175,9 +173,14 @@ fun MangaCoverDialog(
|
||||
// Copy bitmap in case it came from memory cache
|
||||
// Because SSIV needs to thoroughly read the image
|
||||
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(
|
||||
view.context.resources,
|
||||
it.bitmap.copy(Bitmap.Config.HARDWARE, false),
|
||||
it.bitmap.copy(config, false),
|
||||
)
|
||||
} ?: drawable
|
||||
view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
|
||||
|
@ -1,23 +1,36 @@
|
||||
package eu.kanade.presentation.manga.components
|
||||
|
||||
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.height
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
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.i18n.MR
|
||||
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
|
||||
fun DeleteChaptersDialog(
|
||||
@ -28,7 +41,7 @@ fun DeleteChaptersDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
@ -38,14 +51,14 @@ fun DeleteChaptersDialog(
|
||||
onConfirm()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.are_you_sure))
|
||||
Text(text = stringResource(MR.strings.are_you_sure))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.confirm_delete_chapters))
|
||||
Text(text = stringResource(MR.strings.confirm_delete_chapters))
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -53,46 +66,84 @@ fun DeleteChaptersDialog(
|
||||
@Composable
|
||||
fun SetIntervalDialog(
|
||||
interval: Int,
|
||||
nextUpdate: Instant?,
|
||||
onDismissRequest: () -> Unit,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
onValueChanged: ((Int) -> Unit)? = null,
|
||||
) {
|
||||
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(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(text = stringResource(R.string.manga_modify_calculated_interval_title)) },
|
||||
title = { Text(stringResource(MR.strings.pref_library_update_smart_update)) },
|
||||
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(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
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) {
|
||||
stringResource(R.string.label_default)
|
||||
stringResource(MR.strings.label_default)
|
||||
} else {
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
WheelTextPicker(
|
||||
size = size,
|
||||
items = items,
|
||||
size = size,
|
||||
startIndex = selectedInterval,
|
||||
onSelectionChanged = { selectedInterval = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onValueChanged(selectedInterval)
|
||||
onValueChanged?.invoke(selectedInterval)
|
||||
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.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.Layout
|
||||
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.TextOverflow
|
||||
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.util.system.copyToClipboard
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.TextButton
|
||||
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.secondaryItemAlpha
|
||||
import kotlin.math.absoluteValue
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
|
||||
|
||||
@Composable
|
||||
fun MangaInfoBox(
|
||||
modifier: Modifier = Modifier,
|
||||
isTabletUi: Boolean,
|
||||
appBarPadding: Dp,
|
||||
title: String,
|
||||
@ -103,6 +105,7 @@ fun MangaInfoBox(
|
||||
status: Long,
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
// Backdrop
|
||||
@ -123,7 +126,7 @@ fun MangaInfoBox(
|
||||
)
|
||||
}
|
||||
.blur(4.dp)
|
||||
.alpha(.2f),
|
||||
.alpha(0.2f),
|
||||
)
|
||||
|
||||
// Manga & source info
|
||||
@ -161,55 +164,69 @@ fun MangaInfoBox(
|
||||
|
||||
@Composable
|
||||
fun MangaActionRow(
|
||||
modifier: Modifier = Modifier,
|
||||
favorite: Boolean,
|
||||
trackingCount: Int,
|
||||
fetchInterval: Int?,
|
||||
nextUpdate: Instant?,
|
||||
isUserIntervalMode: Boolean,
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onTrackingClicked: () -> Unit,
|
||||
onEditIntervalClicked: (() -> Unit)?,
|
||||
onEditCategory: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
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)) {
|
||||
MangaActionButton(
|
||||
title = if (favorite) {
|
||||
stringResource(R.string.in_library)
|
||||
stringResource(MR.strings.in_library)
|
||||
} else {
|
||||
stringResource(R.string.add_to_library)
|
||||
stringResource(MR.strings.add_to_library)
|
||||
},
|
||||
icon = if (favorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
|
||||
color = if (favorite) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||
onClick = onAddToLibraryClicked,
|
||||
onLongClick = onEditCategory,
|
||||
)
|
||||
if (onEditIntervalClicked != null && fetchInterval != null) {
|
||||
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,
|
||||
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||
onClick = onEditIntervalClicked,
|
||||
onClick = { onEditIntervalClicked?.invoke() },
|
||||
)
|
||||
}
|
||||
if (onTrackingClicked != null) {
|
||||
MangaActionButton(
|
||||
title = if (trackingCount == 0) {
|
||||
stringResource(R.string.manga_tracking_tab)
|
||||
stringResource(MR.strings.manga_tracking_tab)
|
||||
} 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,
|
||||
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
||||
onClick = onTrackingClicked,
|
||||
)
|
||||
}
|
||||
if (onWebViewClicked != null) {
|
||||
MangaActionButton(
|
||||
title = stringResource(R.string.action_web_view),
|
||||
title = stringResource(MR.strings.action_web_view),
|
||||
icon = Icons.Outlined.Public,
|
||||
color = defaultActionButtonColor,
|
||||
onClick = onWebViewClicked,
|
||||
@ -221,19 +238,19 @@ fun MangaActionRow(
|
||||
|
||||
@Composable
|
||||
fun ExpandableMangaDescription(
|
||||
modifier: Modifier = Modifier,
|
||||
defaultExpandState: Boolean,
|
||||
description: String?,
|
||||
tagsProvider: () -> List<String>?,
|
||||
onTagSearch: (String) -> Unit,
|
||||
onCopyTagToClipboard: (tag: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
val (expanded, onExpanded) = rememberSaveable {
|
||||
mutableStateOf(defaultExpandState)
|
||||
}
|
||||
val desc =
|
||||
description.takeIf { !it.isNullOrBlank() } ?: stringResource(R.string.description_placeholder)
|
||||
description.takeIf { !it.isNullOrBlank() } ?: stringResource(MR.strings.description_placeholder)
|
||||
val trimmedDescription = remember(desc) {
|
||||
desc
|
||||
.replace(whitespaceLineRegex, "\n")
|
||||
@ -263,14 +280,14 @@ fun ExpandableMangaDescription(
|
||||
onDismissRequest = { showMenu = false },
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_search)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_search)) },
|
||||
onClick = {
|
||||
onTagSearch(tagSelected)
|
||||
showMenu = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_copy_to_clipboard)) },
|
||||
text = { Text(text = stringResource(MR.strings.action_copy_to_clipboard)) },
|
||||
onClick = {
|
||||
onCopyTagToClipboard(tagSelected)
|
||||
showMenu = false
|
||||
@ -280,7 +297,7 @@ fun ExpandableMangaDescription(
|
||||
if (expanded) {
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
) {
|
||||
tags.forEach {
|
||||
TagsChip(
|
||||
@ -296,7 +313,7 @@ fun ExpandableMangaDescription(
|
||||
} else {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
) {
|
||||
items(items = tags) {
|
||||
TagsChip(
|
||||
@ -337,7 +354,7 @@ private fun MangaAndSourceTitlesLarge(
|
||||
MangaCover.Book(
|
||||
modifier = Modifier.fillMaxWidth(0.65f),
|
||||
data = coverDataProvider(),
|
||||
contentDescription = stringResource(R.string.manga_cover),
|
||||
contentDescription = stringResource(MR.strings.manga_cover),
|
||||
onClick = onCoverClick,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
@ -379,7 +396,7 @@ private fun MangaAndSourceTitlesSmall(
|
||||
.sizeIn(maxWidth = 100.dp)
|
||||
.align(Alignment.Top),
|
||||
data = coverDataProvider(),
|
||||
contentDescription = stringResource(R.string.manga_cover),
|
||||
contentDescription = stringResource(MR.strings.manga_cover),
|
||||
onClick = onCoverClick,
|
||||
)
|
||||
Column(
|
||||
@ -399,19 +416,19 @@ private fun MangaAndSourceTitlesSmall(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MangaContentInfo(
|
||||
private fun ColumnScope.MangaContentInfo(
|
||||
title: String,
|
||||
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
author: String?,
|
||||
artist: String?,
|
||||
status: Long,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Text(
|
||||
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
||||
text = title.ifBlank { stringResource(MR.strings.unknown_title) },
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.clickableNoIndication(
|
||||
onLongClick = {
|
||||
@ -431,7 +448,7 @@ private fun MangaContentInfo(
|
||||
|
||||
Row(
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
@ -441,7 +458,7 @@ private fun MangaContentInfo(
|
||||
)
|
||||
Text(
|
||||
text = author?.takeIf { it.isNotBlank() }
|
||||
?: stringResource(R.string.unknown_author),
|
||||
?: stringResource(MR.strings.unknown_author),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier
|
||||
.clickableNoIndication(
|
||||
@ -462,7 +479,7 @@ private fun MangaContentInfo(
|
||||
if (!artist.isNullOrBlank() && author != artist) {
|
||||
Row(
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
@ -507,13 +524,13 @@ private fun MangaContentInfo(
|
||||
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
|
||||
Text(
|
||||
text = when (status) {
|
||||
SManga.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
||||
SManga.COMPLETED.toLong() -> stringResource(R.string.completed)
|
||||
SManga.LICENSED.toLong() -> stringResource(R.string.licensed)
|
||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(R.string.publishing_finished)
|
||||
SManga.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
||||
SManga.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
||||
else -> stringResource(R.string.unknown)
|
||||
SManga.ONGOING.toLong() -> stringResource(MR.strings.ongoing)
|
||||
SManga.COMPLETED.toLong() -> stringResource(MR.strings.completed)
|
||||
SManga.LICENSED.toLong() -> stringResource(MR.strings.licensed)
|
||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(MR.strings.publishing_finished)
|
||||
SManga.CANCELLED.toLong() -> stringResource(MR.strings.cancelled)
|
||||
SManga.ON_HIATUS.toLong() -> stringResource(MR.strings.on_hiatus)
|
||||
else -> stringResource(MR.strings.unknown)
|
||||
},
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
@ -551,7 +568,10 @@ private fun MangaSummary(
|
||||
expanded: Boolean,
|
||||
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(
|
||||
modifier = modifier.clipToBounds(),
|
||||
contents = listOf(
|
||||
@ -587,7 +607,9 @@ private fun MangaSummary(
|
||||
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down)
|
||||
Icon(
|
||||
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,
|
||||
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.setValue
|
||||
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.unit.dp
|
||||
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.UpIcon
|
||||
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
|
||||
|
||||
@Composable
|
||||
fun MangaToolbar(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
titleAlphaProvider: () -> Float,
|
||||
backgroundAlphaProvider: () -> Float = titleAlphaProvider,
|
||||
hasFilters: Boolean,
|
||||
onBackClicked: () -> Unit,
|
||||
onClickFilter: () -> Unit,
|
||||
@ -46,10 +44,14 @@ fun MangaToolbar(
|
||||
onClickEditCategory: (() -> Unit)?,
|
||||
onClickRefresh: () -> Unit,
|
||||
onClickMigrate: (() -> Unit)?,
|
||||
|
||||
// For action mode
|
||||
actionModeCounter: Int,
|
||||
onSelectAll: () -> Unit,
|
||||
onInvertSelection: () -> Unit,
|
||||
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundAlphaProvider: () -> Float = titleAlphaProvider,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
@ -61,25 +63,25 @@ fun MangaToolbar(
|
||||
text = if (isActionMode) actionModeCounter.toString() else title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.alpha(if (isActionMode) 1f else titleAlphaProvider()),
|
||||
color = LocalContentColor.current.copy(alpha = if (isActionMode) 1f else titleAlphaProvider()),
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBackClicked) {
|
||||
UpIcon(Icons.Outlined.Close.takeIf { isActionMode })
|
||||
UpIcon(navigationIcon = Icons.Outlined.Close.takeIf { isActionMode })
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (isActionMode) {
|
||||
AppBarActions(
|
||||
listOf(
|
||||
persistentListOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_select_all),
|
||||
title = stringResource(MR.strings.action_select_all),
|
||||
icon = Icons.Outlined.SelectAll,
|
||||
onClick = onSelectAll,
|
||||
),
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_select_inverse),
|
||||
title = stringResource(MR.strings.action_select_inverse),
|
||||
icon = Icons.Outlined.FlipToBack,
|
||||
onClick = onInvertSelection,
|
||||
),
|
||||
@ -98,11 +100,12 @@ fun MangaToolbar(
|
||||
|
||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||
AppBarActions(
|
||||
actions = buildList {
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
if (onClickDownload != null) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.manga_download),
|
||||
title = stringResource(MR.strings.manga_download),
|
||||
icon = Icons.Outlined.Download,
|
||||
onClick = { downloadExpanded = !downloadExpanded },
|
||||
),
|
||||
@ -110,7 +113,7 @@ fun MangaToolbar(
|
||||
}
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_filter),
|
||||
title = stringResource(MR.strings.action_filter),
|
||||
icon = Icons.Outlined.FilterList,
|
||||
iconTint = filterTint,
|
||||
onClick = onClickFilter,
|
||||
@ -118,14 +121,14 @@ fun MangaToolbar(
|
||||
)
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_webview_refresh),
|
||||
title = stringResource(MR.strings.action_webview_refresh),
|
||||
onClick = onClickRefresh,
|
||||
),
|
||||
)
|
||||
if (onClickEditCategory != null) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_edit_categories),
|
||||
title = stringResource(MR.strings.action_edit_categories),
|
||||
onClick = onClickEditCategory,
|
||||
),
|
||||
)
|
||||
@ -133,7 +136,7 @@ fun MangaToolbar(
|
||||
if (onClickMigrate != null) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_migrate),
|
||||
title = stringResource(MR.strings.action_migrate),
|
||||
onClick = onClickMigrate,
|
||||
),
|
||||
)
|
||||
@ -141,12 +144,13 @@ fun MangaToolbar(
|
||||
if (onClickShare != null) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_share),
|
||||
title = stringResource(MR.strings.action_share),
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
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.windowInsetsPadding
|
||||
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.GetApp
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
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.Settings
|
||||
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.graphics.vector.ImageVector
|
||||
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 eu.kanade.presentation.components.WarningBanner
|
||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
||||
import tachiyomi.core.Constants
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun MoreScreen(
|
||||
@ -59,12 +58,7 @@ fun MoreScreen(
|
||||
),
|
||||
) {
|
||||
if (isFDroid) {
|
||||
WarningBanner(
|
||||
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")
|
||||
},
|
||||
)
|
||||
// Don't really care about slow updaters now
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -77,8 +71,8 @@ fun MoreScreen(
|
||||
}
|
||||
item {
|
||||
SwitchPreferenceWidget(
|
||||
title = stringResource(R.string.label_downloaded_only),
|
||||
subtitle = stringResource(R.string.downloaded_only_summary),
|
||||
title = stringResource(MR.strings.label_downloaded_only),
|
||||
subtitle = stringResource(MR.strings.downloaded_only_summary),
|
||||
icon = Icons.Outlined.CloudOff,
|
||||
checked = downloadedOnly,
|
||||
onCheckedChanged = onDownloadedOnlyChange,
|
||||
@ -86,8 +80,8 @@ fun MoreScreen(
|
||||
}
|
||||
item {
|
||||
SwitchPreferenceWidget(
|
||||
title = stringResource(R.string.pref_incognito_mode),
|
||||
subtitle = stringResource(R.string.pref_incognito_mode_summary),
|
||||
title = stringResource(MR.strings.pref_incognito_mode),
|
||||
subtitle = stringResource(MR.strings.pref_incognito_mode_summary),
|
||||
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
|
||||
checked = incognitoMode,
|
||||
onCheckedChanged = onIncognitoModeChange,
|
||||
@ -99,17 +93,17 @@ fun MoreScreen(
|
||||
item {
|
||||
val downloadQueueState = downloadQueueStateProvider()
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.label_download_queue),
|
||||
title = stringResource(MR.strings.label_download_queue),
|
||||
subtitle = when (downloadQueueState) {
|
||||
DownloadQueueState.Stopped -> null
|
||||
is DownloadQueueState.Paused -> {
|
||||
val pending = downloadQueueState.pending
|
||||
if (pending == 0) {
|
||||
stringResource(R.string.paused)
|
||||
stringResource(MR.strings.paused)
|
||||
} else {
|
||||
"${stringResource(R.string.paused)} • ${
|
||||
"${stringResource(MR.strings.paused)} • ${
|
||||
pluralStringResource(
|
||||
id = R.plurals.download_queue_summary,
|
||||
MR.plurals.download_queue_summary,
|
||||
count = pending,
|
||||
pending,
|
||||
)
|
||||
@ -118,7 +112,7 @@ fun MoreScreen(
|
||||
}
|
||||
is DownloadQueueState.Downloading -> {
|
||||
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,
|
||||
@ -127,21 +121,21 @@ fun MoreScreen(
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.categories),
|
||||
icon = Icons.Outlined.Label,
|
||||
title = stringResource(MR.strings.categories),
|
||||
icon = Icons.AutoMirrored.Outlined.Label,
|
||||
onPreferenceClick = onClickCategories,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.label_stats),
|
||||
title = stringResource(MR.strings.label_stats),
|
||||
icon = Icons.Outlined.QueryStats,
|
||||
onPreferenceClick = onClickStats,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.label_data_storage),
|
||||
title = stringResource(MR.strings.label_data_storage),
|
||||
icon = Icons.Outlined.Storage,
|
||||
onPreferenceClick = onClickDataAndStorage,
|
||||
)
|
||||
@ -151,22 +145,22 @@ fun MoreScreen(
|
||||
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.label_settings),
|
||||
title = stringResource(MR.strings.label_settings),
|
||||
icon = Icons.Outlined.Settings,
|
||||
onPreferenceClick = onClickSettings,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.pref_category_about),
|
||||
title = stringResource(MR.strings.pref_category_about),
|
||||
icon = Icons.Outlined.Info,
|
||||
onPreferenceClick = onClickAbout,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.label_help),
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
title = stringResource(MR.strings.label_help),
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
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