Compare commits
296 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26cfb4811f | |||
| e5a6d1b456 | |||
| f0b621dfe5 | |||
| d88f570f65 | |||
| b430e31da4 | |||
| 271f2d37bb | |||
| c2e36b4c5c | |||
| cb25deb5ac | |||
| a6c6cf77bb | |||
| e3dae57e0b | |||
| 226321f334 | |||
| 2187731d70 | |||
| fd32f2e879 | |||
| 5a094850d1 | |||
| e74053e989 | |||
| 798db44908 | |||
| 7715b5bdd0 | |||
| 084e11f21d | |||
| 01792c0618 | |||
| 0b93ceaa8f | |||
| bfdbe18509 | |||
| e3245d0610 | |||
| c2df6ee54a | |||
| ffc1e2d97b | |||
| d0c8b0c98a | |||
| f206ab8b32 | |||
| a443629234 | |||
| 3de4711e03 | |||
| 106f63a657 | |||
| 3c09343f7b | |||
| 86e1406565 | |||
| b48556aa9f | |||
| f3e905513f | |||
| 633a1892b3 | |||
| 74cf08b47b | |||
| cc7ce80abf | |||
| e06941f82d | |||
| a8a290d03d | |||
| b49ca3ce4c | |||
| c51c364cdd | |||
| 366415d323 | |||
| 14f6fd7908 | |||
| 15f1ee2205 | |||
| 651579b243 | |||
| 8f596069fa | |||
| a28d526102 | |||
| bbaa74d99c | |||
| 310b1ad69b | |||
| 7f37989c4e | |||
| 185920b984 | |||
| 4639077756 | |||
| 0bf1519c25 | |||
| 45a36cef32 | |||
| dece1bc0cb | |||
| eaffd3f2dc | |||
| aabe409ee5 | |||
| e626cdd030 | |||
| b161c333ec | |||
| e587bb7f44 | |||
| 6cf7ef7bba | |||
| 91d61a75e3 | |||
| 95ae5211a7 | |||
| 62afbf8ff3 | |||
| 2ea8449eb7 | |||
| 697b0de226 | |||
| 41e523e074 | |||
| dee543c7c5 | |||
| 788d3797cb | |||
| 6464c00503 | |||
| dc88ea8f63 | |||
| 95cbb35152 | |||
| 558ce084c8 | |||
| 943555c0af | |||
| 216bc2c57d | |||
| cde3002355 | |||
| db907cf270 | |||
| a269802af9 | |||
| affab50a02 | |||
| 2f102db19d | |||
| 457e5f963b | |||
| 2bd9a914c1 | |||
| f6d2d0bd48 | |||
| 91ae683b74 | |||
| bccd1eff2b | |||
| 9ed90eb6f2 | |||
| a246d897de | |||
| 4923ba0b54 | |||
| bd278b1878 | |||
| ea0816a6c1 | |||
| af3c7a0753 | |||
| 4a9184bfc1 | |||
| 77d75de855 | |||
| d7fbdb1b35 | |||
| 6ad9eb098f | |||
| a6667bc91d | |||
| 1e7b6d488c | |||
| d0ef7bcd54 | |||
| e29e7c9169 | |||
| 4eb8dc35b9 | |||
| 1077820d59 | |||
| ccf1f3b6ef | |||
| 73d91a8537 | |||
| a141e63408 | |||
| 3e1c346a04 | |||
| 6872dc449f | |||
| c672548491 | |||
| 74abed9abd | |||
| cb813908a6 | |||
| 049a395790 | |||
| bc79694eae | |||
| 812f76b8f5 | |||
| 4d8b5fc8a1 | |||
| a40c54e60c | |||
| e8ff402fff | |||
| 0753ffe425 | |||
| 03aa27fb6b | |||
| 51b9004a2d | |||
| 23285587a7 | |||
| 5dcc02c44f | |||
| ecd38d9429 | |||
| 3b3e3f5d35 | |||
| 0d66d03f56 | |||
| a68bb60126 | |||
| 1e9f7612f0 | |||
| 51229ca511 | |||
| b98e198e15 | |||
| 3cac63ed91 | |||
| 6bb2bc03f3 | |||
| da3823daed | |||
| 3edb03de32 | |||
| 3408ef635d | |||
| 365cd0b14d | |||
| 96439afce4 | |||
| c1c615000b | |||
| 58df8b79fb | |||
| f524763854 | |||
| 402f5e6bad | |||
| 0d13c6187c | |||
| 1853a86a73 | |||
| 77e6e06cfa | |||
| 21440a0290 | |||
| d6ffef15e1 | |||
| 3cc250e122 | |||
| 051c559840 | |||
| 3972d7fe4b | |||
| 44fd9f3564 | |||
| f36906df45 | |||
| efbaf1a4ca | |||
| 2f8efe0526 | |||
| dea38912fc | |||
| 5243346356 | |||
| 6bb3ec5b8a | |||
| 7390e72045 | |||
| 64ff5cb9af | |||
| 82f011e48e | |||
| 8868a5db2b | |||
| a335feedfc | |||
| 90ff5e69ec | |||
| 68a1820695 | |||
| 292b551027 | |||
| e19c62a8ae | |||
| 348cb335c4 | |||
| c426d11d76 | |||
| 1965c0825d | |||
| 0124763fcd | |||
| b0d737592c | |||
| e14596465b | |||
| 5e52dfcc66 | |||
| a09643fc77 | |||
| 19bc08659b | |||
| 2cb8f8f872 | |||
| 23c7bb09d3 | |||
| bdb8553e28 | |||
| fbac29e0cd | |||
| b0aa2ffc42 | |||
| 45b5d9b8a4 | |||
| 91b98cdb82 | |||
| 7f544f7163 | |||
| 3705880a77 | |||
| 759fd4d4e3 | |||
| fc956fc791 | |||
| 9d7346157b | |||
| 0f0f4cf4a9 | |||
| 426ef65102 | |||
| 95c834581b | |||
| 71f2daf8f3 | |||
| 7ec14cd9f0 | |||
| c23c9491fc | |||
| 29f3766c87 | |||
| 07c89890bc | |||
| 64a54f55b3 | |||
| f7202e67cc | |||
| 155b03c176 | |||
| 6b0482576b | |||
| c137bafd68 | |||
| 49bdffdc28 | |||
| f1b32d531a | |||
| a5ec6c5cdd | |||
| 9c56cdb1c1 | |||
| 543de065a6 | |||
| 33296e1faf | |||
| d1a90c0bb7 | |||
| 9fa61d33be | |||
| 0e9dcc7855 | |||
| 6738c6072d | |||
| 29033c539c | |||
| eaa3413c37 | |||
| 73d9d1d46d | |||
| 94f9aaf351 | |||
| e21149cb37 | |||
| 11aad16f59 | |||
| 33a3918e86 | |||
| d655b8ecdf | |||
| 70a8bef7a5 | |||
| 999a8613cf | |||
| 5721a02bca | |||
| e303b88b90 | |||
| a62dd5821a | |||
| a0786d9b09 | |||
| 04580ce357 | |||
| b759f2f02a | |||
| 8ae8068ecd | |||
| eecd9367d4 | |||
| 55dee69838 | |||
| a730ca5444 | |||
| cebd8fe0a8 | |||
| 55a979c5f7 | |||
| 728f3fc349 | |||
| a9a3ed1d16 | |||
| 36f13a7c6a | |||
| 37a2ccc678 | |||
| bb39088dd7 | |||
| c5546e1095 | |||
| 2d12c670db | |||
| 3db4bccebc | |||
| 2f23ad6bfd | |||
| de1898a2c9 | |||
| eb135ec22d | |||
| bf6c646dc7 | |||
| 9ce16d5e1c | |||
| 619ff726c8 | |||
| 730ceaaf49 | |||
| 07b701cb3c | |||
| b64c6b78ea | |||
| 521bce5c08 | |||
| a719ed8c9e | |||
| f6fc2d7e2f | |||
| 48d43c4f07 | |||
| f4fa86b2dc | |||
| 37db0dc1f6 | |||
| 1ada03b07a | |||
| f4c1e7c2d5 | |||
| 6c5282c598 | |||
| 7899474a36 | |||
| 225b419bba | |||
| fa64103a1c | |||
| 57e0e99f06 | |||
| efde7afa8e | |||
| f929a4bc26 | |||
| d35141c1cc | |||
| 6988966019 | |||
| f6d8ebbb0a | |||
| ae45df9fcf | |||
| f332344681 | |||
| c6abb340ca | |||
| 99dbb16a7a | |||
| f62e8933d7 | |||
| ee3c2fd79c | |||
| 6b08b873a8 | |||
| a3f2f49ab8 | |||
| 524f5cc6ab | |||
| a35e084b9e | |||
| 78f7fba67b | |||
| 69d1db3018 | |||
| d1b317e5c8 | |||
| fff40e031f | |||
| 5be2ec51ba | |||
| 1c2a7af13e | |||
| 182158acb0 | |||
| 21f92bfb3a | |||
| a5522ef732 | |||
| 239793f7fd | |||
| 4e9cfe4602 | |||
| f548c85e7a | |||
| 576349c446 | |||
| 9b00e0458b | |||
| 6a1ff99441 | |||
| 0121fe9397 | |||
| 5c47c7a409 | |||
| 8bb4f33f2e | |||
| 5f5fd51668 | |||
| c7bbad93b2 | |||
| 1a4a2506f4 | |||
| 7b7a594ddb | |||
| c2eece0fff | |||
| d29a4ff381 |
@@ -53,7 +53,7 @@ body:
|
||||
label: TachiyomiSY version
|
||||
description: You can find your TachiyomiSY version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "1.10.5"
|
||||
Example: "1.11.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -63,7 +63,7 @@ body:
|
||||
label: Android version
|
||||
description: You can find this somewhere in your Android settings.
|
||||
placeholder: |
|
||||
Example: "Android 11"
|
||||
Example: "Android 14"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -73,7 +73,7 @@ body:
|
||||
label: Device
|
||||
description: List your device and model.
|
||||
placeholder: |
|
||||
Example: "Google Pixel 5"
|
||||
Example: "Google Pixel 8"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -96,7 +96,7 @@ body:
|
||||
required: true
|
||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https:/mihon.app/docs/guides/troubleshooting/).
|
||||
required: true
|
||||
- label: I have updated the app to version **[1.10.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||
- label: I have updated the app to version **[1.11.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||
required: true
|
||||
- label: I have updated all installed extensions.
|
||||
required: true
|
||||
|
||||
@@ -31,7 +31,7 @@ body:
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I have updated the app to version **[1.10.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||
- label: I have updated the app to version **[1.11.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base"],
|
||||
"labels": ["Dependencies"],
|
||||
"includePaths": [".github/workflows/*", "gradle/sy.versions.toml"],
|
||||
"semanticCommits": "disabled"
|
||||
}
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
build:
|
||||
name: Build app
|
||||
@@ -27,19 +27,19 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: adopt
|
||||
|
||||
- name: Set up gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew detekt assembleDevDebug
|
||||
run: ./gradlew spotlessCheck assembleDevDebug
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TachiyomiSY-${{ github.sha }}.apk
|
||||
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v2
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
- name: Setup Android SDK
|
||||
run: |
|
||||
@@ -31,18 +31,18 @@ jobs:
|
||||
distribution: adopt
|
||||
|
||||
- name: Set up gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
# SY <--
|
||||
- name: Write google-services.json
|
||||
uses: DamianReeves/write-file-action@v1.2
|
||||
uses: DamianReeves/write-file-action@v1.3
|
||||
with:
|
||||
path: app/google-services.json
|
||||
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
|
||||
write-mode: overwrite
|
||||
|
||||
- name: Write client_secrets.json
|
||||
uses: DamianReeves/write-file-action@v1.2
|
||||
uses: DamianReeves/write-file-action@v1.3
|
||||
with:
|
||||
path: app/src/main/assets/client_secrets.json
|
||||
contents: ${{ secrets.CLIENT_SECRETS_TEXT }}
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
# SY -->
|
||||
|
||||
- name: Build app and run unit tests
|
||||
run: ./gradlew detekt assembleStandardRelease testStandardReleaseUnitTest --stacktrace
|
||||
run: ./gradlew spotlessCheck assembleStandardRelease testStandardReleaseUnitTest --stacktrace
|
||||
|
||||
- name: Sign APK
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.run_number }}
|
||||
name: TachiyomiSY
|
||||
|
||||
@@ -3,12 +3,11 @@ name: Remote Dispatch Action Initiator
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'preview'
|
||||
|
||||
jobs:
|
||||
trigger_preview_build:
|
||||
name: Trigger preview build
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -16,7 +15,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
- name: Create Tag
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Label PRs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
label_pr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check PR and Add Label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const prAuthor = context.payload.pull_request.user.login;
|
||||
|
||||
if (prAuthor === 'weblate') {
|
||||
const labels = ['Translations'];
|
||||
await github.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
labels: labels
|
||||
});
|
||||
}
|
||||
+16
-20
@@ -1,25 +1,21 @@
|
||||
# Build files
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
.DS_Store
|
||||
.kotlin
|
||||
build
|
||||
|
||||
# IDE files
|
||||
*.iml
|
||||
.idea/*
|
||||
!.idea/icon.png
|
||||
*iml
|
||||
*.iml
|
||||
/mainframer
|
||||
/.mainframer
|
||||
|
||||
# Built files
|
||||
*/build
|
||||
/build
|
||||
*.apk
|
||||
app/**/output.json
|
||||
|
||||
# Unnecessary file
|
||||
*.swp
|
||||
|
||||
TODO.md
|
||||
CHANGELOG.md
|
||||
/captures
|
||||
build.sh
|
||||
|
||||
# Configuration files
|
||||
local.properties
|
||||
|
||||
# macOS specific files
|
||||
.DS_Store
|
||||
|
||||
# SY ignores
|
||||
google-services.json
|
||||
/app/src/main/assets/client_secrets.json
|
||||
*.apk
|
||||
+7
-14
@@ -1,4 +1,4 @@
|
||||
Looking to report an issue/bug or make a feature request? Please refer to the [README file](https://github.com/tachiyomiorg/tachiyomi#issues-feature-requests-and-contributing).
|
||||
Looking to report an issue/bug or make a feature request? Please refer to the [README file](/README.md#issues-feature-requests-and-contributing).
|
||||
|
||||
---
|
||||
|
||||
@@ -9,7 +9,7 @@ Thanks for your interest in contributing to Tachiyomi!
|
||||
|
||||
Pull requests are welcome!
|
||||
|
||||
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
|
||||
If you're interested in taking on [an open issue](https://github.com/jobobby04/TachiyomiSY/issues), please comment on it so others are aware.
|
||||
You do not need to ask for permission nor an assignment.
|
||||
|
||||
## Prerequisites
|
||||
@@ -24,34 +24,27 @@ Before you start, please note that the ability to use following technologies is
|
||||
- [Android Studio](https://developer.android.com/studio)
|
||||
- Emulator or phone with developer options enabled to test changes.
|
||||
|
||||
## Linting
|
||||
|
||||
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.
|
||||
- Join [the Discord server](https://discord.gg/mihon) for online help and to ask questions while developing.
|
||||
|
||||
# Translations
|
||||
|
||||
Translations are done externally via Weblate. See [our website](https://tachiyomi.org/docs/contribute#translation) for more details.
|
||||
Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#translation) for more details.
|
||||
|
||||
|
||||
# Forks
|
||||
|
||||
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/tachiyomiorg/tachiyomi/blob/master/LICENSE).
|
||||
Forks are allowed so long as they abide by [the project's LICENSE](/LICENSE).
|
||||
|
||||
When creating a fork, remember to:
|
||||
|
||||
- To avoid confusion with the main app:
|
||||
- Change the app name
|
||||
- Change the app icon
|
||||
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
||||
- Change or disable the [app update checker](/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
||||
- To avoid installation conflicts:
|
||||
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts)
|
||||
- To avoid having your data polluting the main app's analytics and crash report services:
|
||||
- If you want to use Firebase analytics, replace [`google-services.json`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/standard/google-services.json) with your own
|
||||
- If you want to use ACRA crash reporting, replace the `ACRA_URI` endpoint in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts) with your own
|
||||
- Change the `applicationId` in [`build.gradle.kts`](/app/build.gradle.kts)
|
||||
|
||||
|
||||
### Supporting Cloud Sync - Google Drive Implementation
|
||||
|
||||
@@ -14,7 +14,7 @@ Features of Mihon(original) include:
|
||||
* Online reading from a variety of sources
|
||||
* Local reading of downloaded content
|
||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
||||
* Categories to organize your library
|
||||
* Light and dark themes
|
||||
* Schedule updating your library for new chapters
|
||||
@@ -67,13 +67,22 @@ Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/re
|
||||
|
||||
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/jobobby04/tachiyomisypreview/releases).
|
||||
|
||||
## Translation
|
||||
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/mihon/tachiyomisy/)
|
||||
|
||||
<details><summary>Translation Progress</summary>
|
||||
<a href="https://hosted.weblate.org/engage/mihon/">
|
||||
<img src="https://hosted.weblate.org/widgets/mihon/-/tachiyomisy/multi-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
</details>
|
||||
|
||||
## Issues, Feature Requests and Contributing
|
||||
|
||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
||||
|
||||
<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://github.com/jobobby04/tachiyomisy/releases) and the already opened [issues](https://github.com/jobobby04/tachiyomisy/issues).**
|
||||
1. **Before reporting a new issue, take a look at the [FAQ](https://mihon.app/docs/faq/general), the [changelog](https://github.com/jobobby04/tachiyomisy/releases) and the already opened [issues](https://github.com/jobobby04/tachiyomisy/issues).**
|
||||
2. If you are unsure, ask here: [](https://discord.gg/mihon)
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
/build
|
||||
*iml
|
||||
*.iml
|
||||
google-services.json
|
||||
+52
-47
@@ -1,27 +1,29 @@
|
||||
@file:Suppress("ChromeOsAbiSupport")
|
||||
|
||||
import mihon.buildlogic.getBuildTime
|
||||
import mihon.buildlogic.getCommitCount
|
||||
import mihon.buildlogic.getGitSha
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
id("mihon.android.application")
|
||||
id("mihon.android.application.compose")
|
||||
id("com.mikepenz.aboutlibraries.plugin")
|
||||
kotlin("plugin.parcelize")
|
||||
kotlin("plugin.serialization")
|
||||
// id("com.github.zellius.shortcut-helper")
|
||||
alias(libs.plugins.aboutLibraries)
|
||||
id("com.github.ben-manes.versions")
|
||||
}
|
||||
|
||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
||||
// Firebase Crashlytics
|
||||
apply(plugin = "com.google.firebase.crashlytics")
|
||||
pluginManager.apply {
|
||||
apply(libs.plugins.google.services.get().pluginId)
|
||||
apply(libs.plugins.firebase.crashlytics.get().pluginId)
|
||||
}
|
||||
}
|
||||
|
||||
// shortcutHelper.setFilePath("./shortcuts.xml")
|
||||
|
||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
android {
|
||||
namespace = "eu.kanade.tachiyomi"
|
||||
@@ -29,8 +31,8 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "eu.kanade.tachiyomi.sy"
|
||||
|
||||
versionCode = 68
|
||||
versionName = "1.10.5"
|
||||
versionCode = 71
|
||||
versionName = "1.11.0"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
@@ -38,7 +40,7 @@ android {
|
||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||
|
||||
ndk {
|
||||
abiFilters += SUPPORTED_ABIS
|
||||
abiFilters += supportedAbis
|
||||
}
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -47,7 +49,7 @@ android {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
include(*SUPPORTED_ABIS.toTypedArray())
|
||||
include(*supportedAbis.toTypedArray())
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
@@ -106,13 +108,16 @@ android {
|
||||
packaging {
|
||||
resources.excludes.addAll(
|
||||
listOf(
|
||||
"kotlin-tooling-metadata.json",
|
||||
"META-INF/DEPENDENCIES",
|
||||
"LICENSE.txt",
|
||||
"META-INF/LICENSE",
|
||||
"META-INF/LICENSE.txt",
|
||||
"META-INF/**/LICENSE.txt",
|
||||
"META-INF/*.properties",
|
||||
"META-INF/**/*.properties",
|
||||
"META-INF/README.md",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/*.kotlin_module",
|
||||
"META-INF/*.version",
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -137,6 +142,24 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.addAll(
|
||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.i18n)
|
||||
// SY -->
|
||||
@@ -161,14 +184,15 @@ dependencies {
|
||||
debugImplementation(compose.ui.tooling)
|
||||
implementation(compose.ui.tooling.preview)
|
||||
implementation(compose.ui.util)
|
||||
implementation(compose.accompanist.systemuicontroller)
|
||||
|
||||
implementation(androidx.interpolator)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
|
||||
implementation(libs.bundles.sqlite)
|
||||
// SY -->
|
||||
implementation(libs.sqlcipher)
|
||||
implementation(sylibs.sqlcipher)
|
||||
// SY <--
|
||||
|
||||
implementation(kotlinx.reflect)
|
||||
@@ -210,10 +234,6 @@ dependencies {
|
||||
// Disk
|
||||
implementation(libs.disklrucache)
|
||||
implementation(libs.unifile)
|
||||
implementation(libs.bundles.archive)
|
||||
// SY -->
|
||||
implementation(libs.zip4j)
|
||||
// SY <--
|
||||
|
||||
// Preferences
|
||||
implementation(libs.preferencektx)
|
||||
@@ -245,15 +265,13 @@ dependencies {
|
||||
implementation(libs.compose.webview)
|
||||
implementation(libs.compose.grid)
|
||||
|
||||
|
||||
implementation(libs.google.api.services.drive)
|
||||
implementation(libs.google.api.client.oauth)
|
||||
|
||||
// Logging
|
||||
implementation(libs.logcat)
|
||||
|
||||
// Crash reports/analytics
|
||||
// "standardImplementation"(libs.firebase.analytics)
|
||||
// "standardImplementation"(platform(libs.firebase.bom))
|
||||
// "standardImplementation"(libs.firebase.analytics)
|
||||
// "standardImplementation"(libs.firebase.crashlytics)
|
||||
|
||||
// Shizuku
|
||||
implementation(libs.bundles.shizuku)
|
||||
@@ -272,8 +290,9 @@ dependencies {
|
||||
implementation(sylibs.simularity)
|
||||
|
||||
// Firebase (EH)
|
||||
implementation(sylibs.firebase.analytics)
|
||||
implementation(sylibs.firebase.crashlytics.ktx)
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.analytics)
|
||||
implementation(libs.firebase.crashlytics)
|
||||
|
||||
// Better logging (EH)
|
||||
implementation(sylibs.xlog)
|
||||
@@ -281,6 +300,14 @@ dependencies {
|
||||
// RatingBar (SY)
|
||||
implementation(sylibs.ratingbar)
|
||||
implementation(sylibs.composeRatingbar)
|
||||
|
||||
// Google drive
|
||||
implementation(sylibs.google.api.services.drive)
|
||||
implementation(sylibs.google.api.client.oauth)
|
||||
|
||||
// Koin
|
||||
implementation(sylibs.koin.core)
|
||||
implementation(sylibs.koin.android)
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
@@ -299,28 +326,6 @@ androidComponents {
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||
withType<KotlinCompile> {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-Xcontext-receivers",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath(kotlinx.gradle)
|
||||
|
||||
Vendored
-3
@@ -127,9 +127,6 @@
|
||||
# XmlUtil
|
||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
||||
|
||||
# Apache Commons Compress
|
||||
-keep class * extends org.apache.commons.compress.archivers.zip.ZipExtraField { <init>(); }
|
||||
|
||||
# Firebase
|
||||
-keep class com.google.firebase.installations.** { *; }
|
||||
-keep interface com.google.firebase.installations.** { *; }
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||
</adaptive-icon>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||
</adaptive-icon>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package mihon.core.firebase
|
||||
|
||||
import android.content.Context
|
||||
|
||||
object FirebaseConfig {
|
||||
fun init(context: Context) = Unit
|
||||
|
||||
fun setAnalyticsEnabled(enabled: Boolean) = Unit
|
||||
|
||||
fun setCrashlyticsEnabled(enabled: Boolean) = Unit
|
||||
}
|
||||
@@ -34,11 +34,23 @@
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<!-- Remove permission from Firebase dependency -->
|
||||
<!-- Remove unnecessary permissions from Firebase dependency -->
|
||||
<uses-permission
|
||||
android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE"
|
||||
tools:node="remove" />
|
||||
|
||||
<uses-permission
|
||||
android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION"
|
||||
tools:node="remove" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_ADSERVICES_AD_ID"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="false"
|
||||
@@ -256,6 +268,14 @@
|
||||
android:name="android.webkit.WebView.MetricsOptOut"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Disable for manual opt-in -->
|
||||
<meta-data
|
||||
android:name="firebase_analytics_collection_enabled"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="firebase_crashlytics_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
<!-- Disable advertising ID collection for Firebase -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
@@ -395,4 +415,7 @@
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<uses-sdk tools:overrideLibrary="rikka.shizuku.api"
|
||||
tools:ignore="ManifestOrder" />
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -5,7 +5,7 @@ import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||
generator: (T?, T?) -> R?,
|
||||
generator: (before: T?, after: T?) -> R?,
|
||||
): List<R> {
|
||||
if (isEmpty()) return emptyList()
|
||||
val newList = mutableListOf<R>()
|
||||
@@ -19,6 +19,24 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
||||
return newList
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to [eu.kanade.core.util.insertSeparators] but iterates from last to first element
|
||||
*/
|
||||
fun <T : R, R : Any> List<T>.insertSeparatorsReversed(
|
||||
generator: (before: T?, after: T?) -> R?,
|
||||
): List<R> {
|
||||
if (isEmpty()) return emptyList()
|
||||
val newList = mutableListOf<R>()
|
||||
for (i in size downTo 0) {
|
||||
val after = getOrNull(i)
|
||||
after?.let(newList::add)
|
||||
val before = getOrNull(i - 1)
|
||||
val separator = generator.invoke(before, after)
|
||||
separator?.let(newList::add)
|
||||
}
|
||||
return newList.asReversed()
|
||||
}
|
||||
|
||||
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||
if (shouldAdd) {
|
||||
add(value)
|
||||
|
||||
@@ -23,7 +23,11 @@ import eu.kanade.domain.track.interactor.AddTracks
|
||||
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||
import eu.kanade.domain.track.interactor.TrackChapter
|
||||
import eu.kanade.tachiyomi.di.InjektModule
|
||||
import eu.kanade.tachiyomi.di.addFactory
|
||||
import eu.kanade.tachiyomi.di.addSingletonFactory
|
||||
import mihon.data.repository.ExtensionRepoRepositoryImpl
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||
@@ -90,11 +94,7 @@ import tachiyomi.domain.track.interactor.InsertTrack
|
||||
import tachiyomi.domain.track.repository.TrackRepository
|
||||
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
import uy.kohesive.injekt.api.InjektRegistrar
|
||||
import uy.kohesive.injekt.api.addFactory
|
||||
import uy.kohesive.injekt.api.addSingletonFactory
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class DomainModule : InjektModule {
|
||||
|
||||
@@ -152,6 +152,7 @@ class DomainModule : InjektModule {
|
||||
addFactory { ShouldUpdateDbChapter() }
|
||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
addFactory { GetAvailableScanlators(get()) }
|
||||
addFactory { FilterChaptersForDownload(get(), get(), get(), get()) }
|
||||
|
||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||
addFactory { GetHistory(get()) }
|
||||
|
||||
@@ -14,6 +14,9 @@ import eu.kanade.domain.source.interactor.GetSourceCategories
|
||||
import eu.kanade.domain.source.interactor.RenameSourceCategory
|
||||
import eu.kanade.domain.source.interactor.SetSourceCategories
|
||||
import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
|
||||
import eu.kanade.tachiyomi.di.InjektModule
|
||||
import eu.kanade.tachiyomi.di.addFactory
|
||||
import eu.kanade.tachiyomi.di.addSingletonFactory
|
||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||
import exh.search.SearchEngine
|
||||
import tachiyomi.data.manga.CustomMangaRepositoryImpl
|
||||
@@ -42,7 +45,7 @@ import tachiyomi.domain.manga.interactor.GetMergedManga
|
||||
import tachiyomi.domain.manga.interactor.GetMergedMangaById
|
||||
import tachiyomi.domain.manga.interactor.GetMergedMangaForDownloading
|
||||
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
|
||||
import tachiyomi.domain.manga.interactor.GetReadMangaNotInLibrary
|
||||
import tachiyomi.domain.manga.interactor.GetReadMangaNotInLibraryView
|
||||
import tachiyomi.domain.manga.interactor.GetSearchMetadata
|
||||
import tachiyomi.domain.manga.interactor.GetSearchTags
|
||||
import tachiyomi.domain.manga.interactor.GetSearchTitles
|
||||
@@ -71,11 +74,7 @@ import tachiyomi.domain.source.interactor.InsertSavedSearch
|
||||
import tachiyomi.domain.source.repository.FeedSavedSearchRepository
|
||||
import tachiyomi.domain.source.repository.SavedSearchRepository
|
||||
import tachiyomi.domain.track.interactor.IsTrackUnfollowed
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
import uy.kohesive.injekt.api.InjektRegistrar
|
||||
import uy.kohesive.injekt.api.addFactory
|
||||
import uy.kohesive.injekt.api.addSingletonFactory
|
||||
import uy.kohesive.injekt.api.get
|
||||
import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
||||
|
||||
class SYDomainModule : InjektModule {
|
||||
@@ -102,7 +101,7 @@ class SYDomainModule : InjektModule {
|
||||
addFactory { GetPagePreviews(get(), get()) }
|
||||
addFactory { SearchEngine() }
|
||||
addFactory { IsTrackUnfollowed() }
|
||||
addFactory { GetReadMangaNotInLibrary(get()) }
|
||||
addFactory { GetReadMangaNotInLibraryView(get()) }
|
||||
|
||||
// Required for [MetadataSource]
|
||||
addFactory<MetadataSource.GetMangaId> { GetManga(get()) }
|
||||
|
||||
@@ -30,4 +30,6 @@ class BasePreferences(
|
||||
}
|
||||
|
||||
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
||||
|
||||
fun alwaysUseSSIVToDecode() = preferenceStore.getBoolean("pref_always_use_ssiv_to_decode", false)
|
||||
}
|
||||
|
||||
@@ -32,10 +32,11 @@ class GetEnabledSources(
|
||||
) { a, b, c -> Triple(a, b, c) },
|
||||
// SY <--
|
||||
repository.getSources(),
|
||||
) { pinnedSourceIds,
|
||||
(enabledLanguages, disabledSources, lastUsedSource),
|
||||
(excludedFromDataSaver, sourcesInCategories, sourceCategoriesFilter),
|
||||
sources,
|
||||
) {
|
||||
pinnedSourceIds,
|
||||
(enabledLanguages, disabledSources, lastUsedSource),
|
||||
(excludedFromDataSaver, sourcesInCategories, sourceCategoriesFilter),
|
||||
sources,
|
||||
->
|
||||
|
||||
val sourcesAndCategories = sourcesInCategories.map {
|
||||
|
||||
@@ -49,6 +49,11 @@ class SourcePreferences(
|
||||
emptySet(),
|
||||
)
|
||||
|
||||
fun globalSearchFilterState() = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("has_filters_toggle_state"),
|
||||
false,
|
||||
)
|
||||
|
||||
// SY -->
|
||||
fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class SyncPreferences(
|
||||
fun clientAPIKey() = preferenceStore.getString("sync_client_api_key", "")
|
||||
fun lastSyncTimestamp() = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L)
|
||||
|
||||
fun lastSyncEtag() = preferenceStore.getString("sync_etag", "")
|
||||
fun lastSyncEtag() = preferenceStore.getString("sync_etag", "")
|
||||
|
||||
fun syncInterval() = preferenceStore.getInt("sync_interval", 0)
|
||||
fun syncService() = preferenceStore.getInt("sync_service", 0)
|
||||
@@ -29,7 +29,7 @@ class SyncPreferences(
|
||||
)
|
||||
|
||||
fun uniqueDeviceID(): String {
|
||||
val uniqueIDPreference = preferenceStore.getString("unique_device_id", "")
|
||||
val uniqueIDPreference = preferenceStore.getString(Preference.appStateKey("unique_device_id"), "")
|
||||
|
||||
// Retrieve the current value of the preference
|
||||
var uniqueID = uniqueIDPreference.get()
|
||||
@@ -53,12 +53,14 @@ class SyncPreferences(
|
||||
tracking = preferenceStore.getBoolean("tracking", true).get(),
|
||||
history = preferenceStore.getBoolean("history", true).get(),
|
||||
appSettings = preferenceStore.getBoolean("appSettings", true).get(),
|
||||
extensionRepoSettings = preferenceStore.getBoolean("extensionRepoSettings", true).get(),
|
||||
sourceSettings = preferenceStore.getBoolean("sourceSettings", true).get(),
|
||||
privateSettings = preferenceStore.getBoolean("privateSettings", true).get(),
|
||||
|
||||
// SY -->
|
||||
customInfo = preferenceStore.getBoolean("customInfo", true).get(),
|
||||
readEntries = preferenceStore.getBoolean("readEntries", true).get()
|
||||
readEntries = preferenceStore.getBoolean("readEntries", true).get(),
|
||||
savedSearches = preferenceStore.getBoolean("savedSearches", true).get(),
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
@@ -70,12 +72,14 @@ class SyncPreferences(
|
||||
preferenceStore.getBoolean("tracking", true).set(syncSettings.tracking)
|
||||
preferenceStore.getBoolean("history", true).set(syncSettings.history)
|
||||
preferenceStore.getBoolean("appSettings", true).set(syncSettings.appSettings)
|
||||
preferenceStore.getBoolean("extensionRepoSettings", true).set(syncSettings.extensionRepoSettings)
|
||||
preferenceStore.getBoolean("sourceSettings", true).set(syncSettings.sourceSettings)
|
||||
preferenceStore.getBoolean("privateSettings", true).set(syncSettings.privateSettings)
|
||||
|
||||
// SY -->
|
||||
preferenceStore.getBoolean("customInfo", true).set(syncSettings.customInfo)
|
||||
preferenceStore.getBoolean("readEntries", true).set(syncSettings.readEntries)
|
||||
preferenceStore.getBoolean("savedSearches", true).set(syncSettings.savedSearches)
|
||||
// SY <--
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ data class SyncSettings(
|
||||
val tracking: Boolean = true,
|
||||
val history: Boolean = true,
|
||||
val appSettings: Boolean = true,
|
||||
val extensionRepoSettings: Boolean = true,
|
||||
val sourceSettings: Boolean = true,
|
||||
val privateSettings: Boolean = false,
|
||||
|
||||
// SY -->
|
||||
val customInfo: Boolean = true,
|
||||
val readEntries: Boolean = true
|
||||
val readEntries: Boolean = true,
|
||||
val savedSearches: Boolean = true,
|
||||
// SY <--
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import eu.kanade.domain.track.model.toDomainTrack
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||
import logcat.LogPriority
|
||||
@@ -14,17 +15,16 @@ import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.history.interactor.GetHistory
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.track.interactor.GetTracks
|
||||
import tachiyomi.domain.track.interactor.InsertTrack
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class AddTracks(
|
||||
private val getTracks: GetTracks,
|
||||
private val insertTrack: InsertTrack,
|
||||
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
private val trackerManager: TrackerManager,
|
||||
) {
|
||||
|
||||
// TODO: update all trackers based on common data
|
||||
@@ -79,7 +79,7 @@ class AddTracks(
|
||||
|
||||
suspend fun bindEnhancedTrackers(manga: Manga, source: Source) = withNonCancellableContext {
|
||||
withIOContext {
|
||||
getTracks.await(manga.id)
|
||||
trackerManager.loggedInTrackers()
|
||||
.filterIsInstance<EnhancedTracker>()
|
||||
.filter { it.accept(source) }
|
||||
.forEach { service ->
|
||||
@@ -87,11 +87,11 @@ class AddTracks(
|
||||
service.match(manga)?.let { track ->
|
||||
track.manga_id = manga.id
|
||||
(service as Tracker).bind(track)
|
||||
insertTrack.await(track.toDomainTrack()!!)
|
||||
insertTrack.await(track.toDomainTrack(idRequired = false)!!)
|
||||
|
||||
syncChapterProgressWithTrack.await(
|
||||
manga.id,
|
||||
track.toDomainTrack()!!,
|
||||
track.toDomainTrack(idRequired = false)!!,
|
||||
service,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||
import tachiyomi.domain.track.interactor.InsertTrack
|
||||
import tachiyomi.domain.track.model.Track
|
||||
import kotlin.math.max
|
||||
|
||||
class SyncChapterProgressWithTrack(
|
||||
private val updateChapter: UpdateChapter,
|
||||
@@ -36,7 +37,8 @@ class SyncChapterProgressWithTrack(
|
||||
|
||||
// only take into account continuous reading
|
||||
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
|
||||
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
||||
val lastRead = max(remoteTrack.lastChapterRead, localLastRead.toDouble())
|
||||
val updatedTrack = remoteTrack.copy(lastChapterRead = lastRead)
|
||||
|
||||
try {
|
||||
tracker.update(updatedTrack.toDbTrack())
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package eu.kanade.domain.track.model
|
||||
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import tachiyomi.i18n.MR
|
||||
|
||||
enum class AutoTrackState(val titleRes: StringResource) {
|
||||
ALWAYS(MR.strings.lock_always),
|
||||
ASK(MR.strings.default_category_summary),
|
||||
NEVER(MR.strings.lock_never),
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package eu.kanade.domain.track.service
|
||||
|
||||
import eu.kanade.domain.track.model.AutoTrackState
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import tachiyomi.core.common.preference.Preference
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.core.common.preference.getEnum
|
||||
|
||||
class TrackPreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
@@ -35,4 +37,9 @@ class TrackPreferences(
|
||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||
|
||||
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||
|
||||
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum(
|
||||
"pref_auto_update_manga_on_mark_read",
|
||||
AutoTrackState.ALWAYS,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,12 +18,20 @@ class UiPreferences(
|
||||
|
||||
fun themeMode() = preferenceStore.getEnum(
|
||||
"pref_theme_mode_key",
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ThemeMode.SYSTEM } else { ThemeMode.LIGHT },
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
ThemeMode.SYSTEM
|
||||
} else {
|
||||
ThemeMode.LIGHT
|
||||
},
|
||||
)
|
||||
|
||||
fun appTheme() = preferenceStore.getEnum(
|
||||
"pref_app_theme",
|
||||
if (DeviceUtil.isDynamicColorAvailable) { AppTheme.MONET } else { AppTheme.DEFAULT },
|
||||
if (DeviceUtil.isDynamicColorAvailable) {
|
||||
AppTheme.MONET
|
||||
} else {
|
||||
AppTheme.DEFAULT
|
||||
},
|
||||
)
|
||||
|
||||
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||
|
||||
@@ -198,7 +198,7 @@ private fun ExtensionDetails(
|
||||
key = { it.source.id },
|
||||
) { source ->
|
||||
SourceSwitchPreference(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
source = source,
|
||||
onClickSourcePreferences = onClickSourcePreferences,
|
||||
onClickSource = onClickSource,
|
||||
@@ -240,7 +240,7 @@ private fun DetailsHeader(
|
||||
Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName})
|
||||
Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode})
|
||||
NSFW: ${extension.isNsfw}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
if (extension is Extension.Installed) {
|
||||
@@ -250,8 +250,8 @@ private fun DetailsHeader(
|
||||
Update available: ${extension.hasUpdate}
|
||||
Obsolete: ${extension.isObsolete}
|
||||
Shared: ${extension.isShared}
|
||||
Repository: ${extension.repoUrl}
|
||||
""".trimIndent()
|
||||
Repository: ${extension.repoUrl}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ private fun ExtensionFilterContent(
|
||||
) {
|
||||
items(state.languages) { language ->
|
||||
SwitchPreferenceWidget(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||
checked = language in state.enabledLanguages,
|
||||
onCheckedChanged = { onClickLang(language) },
|
||||
|
||||
@@ -48,6 +48,7 @@ import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||
import eu.kanade.presentation.components.WarningBanner
|
||||
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||
@@ -91,7 +92,7 @@ fun ExtensionScreen(
|
||||
PullRefresh(
|
||||
refreshing = state.isRefreshing,
|
||||
onRefresh = onRefresh,
|
||||
enabled = { !state.isLoading },
|
||||
enabled = !state.isLoading,
|
||||
) {
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
@@ -188,14 +189,14 @@ private fun ExtensionContent(
|
||||
}
|
||||
ExtensionHeader(
|
||||
textRes = header.textRes,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
action = action,
|
||||
)
|
||||
}
|
||||
is ExtensionUiModel.Header.Text -> {
|
||||
ExtensionHeader(
|
||||
text = header.text,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -213,13 +214,15 @@ private fun ExtensionContent(
|
||||
},
|
||||
) { item ->
|
||||
ExtensionItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
item = item,
|
||||
onClickItem = {
|
||||
when (it) {
|
||||
is Extension.Available -> onInstallExtension(it)
|
||||
is Extension.Installed -> onOpenExtension(it)
|
||||
is Extension.Untrusted -> { trustState = it }
|
||||
is Extension.Untrusted -> {
|
||||
trustState = it
|
||||
}
|
||||
}
|
||||
},
|
||||
onLongClickItem = onLongClickItem,
|
||||
@@ -241,7 +244,9 @@ private fun ExtensionContent(
|
||||
onOpenExtension(it)
|
||||
}
|
||||
}
|
||||
is Extension.Untrusted -> { trustState = it }
|
||||
is Extension.Untrusted -> {
|
||||
trustState = it
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -92,7 +92,7 @@ fun FeedScreen(
|
||||
refreshing = true
|
||||
onRefresh()
|
||||
},
|
||||
enabled = { !state.isLoadingItems },
|
||||
enabled = !state.isLoadingItems,
|
||||
) {
|
||||
ScrollbarLazyColumn(
|
||||
contentPadding = contentPadding + topSmallPaddingValues,
|
||||
@@ -103,7 +103,6 @@ fun FeedScreen(
|
||||
key = { it.feed.id },
|
||||
) { item ->
|
||||
GlobalSearchResultItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
title = item.title,
|
||||
subtitle = item.subtitle,
|
||||
onLongClick = {
|
||||
@@ -116,6 +115,7 @@ fun FeedScreen(
|
||||
onClickSource(item.source)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.animateItem(),
|
||||
) {
|
||||
FeedItem(
|
||||
item = item,
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||
@@ -80,6 +81,7 @@ internal fun GlobalSearchContent(
|
||||
} ?: source.name,
|
||||
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||
onClick = { onClickSource(source) },
|
||||
modifier = Modifier.animateItem(),
|
||||
) {
|
||||
when (result) {
|
||||
SearchItemResult.Loading -> {
|
||||
|
||||
@@ -144,7 +144,7 @@ private fun MigrateSourceList(
|
||||
key = { (source, _) -> "migrate-${source.id}" },
|
||||
) { (source, count) ->
|
||||
MigrateSourceItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
source = source,
|
||||
count = count,
|
||||
onClickItem = { onClickItem(source) },
|
||||
|
||||
@@ -28,6 +28,7 @@ import eu.kanade.presentation.browse.components.MigrationItem
|
||||
import eu.kanade.presentation.browse.components.MigrationItemResult
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -95,7 +96,7 @@ fun MigrationListScreen(
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.animateItemPlacement()
|
||||
.animateItemFastScroll()
|
||||
.padding(horizontal = 16.dp)
|
||||
.height(IntrinsicSize.Min),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
|
||||
@@ -15,6 +15,7 @@ import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.model.FeedSavedSearch
|
||||
@@ -153,7 +154,7 @@ fun SourceFeedList(
|
||||
key = { it.id },
|
||||
) { item ->
|
||||
GlobalSearchResultItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
title = item.title,
|
||||
subtitle = null,
|
||||
onLongClick = if (item is SourceFeedUI.SourceSavedSearch) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.source.model.Source
|
||||
@@ -79,7 +80,7 @@ private fun SourcesFilterContent(
|
||||
contentType = "source-filter-header",
|
||||
) {
|
||||
SourcesFilterHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
language = language,
|
||||
enabled = enabled,
|
||||
onClickItem = onClickLanguage,
|
||||
@@ -95,7 +96,7 @@ private fun SourcesFilterContent(
|
||||
sources.none { it.id.toString() in state.disabledSources }
|
||||
}
|
||||
SourcesFilterToggle(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
isEnabled = toggleEnabled,
|
||||
onClickItem = {
|
||||
onClickSources(!toggleEnabled, sources)
|
||||
@@ -109,7 +110,7 @@ private fun SourcesFilterContent(
|
||||
contentType = { "source-filter-item" },
|
||||
) { source ->
|
||||
SourcesFilterItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
source = source,
|
||||
enabled = "${source.id}" !in state.disabledSources,
|
||||
onClickItem = onClickSource,
|
||||
|
||||
@@ -35,7 +35,7 @@ import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -81,7 +81,7 @@ fun SourcesScreen(
|
||||
when (model) {
|
||||
is SourceUiModel.Header -> {
|
||||
SourceHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
language = model.language,
|
||||
// SY -->
|
||||
isCategory = model.isCategory,
|
||||
@@ -89,7 +89,7 @@ fun SourcesScreen(
|
||||
)
|
||||
}
|
||||
is SourceUiModel.Item -> SourceItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
source = model.source,
|
||||
// SY -->
|
||||
showLatest = state.showLatest,
|
||||
@@ -179,7 +179,7 @@ private fun SourcePinButton(
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground.copy(
|
||||
alpha = SecondaryItemAlpha,
|
||||
alpha = SECONDARY_ALPHA,
|
||||
)
|
||||
}
|
||||
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
|
||||
|
||||
@@ -127,7 +127,7 @@ private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): St
|
||||
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
||||
withIOContext {
|
||||
value = try {
|
||||
val appInfo = ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
|
||||
val appInfo = ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo!!
|
||||
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
||||
Result.Success(
|
||||
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
||||
|
||||
+2
-4
@@ -30,9 +30,6 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchResultItem(
|
||||
// SY -->
|
||||
modifier: Modifier = Modifier,
|
||||
// SY <--
|
||||
title: String,
|
||||
// SY -->
|
||||
subtitle: String?,
|
||||
@@ -41,9 +38,10 @@ fun GlobalSearchResultItem(
|
||||
// SY -->
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
// SY <--
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Column(modifier) {
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
|
||||
@@ -107,7 +107,7 @@ private fun CategoryContent(
|
||||
key = { _, category -> "category-${category.id}" },
|
||||
) { index, category ->
|
||||
CategoryListItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
category = category,
|
||||
canMoveUp = index != 0,
|
||||
canMoveDown = index != categories.lastIndex,
|
||||
|
||||
+2
-3
@@ -10,8 +10,7 @@ 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
|
||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||
|
||||
@Composable
|
||||
fun CategoryFloatingActionButton(
|
||||
@@ -23,7 +22,7 @@ fun CategoryFloatingActionButton(
|
||||
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||
onClick = onCreate,
|
||||
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
||||
expanded = lazyListState.shouldExpandFAB(),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ fun BiometricTimesContent(
|
||||
) {
|
||||
items(timeRanges, key = { it.formattedString }) { timeRange ->
|
||||
BiometricTimesListItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
timeRange = timeRange,
|
||||
onDelete = { onClickDelete(timeRange) },
|
||||
)
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ fun SortTagContent(
|
||||
) {
|
||||
itemsIndexed(tags, key = { _, tag -> tag }) { index, tag ->
|
||||
SortTagListItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
tag = tag,
|
||||
canMoveUp = index != 0,
|
||||
canMoveDown = index != tags.lastIndex,
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ fun SourceCategoryContent(
|
||||
) {
|
||||
items(categories, key = { it }) { category ->
|
||||
SourceCategoryListItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
category = category,
|
||||
onRename = { onClickRename(category) },
|
||||
onDelete = { onClickDelete(category) },
|
||||
|
||||
@@ -179,7 +179,7 @@ fun AppBarTitle(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.basicMarquee(
|
||||
delayMillis = 2_000,
|
||||
repeatDelayMillis = 2_000,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ fun TabbedDialog(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
state = pagerState,
|
||||
verticalAlignment = Alignment.Top,
|
||||
pageContent = { page -> content(page) }
|
||||
pageContent = { page -> content(page) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
@@ -14,7 +15,6 @@ import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -33,20 +33,13 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
fun TabbedScreen(
|
||||
titleRes: StringResource,
|
||||
tabs: ImmutableList<TabContent>,
|
||||
startIndex: Int? = null,
|
||||
state: PagerState = rememberPagerState { tabs.size },
|
||||
searchQuery: String? = null,
|
||||
onChangeSearchQuery: (String?) -> Unit = {},
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = rememberPagerState { tabs.size }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
LaunchedEffect(startIndex) {
|
||||
if (startIndex != null) {
|
||||
state.scrollToPage(startIndex)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val tab = tabs[state.currentPage]
|
||||
|
||||
@@ -18,6 +18,7 @@ import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.presentation.components.relativeDateText
|
||||
import eu.kanade.presentation.history.components.HistoryItem
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -114,14 +115,14 @@ private fun HistoryScreenContent(
|
||||
when (item) {
|
||||
is HistoryUiModel.Header -> {
|
||||
ListGroupHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
text = relativeDateText(item.date),
|
||||
)
|
||||
}
|
||||
is HistoryUiModel.Item -> {
|
||||
val value = item.item
|
||||
HistoryItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
history = value,
|
||||
onClickCover = { onClickCover(value) },
|
||||
onClickResume = { onClickResume(value) },
|
||||
|
||||
@@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -35,6 +37,7 @@ import tachiyomi.domain.library.model.sort
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import tachiyomi.presentation.core.components.BaseSortItem
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.HeadingItem
|
||||
import tachiyomi.presentation.core.components.IconItem
|
||||
@@ -155,7 +158,7 @@ private fun ColumnScope.FilterPage(
|
||||
)
|
||||
// SY <--
|
||||
|
||||
val trackers = remember { screenModel.trackers }
|
||||
val trackers by screenModel.trackersFlow.collectAsState()
|
||||
when (trackers.size) {
|
||||
0 -> {
|
||||
// No trackers
|
||||
@@ -188,6 +191,7 @@ private fun ColumnScope.SortPage(
|
||||
category: Category?,
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
) {
|
||||
val trackers by screenModel.trackersFlow.collectAsState()
|
||||
// SY -->
|
||||
val globalSortMode by screenModel.libraryPreferences.sortingMode().collectAsState()
|
||||
val sortingMode = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
|
||||
@@ -196,40 +200,58 @@ private fun ColumnScope.SortPage(
|
||||
globalSortMode.type
|
||||
}
|
||||
val sortDescending = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
|
||||
category.sort.isAscending
|
||||
!category.sort.isAscending
|
||||
} else {
|
||||
globalSortMode.isAscending
|
||||
}.not()
|
||||
!globalSortMode.isAscending
|
||||
}
|
||||
val hasSortTags by remember {
|
||||
screenModel.libraryPreferences.sortTagsForLibrary().changes()
|
||||
.map { it.isNotEmpty() }
|
||||
}.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty())
|
||||
// SY <--
|
||||
|
||||
val trackerSortOption =
|
||||
if (screenModel.trackers.isEmpty()) {
|
||||
emptyList()
|
||||
val options = remember(trackers.isEmpty()/* SY --> */, hasSortTags/* SY <-- */) {
|
||||
val trackerMeanPair = if (trackers.isNotEmpty()) {
|
||||
MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean
|
||||
} else {
|
||||
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
||||
null
|
||||
}
|
||||
|
||||
listOfNotNull(
|
||||
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,
|
||||
// SY -->
|
||||
if (hasSortTags) {
|
||||
val tagSortPair = if (hasSortTags) {
|
||||
SYMR.strings.tag_sorting to LibrarySort.Type.TagList
|
||||
} else {
|
||||
null
|
||||
},
|
||||
}
|
||||
// SY <--
|
||||
).plus(trackerSortOption).map { (titleRes, mode) ->
|
||||
listOfNotNull(
|
||||
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,
|
||||
trackerMeanPair,
|
||||
// SY -->
|
||||
tagSortPair,
|
||||
// SY <--
|
||||
MR.strings.action_sort_random to LibrarySort.Type.Random,
|
||||
)
|
||||
}
|
||||
|
||||
options.map { (titleRes, mode) ->
|
||||
if (mode == LibrarySort.Type.Random) {
|
||||
BaseSortItem(
|
||||
label = stringResource(titleRes),
|
||||
icon = Icons.Default.Refresh
|
||||
.takeIf { sortingMode == LibrarySort.Type.Random },
|
||||
onClick = {
|
||||
screenModel.setSort(category, mode, LibrarySort.Direction.Ascending)
|
||||
},
|
||||
)
|
||||
return@map
|
||||
}
|
||||
SortItem(
|
||||
label = stringResource(titleRes),
|
||||
sortDescending = sortDescending.takeIf { sortingMode == mode },
|
||||
@@ -241,7 +263,11 @@ private fun ColumnScope.SortPage(
|
||||
} else {
|
||||
LibrarySort.Direction.Descending
|
||||
}
|
||||
else -> if (sortDescending) LibrarySort.Direction.Descending else LibrarySort.Direction.Ascending
|
||||
else -> if (sortDescending) {
|
||||
LibrarySort.Direction.Descending
|
||||
} else {
|
||||
LibrarySort.Direction.Ascending
|
||||
}
|
||||
}
|
||||
screenModel.setSort(category, mode, direction)
|
||||
},
|
||||
@@ -346,12 +372,13 @@ private fun ColumnScope.GroupPage(
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
hasCategories: Boolean,
|
||||
) {
|
||||
val groups = remember(hasCategories, screenModel.trackers) {
|
||||
val trackers by screenModel.trackersFlow.collectAsState()
|
||||
val groups = remember(hasCategories, trackers) {
|
||||
buildList {
|
||||
add(LibraryGroup.BY_DEFAULT)
|
||||
add(LibraryGroup.BY_SOURCE)
|
||||
add(LibraryGroup.BY_STATUS)
|
||||
if (screenModel.trackers.isNotEmpty()) {
|
||||
if (trackers.isNotEmpty()) {
|
||||
add(LibraryGroup.BY_TRACK_STATUS)
|
||||
}
|
||||
if (hasCategories) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shadow
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.manga.components.MangaCover
|
||||
@@ -42,19 +43,26 @@ import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.BadgeGroup
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.selectedBackground
|
||||
import tachiyomi.domain.manga.model.MangaCover as MangaCoverModel
|
||||
|
||||
object CommonMangaItemDefaults {
|
||||
val GridHorizontalSpacer = 4.dp
|
||||
val GridVerticalSpacer = 4.dp
|
||||
|
||||
@Suppress("ConstPropertyName")
|
||||
const val BrowseFavoriteCoverAlpha = 0.34f
|
||||
}
|
||||
|
||||
private val ContinueReadingButtonSize = 28.dp
|
||||
private val ContinueReadingButtonSizeSmall = 28.dp
|
||||
private val ContinueReadingButtonSizeLarge = 32.dp
|
||||
|
||||
private val ContinueReadingButtonIconSizeSmall = 16.dp
|
||||
private val ContinueReadingButtonIconSizeLarge = 20.dp
|
||||
|
||||
private val ContinueReadingButtonGridPadding = 6.dp
|
||||
private val ContinueReadingButtonListSpacing = 8.dp
|
||||
|
||||
private const val GridSelectedCoverAlpha = 0.76f
|
||||
private const val GRID_SELECTED_COVER_ALPHA = 0.76f
|
||||
|
||||
/**
|
||||
* Layout of grid list item with title overlaying the cover.
|
||||
@@ -62,7 +70,7 @@ private const val GridSelectedCoverAlpha = 0.76f
|
||||
*/
|
||||
@Composable
|
||||
fun MangaCompactGridItem(
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
coverData: MangaCoverModel,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
isSelected: Boolean = false,
|
||||
@@ -82,7 +90,7 @@ fun MangaCompactGridItem(
|
||||
MangaCover.Book(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
||||
.alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
|
||||
data = coverData,
|
||||
)
|
||||
},
|
||||
@@ -96,10 +104,12 @@ fun MangaCompactGridItem(
|
||||
)
|
||||
} else if (onClickContinueReading != null) {
|
||||
ContinueReadingButton(
|
||||
size = ContinueReadingButtonSizeLarge,
|
||||
iconSize = ContinueReadingButtonIconSizeLarge,
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier
|
||||
.padding(ContinueReadingButtonGridPadding)
|
||||
.align(Alignment.BottomEnd),
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -148,11 +158,13 @@ private fun BoxScope.CoverTextOverlay(
|
||||
)
|
||||
if (onClickContinueReading != null) {
|
||||
ContinueReadingButton(
|
||||
size = ContinueReadingButtonSizeSmall,
|
||||
iconSize = ContinueReadingButtonIconSizeSmall,
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier.padding(
|
||||
end = ContinueReadingButtonGridPadding,
|
||||
bottom = ContinueReadingButtonGridPadding,
|
||||
),
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -163,7 +175,7 @@ private fun BoxScope.CoverTextOverlay(
|
||||
*/
|
||||
@Composable
|
||||
fun MangaComfortableGridItem(
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
coverData: MangaCoverModel,
|
||||
title: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
@@ -185,7 +197,7 @@ fun MangaComfortableGridItem(
|
||||
MangaCover.Book(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
||||
.alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
|
||||
data = coverData,
|
||||
)
|
||||
},
|
||||
@@ -194,10 +206,12 @@ fun MangaComfortableGridItem(
|
||||
content = {
|
||||
if (onClickContinueReading != null) {
|
||||
ContinueReadingButton(
|
||||
size = ContinueReadingButtonSizeLarge,
|
||||
iconSize = ContinueReadingButtonIconSizeLarge,
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier
|
||||
.padding(ContinueReadingButtonGridPadding)
|
||||
.align(Alignment.BottomEnd),
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -309,14 +323,14 @@ private fun GridItemSelectable(
|
||||
private fun Modifier.selectedOutline(
|
||||
isSelected: Boolean,
|
||||
color: Color,
|
||||
) = this then drawBehind { if (isSelected) drawRect(color = color) }
|
||||
) = drawBehind { if (isSelected) drawRect(color = color) }
|
||||
|
||||
/**
|
||||
* Layout of list item.
|
||||
*/
|
||||
@Composable
|
||||
fun MangaListItem(
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
coverData: MangaCoverModel,
|
||||
title: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
@@ -354,8 +368,10 @@ fun MangaListItem(
|
||||
BadgeGroup(content = badge)
|
||||
if (onClickContinueReading != null) {
|
||||
ContinueReadingButton(
|
||||
size = ContinueReadingButtonSizeSmall,
|
||||
iconSize = ContinueReadingButtonIconSizeSmall,
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -363,23 +379,25 @@ fun MangaListItem(
|
||||
|
||||
@Composable
|
||||
private fun ContinueReadingButton(
|
||||
size: Dp,
|
||||
iconSize: Dp,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onClickContinueReading: () -> Unit,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
FilledIconButton(
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier.size(ContinueReadingButtonSize),
|
||||
onClick = onClick,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
colors = IconButtonDefaults.filledIconButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
|
||||
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
|
||||
),
|
||||
modifier = Modifier.size(size),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = stringResource(MR.strings.action_resume),
|
||||
modifier = Modifier.size(16.dp),
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ fun LibraryContent(
|
||||
isRefreshing = false
|
||||
}
|
||||
},
|
||||
enabled = { notSelectionMode },
|
||||
enabled = notSelectionMode,
|
||||
) {
|
||||
LibraryPager(
|
||||
state = pagerState,
|
||||
|
||||
@@ -41,6 +41,7 @@ fun LibraryToolbar(
|
||||
onClickSyncNow: () -> Unit,
|
||||
// SY -->
|
||||
onClickSyncExh: (() -> Unit)?,
|
||||
isSyncEnabled: Boolean,
|
||||
// SY <--
|
||||
searchQuery: String?,
|
||||
onSearchQueryChange: (String?) -> Unit,
|
||||
@@ -64,6 +65,7 @@ fun LibraryToolbar(
|
||||
onClickSyncNow = onClickSyncNow,
|
||||
// SY -->
|
||||
onClickSyncExh = onClickSyncExh,
|
||||
isSyncEnabled = isSyncEnabled,
|
||||
// SY <--
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
@@ -82,6 +84,7 @@ private fun LibraryRegularToolbar(
|
||||
onClickSyncNow: () -> Unit,
|
||||
// SY -->
|
||||
onClickSyncExh: (() -> Unit)?,
|
||||
isSyncEnabled: Boolean,
|
||||
// SY <--
|
||||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
) {
|
||||
@@ -128,10 +131,6 @@ private fun LibraryRegularToolbar(
|
||||
title = stringResource(MR.strings.action_open_random_manga),
|
||||
onClick = onClickOpenRandomManga,
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(SYMR.strings.sync_library),
|
||||
onClick = onClickSyncNow,
|
||||
),
|
||||
).builder().apply {
|
||||
// SY -->
|
||||
if (onClickSyncExh != null) {
|
||||
@@ -142,6 +141,14 @@ private fun LibraryRegularToolbar(
|
||||
),
|
||||
)
|
||||
}
|
||||
if (isSyncEnabled) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(SYMR.strings.sync_library),
|
||||
onClick = onClickSyncNow,
|
||||
),
|
||||
)
|
||||
}
|
||||
// SY <--
|
||||
}.build(),
|
||||
)
|
||||
|
||||
@@ -77,6 +77,7 @@ import eu.kanade.tachiyomi.source.online.english.Pururin
|
||||
import eu.kanade.tachiyomi.source.online.english.Tsumino
|
||||
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
||||
import eu.kanade.tachiyomi.ui.manga.MergedMangaData
|
||||
import eu.kanade.tachiyomi.ui.manga.PagePreviewState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.MetadataUtil
|
||||
@@ -102,8 +103,7 @@ import tachiyomi.presentation.core.components.material.ExtendedFloatingActionBut
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrollingUp
|
||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
@@ -431,7 +431,7 @@ private fun MangaScreenSmallImpl(
|
||||
},
|
||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||
expanded = chapterListState.shouldExpandFAB(),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -441,7 +441,7 @@ private fun MangaScreenSmallImpl(
|
||||
PullRefresh(
|
||||
refreshing = state.isRefreshingData,
|
||||
onRefresh = onRefresh,
|
||||
enabled = { !isAnySelected },
|
||||
enabled = !isAnySelected,
|
||||
indicatorPadding = PaddingValues(top = topPadding),
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
@@ -466,13 +466,9 @@ private fun MangaScreenSmallImpl(
|
||||
MangaInfoBox(
|
||||
isTabletUi = false,
|
||||
appBarPadding = topPadding,
|
||||
title = state.manga.title,
|
||||
author = state.manga.author,
|
||||
artist = state.manga.artist,
|
||||
manga = state.manga,
|
||||
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
|
||||
isStubSource = remember { state.source is StubSource },
|
||||
coverDataProvider = { state.manga },
|
||||
status = state.manga.status,
|
||||
onCoverClick = onCoverClicked,
|
||||
doSearch = onSearch,
|
||||
)
|
||||
@@ -578,6 +574,7 @@ private fun MangaScreenSmallImpl(
|
||||
|
||||
sharedChapterItems(
|
||||
manga = state.manga,
|
||||
mergedData = state.mergedData,
|
||||
chapters = listItem,
|
||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
@@ -755,7 +752,7 @@ fun MangaScreenLargeImpl(
|
||||
},
|
||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||
expanded = chapterListState.shouldExpandFAB(),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -763,7 +760,7 @@ fun MangaScreenLargeImpl(
|
||||
PullRefresh(
|
||||
refreshing = state.isRefreshingData,
|
||||
onRefresh = onRefresh,
|
||||
enabled = { !isAnySelected },
|
||||
enabled = !isAnySelected,
|
||||
indicatorPadding = PaddingValues(
|
||||
start = insetPadding.calculateStartPadding(layoutDirection),
|
||||
top = with(density) { topBarHeight.toDp() },
|
||||
@@ -784,13 +781,9 @@ fun MangaScreenLargeImpl(
|
||||
MangaInfoBox(
|
||||
isTabletUi = true,
|
||||
appBarPadding = contentPadding.calculateTopPadding(),
|
||||
title = state.manga.title,
|
||||
author = state.manga.author,
|
||||
artist = state.manga.artist,
|
||||
manga = state.manga,
|
||||
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
|
||||
isStubSource = remember { state.source is StubSource },
|
||||
coverDataProvider = { state.manga },
|
||||
status = state.manga.status,
|
||||
onCoverClick = onCoverClicked,
|
||||
doSearch = onSearch,
|
||||
)
|
||||
@@ -880,6 +873,7 @@ fun MangaScreenLargeImpl(
|
||||
|
||||
sharedChapterItems(
|
||||
manga = state.manga,
|
||||
mergedData = state.mergedData,
|
||||
chapters = listItem,
|
||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
@@ -944,6 +938,7 @@ private fun SharedMangaBottomActionMenu(
|
||||
|
||||
private fun LazyListScope.sharedChapterItems(
|
||||
manga: Manga,
|
||||
mergedData: MergedMangaData?,
|
||||
chapters: List<ChapterList>,
|
||||
isAnyChapterSelected: Boolean,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
@@ -995,7 +990,9 @@ private fun LazyListScope.sharedChapterItems(
|
||||
// SY <--
|
||||
},
|
||||
readProgress = item.chapter.lastPageRead
|
||||
.takeIf { /* SY --> */(!item.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L }
|
||||
.takeIf {
|
||||
/* SY --> */(!item.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L
|
||||
}
|
||||
?.let {
|
||||
stringResource(
|
||||
MR.strings.chapter_progress,
|
||||
@@ -1011,7 +1008,8 @@ private fun LazyListScope.sharedChapterItems(
|
||||
read = item.chapter.read,
|
||||
bookmark = item.chapter.bookmark,
|
||||
selected = item.selected,
|
||||
downloadIndicatorEnabled = !isAnyChapterSelected && !manga.isLocal(),
|
||||
downloadIndicatorEnabled =
|
||||
!isAnyChapterSelected && !(mergedData?.manga?.get(item.chapter.mangaId) ?: manga).isLocal(),
|
||||
downloadStateProvider = { item.downloadState },
|
||||
downloadProgressProvider = { item.downloadProgress },
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
|
||||
@@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -60,6 +60,6 @@ private fun MissingChaptersWarning(count: Int) {
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error.copy(alpha = SecondaryItemAlpha),
|
||||
color = MaterialTheme.colorScheme.error.copy(alpha = SECONDARY_ALPHA),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
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
|
||||
@@ -41,8 +40,8 @@ import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import me.saket.swipe.SwipeableActionsBox
|
||||
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.components.material.DISABLED_ALPHA
|
||||
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.selectedBackground
|
||||
|
||||
@@ -69,9 +68,6 @@ fun MangaChapterListItem(
|
||||
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val textAlpha = if (read) ReadItemAlpha else 1f
|
||||
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
|
||||
|
||||
val start = getSwipeAction(
|
||||
action = chapterSwipeStartAction,
|
||||
read = read,
|
||||
@@ -136,29 +132,39 @@ fun MangaChapterListItem(
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = LocalContentColor.current.copy(alpha = textAlpha),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
onTextLayout = { textHeight = it.size.height },
|
||||
color = LocalContentColor.current.copy(alpha = if (read) DISABLED_ALPHA else 1f),
|
||||
)
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.alpha(textSubtitleAlpha)) {
|
||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||
Row {
|
||||
val subtitleStyle = MaterialTheme.typography.bodySmall
|
||||
.merge(
|
||||
color = LocalContentColor.current
|
||||
.copy(alpha = if (read) DISABLED_ALPHA else SECONDARY_ALPHA),
|
||||
)
|
||||
ProvideTextStyle(value = subtitleStyle) {
|
||||
if (date != null) {
|
||||
Text(
|
||||
text = date,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
if (readProgress != null || scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText()
|
||||
if (readProgress != null ||
|
||||
scanlator != null/* SY --> */ ||
|
||||
sourceName != null/* SY <-- */
|
||||
) {
|
||||
DotSeparatorText()
|
||||
}
|
||||
}
|
||||
if (readProgress != null) {
|
||||
Text(
|
||||
text = readProgress,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = LocalContentColor.current.copy(alpha = ReadItemAlpha),
|
||||
color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
|
||||
)
|
||||
if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText()
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.view.updatePadding
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.ImageRequest
|
||||
@@ -56,7 +57,7 @@ import tachiyomi.presentation.core.util.clickableNoIndication
|
||||
|
||||
@Composable
|
||||
fun MangaCoverDialog(
|
||||
coverDataProvider: () -> Manga,
|
||||
manga: Manga,
|
||||
isCustomCover: Boolean,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onShareClick: () -> Unit,
|
||||
@@ -166,7 +167,7 @@ fun MangaCoverDialog(
|
||||
},
|
||||
update = { view ->
|
||||
val request = ImageRequest.Builder(view.context)
|
||||
.data(coverDataProvider())
|
||||
.data(manga)
|
||||
.size(Size.ORIGINAL)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.target { image ->
|
||||
|
||||
@@ -102,9 +102,12 @@ fun SetIntervalDialog(
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(MaterialTheme.padding.small))
|
||||
} else {
|
||||
Text(
|
||||
stringResource(MR.strings.manga_interval_expected_update_null),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(MaterialTheme.padding.small))
|
||||
|
||||
if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) {
|
||||
Text(stringResource(MR.strings.manga_interval_custom_amount))
|
||||
|
||||
@@ -2,6 +2,7 @@ package eu.kanade.presentation.manga.components
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||
@@ -74,6 +75,8 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
@@ -81,6 +84,7 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
||||
import tachiyomi.presentation.core.components.material.TextButton
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
@@ -97,13 +101,9 @@ private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTIL
|
||||
fun MangaInfoBox(
|
||||
isTabletUi: Boolean,
|
||||
appBarPadding: Dp,
|
||||
title: String,
|
||||
author: String?,
|
||||
artist: String?,
|
||||
manga: Manga,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
coverDataProvider: () -> Manga,
|
||||
status: Long,
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -115,7 +115,10 @@ fun MangaInfoBox(
|
||||
MaterialTheme.colorScheme.background,
|
||||
)
|
||||
AsyncImage(
|
||||
model = coverDataProvider(),
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(manga)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
@@ -135,28 +138,20 @@ fun MangaInfoBox(
|
||||
if (!isTabletUi) {
|
||||
MangaAndSourceTitlesSmall(
|
||||
appBarPadding = appBarPadding,
|
||||
coverDataProvider = coverDataProvider,
|
||||
onCoverClick = onCoverClick,
|
||||
title = title,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
status = status,
|
||||
manga = manga,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
onCoverClick = onCoverClick,
|
||||
doSearch = doSearch,
|
||||
)
|
||||
} else {
|
||||
MangaAndSourceTitlesLarge(
|
||||
appBarPadding = appBarPadding,
|
||||
coverDataProvider = coverDataProvider,
|
||||
onCoverClick = onCoverClick,
|
||||
title = title,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
status = status,
|
||||
manga = manga,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
onCoverClick = onCoverClick,
|
||||
doSearch = doSearch,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -180,7 +175,7 @@ fun MangaActionRow(
|
||||
// SY <--
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
||||
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = DISABLED_ALPHA)
|
||||
|
||||
// TODO: show something better when using custom interval
|
||||
val nextUpdateDays = remember(nextUpdate) {
|
||||
@@ -289,7 +284,8 @@ fun ExpandableMangaDescription(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.padding(vertical = 12.dp)
|
||||
.animateContentSize(),
|
||||
.animateContentSize(animationSpec = spring())
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
var tagSelected by remember { mutableStateOf("") }
|
||||
@@ -374,15 +370,11 @@ fun ExpandableMangaDescription(
|
||||
@Composable
|
||||
private fun MangaAndSourceTitlesLarge(
|
||||
appBarPadding: Dp,
|
||||
coverDataProvider: () -> Manga,
|
||||
onCoverClick: () -> Unit,
|
||||
title: String,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
author: String?,
|
||||
artist: String?,
|
||||
status: Long,
|
||||
manga: Manga,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -392,19 +384,22 @@ private fun MangaAndSourceTitlesLarge(
|
||||
) {
|
||||
MangaCover.Book(
|
||||
modifier = Modifier.fillMaxWidth(0.65f),
|
||||
data = coverDataProvider(),
|
||||
data = ImageRequest.Builder(LocalContext.current)
|
||||
.data(manga)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = stringResource(MR.strings.manga_cover),
|
||||
onClick = onCoverClick,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
MangaContentInfo(
|
||||
title = title,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
status = status,
|
||||
title = manga.title,
|
||||
author = manga.author,
|
||||
artist = manga.artist,
|
||||
status = manga.status,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
doSearch = doSearch,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
@@ -413,15 +408,11 @@ private fun MangaAndSourceTitlesLarge(
|
||||
@Composable
|
||||
private fun MangaAndSourceTitlesSmall(
|
||||
appBarPadding: Dp,
|
||||
coverDataProvider: () -> Manga,
|
||||
onCoverClick: () -> Unit,
|
||||
title: String,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
author: String?,
|
||||
artist: String?,
|
||||
status: Long,
|
||||
manga: Manga,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -434,7 +425,10 @@ private fun MangaAndSourceTitlesSmall(
|
||||
modifier = Modifier
|
||||
.sizeIn(maxWidth = 100.dp)
|
||||
.align(Alignment.Top),
|
||||
data = coverDataProvider(),
|
||||
data = ImageRequest.Builder(LocalContext.current)
|
||||
.data(manga)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = stringResource(MR.strings.manga_cover),
|
||||
onClick = onCoverClick,
|
||||
)
|
||||
@@ -442,13 +436,13 @@ private fun MangaAndSourceTitlesSmall(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
MangaContentInfo(
|
||||
title = title,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
status = status,
|
||||
title = manga.title,
|
||||
author = manga.author,
|
||||
artist = manga.artist,
|
||||
status = manga.status,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
doSearch = doSearch,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -457,12 +451,12 @@ private fun MangaAndSourceTitlesSmall(
|
||||
@Composable
|
||||
private fun ColumnScope.MangaContentInfo(
|
||||
title: String,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
author: String?,
|
||||
artist: String?,
|
||||
status: Long,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SuggestionChip
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -141,7 +141,7 @@ fun TagsChip(
|
||||
border: ChipBorder? = SuggestionChipDefaults.suggestionChipBorder(),
|
||||
borderM3: BorderStroke? = SuggestionChipDefaultsM3.suggestionChipBorder(enabled = true),
|
||||
) {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
if (onClick != null) {
|
||||
SuggestionChip(
|
||||
modifier = modifier,
|
||||
|
||||
@@ -44,7 +44,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
private fun PagePreviewLoading(
|
||||
setMaxWidth: (Dp) -> Unit
|
||||
setMaxWidth: (Dp) -> Unit,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
Box(
|
||||
@@ -63,7 +63,7 @@ private fun PagePreviewLoading(
|
||||
@Composable
|
||||
private fun PagePreviewRow(
|
||||
onOpenPage: (Int) -> Unit,
|
||||
items: ImmutableList<PagePreview>
|
||||
items: ImmutableList<PagePreview>,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -88,7 +88,7 @@ private fun PagePreviewMore(
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
TextButton(onClick = onMorePreviewsClicked) {
|
||||
Text(stringResource(SYMR.strings.more_previews))
|
||||
@@ -116,7 +116,7 @@ fun PagePreviews(
|
||||
pagePreviewState.pagePreviews.take(rowCount * itemPerRowCount).chunked(itemPerRowCount).forEach {
|
||||
PagePreviewRow(
|
||||
onOpenPage = onOpenPage,
|
||||
items = remember(it) { it.toImmutableList() }
|
||||
items = remember(it) { it.toImmutableList() },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ fun LazyListScope.PagePreviewItems(
|
||||
) {
|
||||
PagePreviewRow(
|
||||
onOpenPage = onOpenPage,
|
||||
items = remember(it) { it.toImmutableList() }
|
||||
items = remember(it) { it.toImmutableList() },
|
||||
)
|
||||
}
|
||||
item(
|
||||
|
||||
@@ -32,8 +32,6 @@ 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(
|
||||
@@ -97,8 +95,8 @@ fun ScanlatorFilterDialog(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
},
|
||||
properties = DialogProperties(
|
||||
@@ -111,8 +109,14 @@ fun ScanlatorFilterDialog(
|
||||
}
|
||||
} else {
|
||||
FlowRow {
|
||||
TextButton(onClick = mutableExcludedScanlators::clear) {
|
||||
Text(text = stringResource(MR.strings.action_reset))
|
||||
if (mutableExcludedScanlators.isEmpty()) {
|
||||
TextButton(onClick = { mutableExcludedScanlators.addAll(availableScanlators) }) {
|
||||
Text(text = stringResource(MR.strings.action_select_all))
|
||||
}
|
||||
} else {
|
||||
TextButton(onClick = mutableExcludedScanlators::clear) {
|
||||
Text(text = stringResource(MR.strings.action_reset))
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
|
||||
@@ -75,7 +75,7 @@ private fun NewUpdateScreenPreview() {
|
||||
changelogInfo = """
|
||||
## Yay
|
||||
Foobar
|
||||
|
||||
|
||||
### More info
|
||||
- Hello
|
||||
- World
|
||||
|
||||
@@ -14,11 +14,13 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
@@ -28,19 +30,24 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
|
||||
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
internal class PermissionStep : OnboardingStep {
|
||||
|
||||
private val privacyPreferences: PrivacyPreferences by injectLazy()
|
||||
|
||||
private var notificationGranted by mutableStateOf(false)
|
||||
private var batteryGranted by mutableStateOf(false)
|
||||
|
||||
@@ -73,7 +80,7 @@ internal class PermissionStep : OnboardingStep {
|
||||
}
|
||||
|
||||
Column {
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(MR.strings.onboarding_permission_install_apps),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description),
|
||||
granted = installGranted,
|
||||
@@ -89,7 +96,7 @@ internal class PermissionStep : OnboardingStep {
|
||||
// no-op. resulting checks is being done on resume
|
||||
},
|
||||
)
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(MR.strings.onboarding_permission_notifications),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_notifications_description),
|
||||
granted = notificationGranted,
|
||||
@@ -97,7 +104,7 @@ internal class PermissionStep : OnboardingStep {
|
||||
)
|
||||
}
|
||||
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description),
|
||||
granted = batteryGranted,
|
||||
@@ -109,6 +116,29 @@ internal class PermissionStep : OnboardingStep {
|
||||
context.startActivity(intent)
|
||||
},
|
||||
)
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
|
||||
val crashlyticsPref = privacyPreferences.crashlytics()
|
||||
val crashlytics by crashlyticsPref.collectAsState()
|
||||
PermissionSwitch(
|
||||
title = stringResource(MR.strings.onboarding_permission_crashlytics),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
|
||||
granted = crashlytics,
|
||||
onToggleChange = crashlyticsPref::set,
|
||||
)
|
||||
|
||||
val analyticsPref = privacyPreferences.analytics()
|
||||
val analytics by analyticsPref.collectAsState()
|
||||
PermissionSwitch(
|
||||
title = stringResource(MR.strings.onboarding_permission_analytics),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
|
||||
granted = analytics,
|
||||
onToggleChange = analyticsPref::set,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +157,7 @@ internal class PermissionStep : OnboardingStep {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PermissionItem(
|
||||
private fun PermissionCheckbox(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
granted: Boolean,
|
||||
@@ -157,4 +187,26 @@ internal class PermissionStep : OnboardingStep {
|
||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PermissionSwitch(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
granted: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onToggleChange: (Boolean) -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
headlineContent = { Text(text = title) },
|
||||
supportingContent = { Text(text = subtitle) },
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = granted,
|
||||
onCheckedChange = onToggleChange,
|
||||
)
|
||||
},
|
||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.structuralEqualityPolicy
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.track.service.TrackPreferences
|
||||
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
||||
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
||||
@@ -23,8 +23,6 @@ import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.presentation.core.components.SliderItem
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
|
||||
val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp }
|
||||
@@ -156,16 +154,14 @@ internal fun PreferenceItem(
|
||||
)
|
||||
}
|
||||
is Preference.PreferenceItem.TrackerPreference -> {
|
||||
val uName by Injekt.get<TrackPreferences>()
|
||||
.trackUsername(item.tracker)
|
||||
.collectAsState()
|
||||
item.tracker.run {
|
||||
TrackingPreferenceWidget(
|
||||
tracker = this,
|
||||
checked = uName.isNotEmpty(),
|
||||
onClick = { if (isLoggedIn) item.logout() else item.login() },
|
||||
)
|
||||
val isLoggedIn by item.tracker.let { tracker ->
|
||||
tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
|
||||
}
|
||||
TrackingPreferenceWidget(
|
||||
tracker = item.tracker,
|
||||
checked = isLoggedIn,
|
||||
onClick = { if (isLoggedIn) item.logout() else item.login() },
|
||||
)
|
||||
}
|
||||
is Preference.PreferenceItem.InfoPreference -> {
|
||||
InfoWidget(text = item.title)
|
||||
|
||||
+6
-2
@@ -3,7 +3,6 @@ package eu.kanade.presentation.more.settings.screen
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.webkit.WebStorage
|
||||
import android.webkit.WebView
|
||||
@@ -300,6 +299,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
try {
|
||||
// OkHttp checks for valid values internally
|
||||
Headers.Builder().add("User-Agent", it)
|
||||
context.toast(MR.strings.requires_app_restart)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
context.toast(MR.strings.error_user_agent_string_invalid)
|
||||
return@EditTextPreference false
|
||||
@@ -376,7 +376,11 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
chooseColorProfile.launch(arrayOf("*/*"))
|
||||
},
|
||||
),
|
||||
)
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = basePreferences.alwaysUseSSIVToDecode(),
|
||||
title = stringResource(MR.strings.pref_always_use_ssiv_to_decode),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -180,11 +180,15 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = previewsRowCount,
|
||||
title = stringResource(SYMR.strings.pref_previews_row_count),
|
||||
subtitle = if (previewsRowCount > 0) pluralStringResource(
|
||||
SYMR.plurals.row_count,
|
||||
previewsRowCount,
|
||||
previewsRowCount,
|
||||
) else stringResource(MR.strings.disabled),
|
||||
subtitle = if (previewsRowCount > 0) {
|
||||
pluralStringResource(
|
||||
SYMR.plurals.row_count,
|
||||
previewsRowCount,
|
||||
previewsRowCount,
|
||||
)
|
||||
} else {
|
||||
stringResource(MR.strings.disabled)
|
||||
},
|
||||
min = 0,
|
||||
max = 10,
|
||||
onValueChanged = {
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
||||
pref = uiPreferences.feedTabInFront(),
|
||||
title = stringResource(SYMR.strings.pref_feed_position),
|
||||
subtitle = stringResource(SYMR.strings.pref_feed_position_summery),
|
||||
enabled = hideFeedTab.not()
|
||||
enabled = hideFeedTab.not(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+30
-4
@@ -127,7 +127,17 @@ object SettingsDataScreen : SearchableSettings {
|
||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
// For some reason InkBook devices do not implement the SAF properly. Persistable URI grants do not
|
||||
// work. However, simply retrieving the URI and using it works fine for these devices. Access is not
|
||||
// revoked after the app is closed or the device is restarted.
|
||||
// This also holds for some Samsung devices. Thus, we simply execute inside of a try-catch block and
|
||||
// ignore the exception if it is thrown.
|
||||
try {
|
||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
} catch (e: SecurityException) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
context.toast(MR.strings.file_picker_uri_permission_unsupported)
|
||||
}
|
||||
|
||||
UniFile.fromUri(context, uri)?.let {
|
||||
storageDirPref.set(it.uri.toString())
|
||||
@@ -384,11 +394,23 @@ object SettingsDataScreen : SearchableSettings {
|
||||
syncServiceType: SyncManager.SyncService,
|
||||
syncPreferences: SyncPreferences,
|
||||
): List<Preference> {
|
||||
return when (syncServiceType) {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val preferences = when (syncServiceType) {
|
||||
SyncManager.SyncService.NONE -> emptyList()
|
||||
SyncManager.SyncService.SYNCYOMI -> getSelfHostPreferences(syncPreferences)
|
||||
SyncManager.SyncService.GOOGLE_DRIVE -> getGoogleDrivePreferences()
|
||||
}
|
||||
|
||||
return if (syncServiceType != SyncManager.SyncService.NONE) {
|
||||
preferences + Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(SYMR.strings.pref_choose_what_to_sync),
|
||||
onClick = {
|
||||
navigator.push(SyncSettingsSelector())
|
||||
},
|
||||
)
|
||||
} else {
|
||||
preferences
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -505,7 +527,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||
|
||||
@Composable
|
||||
private fun getSyncNowPref(): Preference.PreferenceGroup {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(SYMR.strings.pref_sync_now_group_title),
|
||||
preferenceItems = persistentListOf(
|
||||
@@ -514,7 +536,11 @@ object SettingsDataScreen : SearchableSettings {
|
||||
title = stringResource(SYMR.strings.pref_sync_now),
|
||||
subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle),
|
||||
onClick = {
|
||||
navigator.push(SyncSettingsSelector())
|
||||
if (!SyncDataJob.isRunning(context)) {
|
||||
SyncDataJob.startNow(context, manual = true)
|
||||
} else {
|
||||
context.toast(SYMR.strings.sync_in_progress)
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
+7
-2
@@ -15,7 +15,6 @@ import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
@@ -35,7 +34,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
||||
@Composable
|
||||
override fun getPreferences(): List<Preference> {
|
||||
val getCategories = remember { Injekt.get<GetCategories>() }
|
||||
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() })
|
||||
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
||||
|
||||
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
|
||||
return listOf(
|
||||
@@ -120,6 +119,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
||||
allCategories: List<Category>,
|
||||
): Preference.PreferenceGroup {
|
||||
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters()
|
||||
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly()
|
||||
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories()
|
||||
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude()
|
||||
|
||||
@@ -152,6 +152,11 @@ object SettingsDownloadScreen : SearchableSettings {
|
||||
pref = downloadNewChaptersPref,
|
||||
title = stringResource(MR.strings.pref_download_new),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = downloadNewUnreadChaptersOnlyPref,
|
||||
title = stringResource(MR.strings.pref_download_new_unread_chapters_only),
|
||||
enabled = downloadNewChapters,
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.categories),
|
||||
subtitle = getCategoriesLabel(
|
||||
|
||||
+1
-8
@@ -25,7 +25,6 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import tachiyomi.domain.UnsortedPreferences
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
||||
@@ -57,9 +56,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
override fun getPreferences(): List<Preference> {
|
||||
val getCategories = remember { Injekt.get<GetCategories>() }
|
||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
||||
val allCategories by getCategories.subscribe().collectAsState(
|
||||
initial = runBlocking { getCategories.await() },
|
||||
)
|
||||
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
||||
// SY -->
|
||||
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
||||
// SY <--
|
||||
@@ -84,9 +81,6 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
val scope = rememberCoroutineScope()
|
||||
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
|
||||
|
||||
val defaultCategory by libraryPreferences.defaultCategory().collectAsState()
|
||||
val selectedCategory = allCategories.find { it.id == defaultCategory.toLong() }
|
||||
|
||||
// For default category
|
||||
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
|
||||
allCategories.fastMap { it.id.toInt() }
|
||||
@@ -108,7 +102,6 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = libraryPreferences.defaultCategory(),
|
||||
title = stringResource(MR.strings.default_category),
|
||||
subtitle = selectedCategory?.visualName ?: stringResource(MR.strings.default_category_summary),
|
||||
entries = ids.zip(labels).toMap().toImmutableMap(),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
|
||||
+61
-5
@@ -17,6 +17,7 @@ import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@@ -88,12 +89,8 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
title = stringResource(MR.strings.pref_page_transitions),
|
||||
),
|
||||
SY <-- */
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPref.flashOnPageChange(),
|
||||
title = stringResource(MR.strings.pref_flash_page),
|
||||
subtitle = stringResource(MR.strings.pref_flash_page_summ),
|
||||
),
|
||||
getDisplayGroup(readerPreferences = readerPref),
|
||||
getEInkGroup(readerPreferences = readerPref),
|
||||
getReadingGroup(readerPreferences = readerPref),
|
||||
getPagedGroup(readerPreferences = readerPref),
|
||||
getWebtoonGroup(readerPreferences = readerPref),
|
||||
@@ -156,6 +153,65 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
val flashPageState by readerPreferences.flashOnPageChange().collectAsState()
|
||||
|
||||
val flashMillisPref = readerPreferences.flashDurationMillis()
|
||||
val flashMillis by flashMillisPref.collectAsState()
|
||||
|
||||
val flashIntervalPref = readerPreferences.flashPageInterval()
|
||||
val flashInterval by flashIntervalPref.collectAsState()
|
||||
|
||||
val flashColorPref = readerPreferences.flashColor()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = "E-Ink",
|
||||
preferenceItems = persistentListOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.flashOnPageChange(),
|
||||
title = stringResource(MR.strings.pref_flash_page),
|
||||
subtitle = stringResource(MR.strings.pref_flash_page_summ),
|
||||
),
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||
min = 1,
|
||||
max = 15,
|
||||
title = stringResource(MR.strings.pref_flash_duration),
|
||||
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||
onValueChanged = {
|
||||
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
|
||||
true
|
||||
},
|
||||
enabled = flashPageState,
|
||||
),
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = flashInterval,
|
||||
min = 1,
|
||||
max = 10,
|
||||
title = stringResource(MR.strings.pref_flash_page_interval),
|
||||
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||
onValueChanged = {
|
||||
flashIntervalPref.set(it)
|
||||
true
|
||||
},
|
||||
enabled = flashPageState,
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = flashColorPref,
|
||||
title = stringResource(MR.strings.pref_flash_with),
|
||||
entries = persistentMapOf(
|
||||
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
|
||||
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
|
||||
ReaderPreferences.FlashColor.WHITE_BLACK
|
||||
to stringResource(MR.strings.pref_flash_style_white_black),
|
||||
),
|
||||
enabled = flashPageState,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
|
||||
+12
-17
@@ -13,8 +13,10 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@@ -28,11 +30,8 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
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.focus.FocusRequester
|
||||
@@ -43,7 +42,6 @@ import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -88,9 +86,7 @@ class SettingsSearchScreen : Screen() {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
|
||||
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(TextFieldValue())
|
||||
}
|
||||
val textFieldState = rememberTextFieldState()
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Column {
|
||||
@@ -105,20 +101,19 @@ class SettingsSearchScreen : Screen() {
|
||||
},
|
||||
title = {
|
||||
BasicTextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = { textFieldValue = it },
|
||||
state = textFieldState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.runOnEnterKeyPressed(action = focusManager::clearFocus),
|
||||
textStyle = MaterialTheme.typography.bodyLarge
|
||||
.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
singleLine = true,
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions(onSearch = { focusManager.clearFocus() }),
|
||||
onKeyboardAction = { focusManager.clearFocus() },
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||
decorationBox = {
|
||||
if (textFieldValue.text.isEmpty()) {
|
||||
decorator = {
|
||||
if (textFieldState.text.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(MR.strings.action_search_settings),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -130,8 +125,8 @@ class SettingsSearchScreen : Screen() {
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
if (textFieldValue.text.isNotEmpty()) {
|
||||
IconButton(onClick = { textFieldValue = TextFieldValue() }) {
|
||||
if (textFieldState.text.isNotEmpty()) {
|
||||
IconButton(onClick = { textFieldState.clearText() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = null,
|
||||
@@ -146,7 +141,7 @@ class SettingsSearchScreen : Screen() {
|
||||
},
|
||||
) { contentPadding ->
|
||||
SearchResult(
|
||||
searchKey = textFieldValue.text,
|
||||
searchKey = textFieldState.text.toString(),
|
||||
listState = listState,
|
||||
contentPadding = contentPadding,
|
||||
) { result ->
|
||||
|
||||
+151
-115
@@ -45,6 +45,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesScreen
|
||||
@@ -70,10 +71,20 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
|
||||
@Composable
|
||||
override fun getPreferences(): List<Preference> {
|
||||
val context = LocalContext.current
|
||||
val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
|
||||
val authSupported = remember { context.isAuthenticationSupported() }
|
||||
val privacyPreferences = remember { Injekt.get<PrivacyPreferences>() }
|
||||
return listOf(
|
||||
getSecurityGroup(securityPreferences),
|
||||
getFirebaseGroup(privacyPreferences),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getSecurityGroup(
|
||||
securityPreferences: SecurityPreferences,
|
||||
): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
val authSupported = remember { context.isAuthenticationSupported() }
|
||||
val useAuthPref = securityPreferences.useAuthenticator()
|
||||
val useAuth by useAuthPref.collectAsState()
|
||||
|
||||
@@ -81,129 +92,132 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
val isCbzPasswordSet by remember { CbzCrypto.isPasswordSetState(scope) }.collectAsState()
|
||||
val passwordProtectDownloads by securityPreferences.passwordProtectDownloads().collectAsState()
|
||||
|
||||
return listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = useAuthPref,
|
||||
title = stringResource(MR.strings.lock_with_biometrics),
|
||||
enabled = authSupported,
|
||||
onValueChanged = {
|
||||
(context as FragmentActivity).authenticate(
|
||||
title = context.stringResource(MR.strings.lock_with_biometrics),
|
||||
)
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = securityPreferences.lockAppAfter(),
|
||||
title = stringResource(MR.strings.lock_when_idle),
|
||||
enabled = authSupported && useAuth,
|
||||
entries = LockAfterValues
|
||||
.associateWith {
|
||||
when (it) {
|
||||
-1 -> stringResource(MR.strings.lock_never)
|
||||
0 -> stringResource(MR.strings.lock_always)
|
||||
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_security),
|
||||
preferenceItems = persistentListOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = useAuthPref,
|
||||
title = stringResource(MR.strings.lock_with_biometrics),
|
||||
enabled = authSupported,
|
||||
onValueChanged = {
|
||||
(context as FragmentActivity).authenticate(
|
||||
title = context.stringResource(MR.strings.lock_with_biometrics),
|
||||
)
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = securityPreferences.lockAppAfter(),
|
||||
title = stringResource(MR.strings.lock_when_idle),
|
||||
enabled = authSupported && useAuth,
|
||||
entries = LockAfterValues
|
||||
.associateWith {
|
||||
when (it) {
|
||||
-1 -> stringResource(MR.strings.lock_never)
|
||||
0 -> stringResource(MR.strings.lock_always)
|
||||
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
|
||||
}
|
||||
}
|
||||
.toImmutableMap(),
|
||||
onValueChanged = {
|
||||
(context as FragmentActivity).authenticate(
|
||||
title = context.stringResource(MR.strings.lock_when_idle),
|
||||
)
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = securityPreferences.hideNotificationContent(),
|
||||
title = stringResource(MR.strings.hide_notification_content),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = securityPreferences.secureScreen(),
|
||||
title = stringResource(MR.strings.secure_screen),
|
||||
entries = SecurityPreferences.SecureScreenMode.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
),
|
||||
// SY -->
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = securityPreferences.passwordProtectDownloads(),
|
||||
title = stringResource(SYMR.strings.password_protect_downloads),
|
||||
subtitle = stringResource(SYMR.strings.password_protect_downloads_summary),
|
||||
enabled = isCbzPasswordSet,
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = securityPreferences.encryptionType(),
|
||||
title = stringResource(SYMR.strings.encryption_type),
|
||||
entries = SecurityPreferences.EncryptionType.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
enabled = passwordProtectDownloads,
|
||||
|
||||
),
|
||||
kotlin.run {
|
||||
var dialogOpen by remember { mutableStateOf(false) }
|
||||
if (dialogOpen) {
|
||||
PasswordDialog(
|
||||
onDismissRequest = { dialogOpen = false },
|
||||
onReturnPassword = { password ->
|
||||
dialogOpen = false
|
||||
|
||||
CbzCrypto.deleteKeyCbz()
|
||||
securityPreferences.cbzPassword().set(CbzCrypto.encryptCbz(password.replace("\n", "")))
|
||||
},
|
||||
)
|
||||
}
|
||||
.toImmutableMap(),
|
||||
onValueChanged = {
|
||||
(context as FragmentActivity).authenticate(
|
||||
title = context.stringResource(MR.strings.lock_when_idle),
|
||||
)
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = securityPreferences.hideNotificationContent(),
|
||||
title = stringResource(MR.strings.hide_notification_content),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = securityPreferences.secureScreen(),
|
||||
title = stringResource(MR.strings.secure_screen),
|
||||
entries = SecurityPreferences.SecureScreenMode.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
),
|
||||
// SY -->
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = securityPreferences.passwordProtectDownloads(),
|
||||
title = stringResource(SYMR.strings.password_protect_downloads),
|
||||
subtitle = stringResource(SYMR.strings.password_protect_downloads_summary),
|
||||
enabled = isCbzPasswordSet,
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = securityPreferences.encryptionType(),
|
||||
title = stringResource(SYMR.strings.encryption_type),
|
||||
entries = SecurityPreferences.EncryptionType.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
enabled = passwordProtectDownloads,
|
||||
|
||||
),
|
||||
kotlin.run {
|
||||
var dialogOpen by remember { mutableStateOf(false) }
|
||||
if (dialogOpen) {
|
||||
PasswordDialog(
|
||||
onDismissRequest = { dialogOpen = false },
|
||||
onReturnPassword = { password ->
|
||||
dialogOpen = false
|
||||
|
||||
CbzCrypto.deleteKeyCbz()
|
||||
securityPreferences.cbzPassword().set(CbzCrypto.encryptCbz(password.replace("\n", "")))
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(SYMR.strings.set_cbz_zip_password),
|
||||
onClick = {
|
||||
dialogOpen = true
|
||||
},
|
||||
)
|
||||
}
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(SYMR.strings.set_cbz_zip_password),
|
||||
onClick = {
|
||||
dialogOpen = true
|
||||
},
|
||||
)
|
||||
},
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(SYMR.strings.delete_cbz_archive_password),
|
||||
onClick = {
|
||||
CbzCrypto.deleteKeyCbz()
|
||||
securityPreferences.cbzPassword().set("")
|
||||
},
|
||||
enabled = isCbzPasswordSet,
|
||||
),
|
||||
kotlin.run {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(SYMR.strings.action_edit_biometric_lock_times),
|
||||
subtitle = pluralStringResource(
|
||||
SYMR.plurals.num_lock_times,
|
||||
count.size,
|
||||
count.size,
|
||||
),
|
||||
title = stringResource(SYMR.strings.delete_cbz_archive_password),
|
||||
onClick = {
|
||||
navigator.push(BiometricTimesScreen())
|
||||
CbzCrypto.deleteKeyCbz()
|
||||
securityPreferences.cbzPassword().set("")
|
||||
},
|
||||
enabled = useAuth,
|
||||
)
|
||||
},
|
||||
kotlin.run {
|
||||
val selection by securityPreferences.authenticatorDays().collectAsState()
|
||||
var dialogOpen by remember { mutableStateOf(false) }
|
||||
if (dialogOpen) {
|
||||
SetLockedDaysDialog(
|
||||
onDismissRequest = { dialogOpen = false },
|
||||
initialSelection = selection,
|
||||
onDaysSelected = {
|
||||
dialogOpen = false
|
||||
securityPreferences.authenticatorDays().set(it)
|
||||
enabled = isCbzPasswordSet,
|
||||
),
|
||||
kotlin.run {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(SYMR.strings.action_edit_biometric_lock_times),
|
||||
subtitle = pluralStringResource(
|
||||
SYMR.plurals.num_lock_times,
|
||||
count.size,
|
||||
count.size,
|
||||
),
|
||||
onClick = {
|
||||
navigator.push(BiometricTimesScreen())
|
||||
},
|
||||
enabled = useAuth,
|
||||
)
|
||||
}
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(SYMR.strings.biometric_lock_days),
|
||||
subtitle = stringResource(SYMR.strings.biometric_lock_days_summary),
|
||||
onClick = { dialogOpen = true },
|
||||
enabled = useAuth,
|
||||
)
|
||||
},
|
||||
// SY <--
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
|
||||
},
|
||||
kotlin.run {
|
||||
val selection by securityPreferences.authenticatorDays().collectAsState()
|
||||
var dialogOpen by remember { mutableStateOf(false) }
|
||||
if (dialogOpen) {
|
||||
SetLockedDaysDialog(
|
||||
onDismissRequest = { dialogOpen = false },
|
||||
initialSelection = selection,
|
||||
onDaysSelected = {
|
||||
dialogOpen = false
|
||||
securityPreferences.authenticatorDays().set(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(SYMR.strings.biometric_lock_days),
|
||||
subtitle = stringResource(SYMR.strings.biometric_lock_days_summary),
|
||||
onClick = { dialogOpen = true },
|
||||
enabled = useAuth,
|
||||
)
|
||||
},
|
||||
// SY <--
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -361,6 +375,28 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
)
|
||||
}
|
||||
// SY <--
|
||||
|
||||
@Composable
|
||||
private fun getFirebaseGroup(
|
||||
privacyPreferences: PrivacyPreferences,
|
||||
): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_firebase),
|
||||
preferenceItems = persistentListOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = privacyPreferences.crashlytics(),
|
||||
title = stringResource(MR.strings.onboarding_permission_crashlytics),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = privacyPreferences.analytics(),
|
||||
title = stringResource(MR.strings.onboarding_permission_analytics),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
|
||||
),
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.firebase_summary)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val LockAfterValues = persistentListOf(
|
||||
|
||||
+10
@@ -40,6 +40,7 @@ import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import eu.kanade.domain.track.model.AutoTrackState
|
||||
import eu.kanade.domain.track.service.TrackPreferences
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||
@@ -53,6 +54,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
@@ -85,6 +87,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
|
||||
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
||||
val sourceManager = remember { Injekt.get<SourceManager>() }
|
||||
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead()
|
||||
|
||||
var dialog by remember { mutableStateOf<Any?>(null) }
|
||||
dialog?.run {
|
||||
@@ -125,6 +128,13 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
pref = trackPreferences.autoUpdateTrack(),
|
||||
title = stringResource(MR.strings.pref_auto_update_manga_sync),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = trackPreferences.autoUpdateTrackOnMarkRead(),
|
||||
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
|
||||
entries = AutoTrackState.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toPersistentMap(),
|
||||
),
|
||||
Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.services),
|
||||
preferenceItems = persistentListOf(
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ class OpenSourceLicensesScreen : Screen() {
|
||||
name = it.name,
|
||||
website = it.website,
|
||||
license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
|
||||
)
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||
import kotlinx.serialization.Serializable
|
||||
import nl.adaptivity.xmlutil.AndroidXmlReader
|
||||
import nl.adaptivity.xmlutil.core.AndroidXmlReader
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
||||
import nl.adaptivity.xmlutil.serialization.XmlValue
|
||||
|
||||
+9
-2
@@ -8,6 +8,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConfirmDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConflictDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog
|
||||
@@ -32,7 +33,7 @@ class ExtensionReposScreen(
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
LaunchedEffect(url) {
|
||||
url?.let { screenModel.createRepo(it) }
|
||||
url?.let { screenModel.showDialog(RepoDialog.Confirm(it)) }
|
||||
}
|
||||
|
||||
if (state is RepoScreenState.Loading) {
|
||||
@@ -67,7 +68,6 @@ class ExtensionReposScreen(
|
||||
repo = dialog.repo,
|
||||
)
|
||||
}
|
||||
|
||||
is RepoDialog.Conflict -> {
|
||||
ExtensionRepoConflictDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
@@ -76,6 +76,13 @@ class ExtensionReposScreen(
|
||||
newRepo = dialog.newRepo,
|
||||
)
|
||||
}
|
||||
is RepoDialog.Confirm -> {
|
||||
ExtensionRepoConfirmDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onCreate = { screenModel.createRepo(dialog.url) },
|
||||
repo = dialog.url,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
||||
+1
@@ -125,6 +125,7 @@ sealed class RepoDialog {
|
||||
data object Create : RepoDialog()
|
||||
data class Delete(val repo: String) : RepoDialog()
|
||||
data class Conflict(val oldRepo: ExtensionRepo, val newRepo: ExtensionRepo) : RepoDialog()
|
||||
data class Confirm(val url: String) : RepoDialog()
|
||||
}
|
||||
|
||||
sealed class RepoScreenState {
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ fun ExtensionReposContent(
|
||||
repos.forEach {
|
||||
item {
|
||||
ExtensionRepoListItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
repo = it,
|
||||
onOpenWebsite = { onOpenWebsite(it) },
|
||||
onDelete = { onClickDelete(it.baseUrl) },
|
||||
|
||||
+32
@@ -152,3 +152,35 @@ fun ExtensionRepoConflictDialog(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExtensionRepoConfirmDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: () -> Unit,
|
||||
repo: String,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.action_add_repo))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(MR.strings.add_repo_confirmation, repo))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onCreate()
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(MR.strings.action_add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+2
-4
@@ -6,7 +6,6 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -27,7 +26,6 @@ import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.update
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
||||
import tachiyomi.presentation.core.components.SectionCard
|
||||
@@ -69,7 +67,7 @@ class CreateBackupScreen : Screen() {
|
||||
LazyColumnWithAction(
|
||||
contentPadding = contentPadding,
|
||||
actionLabel = stringResource(MR.strings.action_create),
|
||||
actionEnabled = state.options.anyEnabled(),
|
||||
actionEnabled = state.options.canCreate(),
|
||||
onClickAction = {
|
||||
if (!BackupCreateJob.isManualJobRunning(context)) {
|
||||
try {
|
||||
@@ -104,7 +102,7 @@ class CreateBackupScreen : Screen() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.Options(
|
||||
private fun Options(
|
||||
options: ImmutableList<BackupOptions.Entry>,
|
||||
state: CreateBackupScreenModel.State,
|
||||
model: CreateBackupScreenModel,
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ class RestoreBackupScreen(
|
||||
LazyColumnWithAction(
|
||||
contentPadding = contentPadding,
|
||||
actionLabel = stringResource(MR.strings.action_restore),
|
||||
actionEnabled = state.canRestore && state.options.anyEnabled(),
|
||||
actionEnabled = state.canRestore && state.options.canRestore(),
|
||||
onClickAction = {
|
||||
model.startRestore()
|
||||
navigator.pop()
|
||||
|
||||
+7
-11
@@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
@@ -16,7 +15,6 @@ import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupOptions
|
||||
import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.update
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -32,7 +30,6 @@ import uy.kohesive.injekt.api.get
|
||||
class SyncSettingsSelector : Screen() {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val model = rememberScreenModel { SyncSettingsSelectorModel() }
|
||||
val state by model.state.collectAsState()
|
||||
@@ -48,15 +45,10 @@ class SyncSettingsSelector : Screen() {
|
||||
) { contentPadding ->
|
||||
LazyColumnWithAction(
|
||||
contentPadding = contentPadding,
|
||||
actionLabel = stringResource(SYMR.strings.label_sync),
|
||||
actionEnabled = state.options.anyEnabled(),
|
||||
actionLabel = stringResource(MR.strings.action_save),
|
||||
actionEnabled = true,
|
||||
onClickAction = {
|
||||
if (!SyncDataJob.isRunning(context)) {
|
||||
model.syncNow(context)
|
||||
navigator.pop()
|
||||
} else {
|
||||
context.toast(SYMR.strings.sync_in_progress)
|
||||
}
|
||||
navigator.pop()
|
||||
},
|
||||
) {
|
||||
item {
|
||||
@@ -122,12 +114,14 @@ private class SyncSettingsSelectorModel(
|
||||
tracking = syncSettings.tracking,
|
||||
history = syncSettings.history,
|
||||
appSettings = syncSettings.appSettings,
|
||||
extensionRepoSettings = syncSettings.extensionRepoSettings,
|
||||
sourceSettings = syncSettings.sourceSettings,
|
||||
privateSettings = syncSettings.privateSettings,
|
||||
|
||||
// SY -->
|
||||
customInfo = syncSettings.customInfo,
|
||||
readEntries = syncSettings.readEntries,
|
||||
savedSearches = syncSettings.savedSearches,
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
@@ -140,12 +134,14 @@ private class SyncSettingsSelectorModel(
|
||||
tracking = backupOptions.tracking,
|
||||
history = backupOptions.history,
|
||||
appSettings = backupOptions.appSettings,
|
||||
extensionRepoSettings = backupOptions.extensionRepoSettings,
|
||||
sourceSettings = backupOptions.sourceSettings,
|
||||
privateSettings = backupOptions.privateSettings,
|
||||
|
||||
// SY -->
|
||||
customInfo = backupOptions.customInfo,
|
||||
readEntries = backupOptions.readEntries,
|
||||
savedSearches = backupOptions.savedSearches,
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
class BackupSchemaScreen : Screen() {
|
||||
|
||||
companion object {
|
||||
const val title = "Backup file schema"
|
||||
const val TITLE = "Backup file schema"
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -41,7 +41,7 @@ class BackupSchemaScreen : Screen() {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = title,
|
||||
title = TITLE,
|
||||
navigateUp = navigator::pop,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
@@ -50,7 +50,7 @@ class BackupSchemaScreen : Screen() {
|
||||
title = stringResource(MR.strings.action_copy_to_clipboard),
|
||||
icon = Icons.Default.ContentCopy,
|
||||
onClick = {
|
||||
context.copyToClipboard(title, schema)
|
||||
context.copyToClipboard(TITLE, schema)
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
+2
-2
@@ -31,11 +31,11 @@ class DebugInfoScreen : Screen() {
|
||||
itemsProvider = {
|
||||
listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = WorkerInfoScreen.title,
|
||||
title = WorkerInfoScreen.TITLE,
|
||||
onClick = { navigator.push(WorkerInfoScreen()) },
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = BackupSchemaScreen.title,
|
||||
title = BackupSchemaScreen.TITLE,
|
||||
onClick = { navigator.push(BackupSchemaScreen()) },
|
||||
),
|
||||
getAppInfoGroup(),
|
||||
|
||||
+4
-4
@@ -49,7 +49,7 @@ import java.time.ZoneId
|
||||
class WorkerInfoScreen : Screen() {
|
||||
|
||||
companion object {
|
||||
const val title = "Worker info"
|
||||
const val TITLE = "Worker info"
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -65,7 +65,7 @@ class WorkerInfoScreen : Screen() {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = title,
|
||||
title = TITLE,
|
||||
navigateUp = navigator::pop,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
@@ -74,7 +74,7 @@ class WorkerInfoScreen : Screen() {
|
||||
title = stringResource(MR.strings.action_copy_to_clipboard),
|
||||
icon = Icons.Default.ContentCopy,
|
||||
onClick = {
|
||||
context.copyToClipboard(title, enqueued + finished + running)
|
||||
context.copyToClipboard(TITLE, enqueued + finished + running)
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -159,7 +159,7 @@ class WorkerInfoScreen : Screen() {
|
||||
Injekt.get<UiPreferences>().dateFormat().get(),
|
||||
),
|
||||
)
|
||||
appendLine("Next scheduled run: $timestamp",)
|
||||
appendLine("Next scheduled run: $timestamp")
|
||||
appendLine("Attempt #${workInfo.runAttemptCount + 1}")
|
||||
}
|
||||
appendLine()
|
||||
|
||||
+15
-23
@@ -29,32 +29,24 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.app.ActivityCompat
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.domain.ui.model.AppTheme
|
||||
import eu.kanade.presentation.manga.components.MangaCover
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
||||
import tachiyomi.core.common.preference.InMemoryPreferenceStore
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.fullType
|
||||
|
||||
@Composable
|
||||
internal fun AppThemePreferenceWidget(
|
||||
@@ -257,18 +249,18 @@ fun AppThemePreviewItem(
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun AppThemesListPreview() {
|
||||
var appTheme by remember { mutableStateOf(AppTheme.DEFAULT) }
|
||||
Injekt.addSingleton(fullType<UiPreferences>(), UiPreferences(InMemoryPreferenceStore()))
|
||||
TachiyomiTheme(appTheme = appTheme) {
|
||||
Surface {
|
||||
AppThemesList(
|
||||
currentTheme = appTheme,
|
||||
amoled = false,
|
||||
onItemClick = { appTheme = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// @PreviewLightDark
|
||||
// @Composable
|
||||
// private fun AppThemesListPreview() {
|
||||
// var appTheme by remember { mutableStateOf(AppTheme.DEFAULT) }
|
||||
// Injekt.addSingleton(fullType<UiPreferences>(), UiPreferences(InMemoryPreferenceStore()))
|
||||
// TachiyomiTheme(appTheme = appTheme) {
|
||||
// Surface {
|
||||
// AppThemesList(
|
||||
// currentTheme = appTheme,
|
||||
// amoled = false,
|
||||
// onItemClick = { appTheme = it },
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
+2
-4
@@ -26,8 +26,6 @@ import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||
|
||||
@Composable
|
||||
fun <T> ListPreferenceWidget(
|
||||
@@ -69,8 +67,8 @@ fun <T> ListPreferenceWidget(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
|
||||
+5
-13
@@ -30,11 +30,11 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||
|
||||
private enum class State {
|
||||
CHECKED, INVERSED, UNCHECKED
|
||||
CHECKED,
|
||||
INVERSED,
|
||||
UNCHECKED,
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -115,16 +115,8 @@ fun <T> TriStateListDialog(
|
||||
}
|
||||
}
|
||||
|
||||
if (!listState.isScrolledToStart()) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
)
|
||||
}
|
||||
if (!listState.isScrolledToEnd()) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
)
|
||||
}
|
||||
if (listState.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (listState.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
@@ -73,7 +73,7 @@ private fun RowScope.BaseStatsItem(
|
||||
style = subtitleStyle
|
||||
.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
.copy(alpha = SecondaryItemAlpha),
|
||||
.copy(alpha = SECONDARY_ALPHA),
|
||||
),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
@@ -8,11 +8,13 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.ui.reader.chapter.ReaderChapterItem
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
@@ -20,8 +22,13 @@ import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.source.isEhBasedManga
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
@@ -39,6 +46,8 @@ fun ChapterListDialog(
|
||||
val manga by screenModel.mangaFlow.collectAsState()
|
||||
val context = LocalContext.current
|
||||
val state = rememberLazyListState(chapters.indexOfFirst { it.isCurrent }.coerceAtLeast(0))
|
||||
val downloadManager: DownloadManager = remember { Injekt.get() }
|
||||
val downloadQueueState by downloadManager.queueState.collectAsState()
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
@@ -52,6 +61,28 @@ fun ChapterListDialog(
|
||||
items = chapters,
|
||||
key = { "chapter-${it.chapter.id}" },
|
||||
) { chapterItem ->
|
||||
val activeDownload = downloadQueueState.find { it.chapter.id == chapterItem.chapter.id }
|
||||
val progress = activeDownload?.let {
|
||||
downloadManager.progressFlow()
|
||||
.filter { it.chapter.id == chapterItem.chapter.id }
|
||||
.map { it.progress }
|
||||
.collectAsState(0).value
|
||||
} ?: 0
|
||||
val downloaded = if (chapterItem.manga.isLocal()) {
|
||||
true
|
||||
} else {
|
||||
downloadManager.isChapterDownloaded(
|
||||
chapterItem.chapter.name,
|
||||
chapterItem.chapter.scanlator,
|
||||
chapterItem.manga.ogTitle,
|
||||
chapterItem.manga.source,
|
||||
)
|
||||
}
|
||||
val downloadState = when {
|
||||
activeDownload != null -> activeDownload.status
|
||||
downloaded -> Download.State.DOWNLOADED
|
||||
else -> Download.State.NOT_DOWNLOADED
|
||||
}
|
||||
MangaChapterListItem(
|
||||
title = chapterItem.chapter.name,
|
||||
date = chapterItem.chapter.dateUpload
|
||||
@@ -76,8 +107,8 @@ fun ChapterListDialog(
|
||||
bookmark = chapterItem.chapter.bookmark,
|
||||
selected = false,
|
||||
downloadIndicatorEnabled = false,
|
||||
downloadStateProvider = { Download.State.NOT_DOWNLOADED },
|
||||
downloadProgressProvider = { 0 },
|
||||
downloadStateProvider = { downloadState },
|
||||
downloadProgressProvider = { progress },
|
||||
chapterSwipeStartAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
|
||||
chapterSwipeEndAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
|
||||
onLongClick = { /*TODO*/ },
|
||||
|
||||
@@ -226,7 +226,7 @@ private fun ChapterText(
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
if (downloaded) {
|
||||
appendInlineContent(DownloadedIconContentId)
|
||||
appendInlineContent(DOWNLOADED_ICON_ID)
|
||||
append(' ')
|
||||
}
|
||||
append(name)
|
||||
@@ -236,7 +236,7 @@ private fun ChapterText(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
inlineContent = persistentMapOf(
|
||||
DownloadedIconContentId to InlineTextContent(
|
||||
DOWNLOADED_ICON_ID to InlineTextContent(
|
||||
Placeholder(
|
||||
width = 22.sp,
|
||||
height = 22.sp,
|
||||
@@ -273,7 +273,7 @@ private val CardColor: CardColors
|
||||
)
|
||||
|
||||
private val VerticalSpacerSize = 24.dp
|
||||
private const val DownloadedIconContentId = "downloaded"
|
||||
private const val DOWNLOADED_ICON_ID = "downloaded"
|
||||
|
||||
private fun previewChapter(name: String, scanlator: String, chapterNumber: Double) = Chapter.create().copy(
|
||||
id = 0L,
|
||||
|
||||
@@ -7,19 +7,42 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@Stable
|
||||
class DisplayRefreshHost {
|
||||
|
||||
internal var currentDisplayRefresh by mutableStateOf(false)
|
||||
private val readerPreferences = Injekt.get<ReaderPreferences>()
|
||||
|
||||
internal val flashMillis = readerPreferences.flashDurationMillis()
|
||||
internal val flashMode = readerPreferences.flashColor()
|
||||
|
||||
internal val flashIntervalPref = readerPreferences.flashPageInterval()
|
||||
|
||||
// Internal State for Flash
|
||||
private var flashInterval = flashIntervalPref.get()
|
||||
private var timesCalled = 0
|
||||
|
||||
fun flash() {
|
||||
currentDisplayRefresh = true
|
||||
if (timesCalled % flashInterval == 0) {
|
||||
currentDisplayRefresh = true
|
||||
}
|
||||
timesCalled += 1
|
||||
}
|
||||
|
||||
fun setInterval(interval: Int) {
|
||||
flashInterval = interval
|
||||
timesCalled = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,18 +52,39 @@ fun DisplayRefreshHost(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val currentDisplayRefresh = hostState.currentDisplayRefresh
|
||||
val refreshDuration by hostState.flashMillis.collectAsState()
|
||||
val flashMode by hostState.flashMode.collectAsState()
|
||||
val flashInterval by hostState.flashIntervalPref.collectAsState()
|
||||
|
||||
var currentColor by remember { mutableStateOf<Color?>(null) }
|
||||
|
||||
LaunchedEffect(currentDisplayRefresh) {
|
||||
if (currentDisplayRefresh) {
|
||||
delay(1.5.seconds)
|
||||
hostState.currentDisplayRefresh = false
|
||||
if (!currentDisplayRefresh) {
|
||||
currentColor = null
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
val refreshDurationHalf = refreshDuration.milliseconds / 2
|
||||
currentColor = if (flashMode == ReaderPreferences.FlashColor.BLACK) {
|
||||
Color.Black
|
||||
} else {
|
||||
Color.White
|
||||
}
|
||||
delay(refreshDurationHalf)
|
||||
if (flashMode == ReaderPreferences.FlashColor.WHITE_BLACK) {
|
||||
currentColor = Color.Black
|
||||
}
|
||||
delay(refreshDurationHalf)
|
||||
hostState.currentDisplayRefresh = false
|
||||
}
|
||||
|
||||
LaunchedEffect(flashInterval) {
|
||||
hostState.setInterval(flashInterval)
|
||||
}
|
||||
|
||||
Canvas(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
if (currentDisplayRefresh) {
|
||||
drawRect(Color.Black)
|
||||
}
|
||||
currentColor?.let { drawRect(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ContentCopy
|
||||
import androidx.compose.material.icons.outlined.Photo
|
||||
import androidx.compose.material.icons.outlined.Save
|
||||
import androidx.compose.material.icons.outlined.Share
|
||||
@@ -31,9 +32,9 @@ fun ReaderPageActionsDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
// SY -->
|
||||
onSetAsCover: (useExtraPage: Boolean) -> Unit,
|
||||
onShare: (useExtraPage: Boolean) -> Unit,
|
||||
onShare: (copy: Boolean, useExtraPage: Boolean) -> Unit,
|
||||
onSave: (useExtraPage: Boolean) -> Unit,
|
||||
onShareCombined: () -> Unit,
|
||||
onShareCombined: (copy: Boolean) -> Unit,
|
||||
onSaveCombined: () -> Unit,
|
||||
hasExtraPage: Boolean,
|
||||
// SY <--
|
||||
@@ -62,6 +63,25 @@ fun ReaderPageActionsDialog(
|
||||
icon = Icons.Outlined.Photo,
|
||||
onClick = { showSetCoverDialog = true },
|
||||
)
|
||||
ActionButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
title = stringResource(
|
||||
// SY -->
|
||||
if (hasExtraPage) {
|
||||
SYMR.strings.action_copy_first_page
|
||||
} else {
|
||||
MR.strings.action_copy_to_clipboard
|
||||
},
|
||||
// SY <--
|
||||
),
|
||||
icon = Icons.Outlined.ContentCopy,
|
||||
onClick = {
|
||||
// SY -->
|
||||
onShare(true, false)
|
||||
// SY <--
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
ActionButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
title = stringResource(
|
||||
@@ -76,7 +96,7 @@ fun ReaderPageActionsDialog(
|
||||
icon = Icons.Outlined.Share,
|
||||
onClick = {
|
||||
// SY -->
|
||||
onShare(false)
|
||||
onShare(false, false)
|
||||
// SY <--
|
||||
onDismissRequest()
|
||||
},
|
||||
@@ -114,12 +134,21 @@ fun ReaderPageActionsDialog(
|
||||
showSetCoverDialog = true
|
||||
},
|
||||
)
|
||||
ActionButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
title = stringResource(SYMR.strings.action_copy_second_page),
|
||||
icon = Icons.Outlined.ContentCopy,
|
||||
onClick = {
|
||||
onShare(true, true)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
ActionButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
title = stringResource(SYMR.strings.action_share_second_page),
|
||||
icon = Icons.Outlined.Share,
|
||||
onClick = {
|
||||
onShare(true)
|
||||
onShare(false, true)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
@@ -136,12 +165,21 @@ fun ReaderPageActionsDialog(
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
) {
|
||||
ActionButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
title = stringResource(SYMR.strings.action_copy_combined_page),
|
||||
icon = Icons.Outlined.ContentCopy,
|
||||
onClick = {
|
||||
onShareCombined(true)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
ActionButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
title = stringResource(SYMR.strings.action_share_combined_page),
|
||||
icon = Icons.Outlined.Share,
|
||||
onClick = {
|
||||
onShareCombined()
|
||||
onShareCombined(false)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
|
||||
@@ -46,6 +46,7 @@ fun BottomReaderBar(
|
||||
doublePages: Boolean,
|
||||
onClickChapterList: () -> Unit,
|
||||
onClickWebView: (() -> Unit)?,
|
||||
onClickBrowser: (() -> Unit)?,
|
||||
onClickShare: (() -> Unit)?,
|
||||
onClickPageLayout: () -> Unit,
|
||||
onClickShiftPage: () -> Unit,
|
||||
@@ -78,6 +79,15 @@ fun BottomReaderBar(
|
||||
}
|
||||
}
|
||||
|
||||
if (ReaderBottomButton.Browser.isIn(enabledButtons) && onClickBrowser != null) {
|
||||
IconButton(onClick = onClickBrowser) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Public,
|
||||
contentDescription = stringResource(MR.strings.action_open_in_browser),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (ReaderBottomButton.Share.isIn(enabledButtons) && onClickShare != null) {
|
||||
IconButton(onClick = onClickShare) {
|
||||
Icon(
|
||||
|
||||
@@ -63,7 +63,7 @@ fun ExhUtils(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.background(backgroundColor),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AnimatedVisibility(visible = isVisible) {
|
||||
Column {
|
||||
@@ -84,7 +84,7 @@ fun ExhUtils(
|
||||
) {
|
||||
Column(
|
||||
Modifier.weight(3f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(SYMR.strings.eh_autoscroll),
|
||||
@@ -93,17 +93,17 @@ fun ExhUtils(
|
||||
fontFamily = FontFamily.SansSerif,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
modifier = Modifier.fillMaxWidth(0.75f),
|
||||
textAlign = TextAlign.Center
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Switch(
|
||||
checked = isAutoScroll,
|
||||
onCheckedChange = null,
|
||||
enabled = isAutoScrollEnabled
|
||||
enabled = isAutoScrollEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ fun ExhUtils(
|
||||
) {
|
||||
Column(
|
||||
Modifier.weight(3f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
var autoScrollFrequencyState by remember {
|
||||
mutableStateOf(autoScrollFrequency)
|
||||
|
||||
@@ -74,6 +74,7 @@ fun ReaderAppBars(
|
||||
// bookmarked: Boolean,
|
||||
// onToggleBookmarked: () -> Unit,
|
||||
onOpenInWebView: (() -> Unit)?,
|
||||
onOpenInBrowser: (() -> Unit)?,
|
||||
onShare: (() -> Unit)?,
|
||||
|
||||
viewer: Viewer?,
|
||||
@@ -83,7 +84,7 @@ fun ReaderAppBars(
|
||||
enabledPrevious: Boolean,
|
||||
currentPage: Int,
|
||||
totalPages: Int,
|
||||
onSliderValueChange: (Int) -> Unit,
|
||||
onPageIndexChange: (Int) -> Unit,
|
||||
|
||||
readingMode: ReadingMode,
|
||||
onClickReadingMode: () -> Unit,
|
||||
@@ -153,7 +154,7 @@ fun ReaderAppBars(
|
||||
enabledPrevious = enabledPrevious,
|
||||
currentPage = currentPage,
|
||||
totalPages = totalPages,
|
||||
onSliderValueChange = onSliderValueChange,
|
||||
onPageIndexChange = onPageIndexChange,
|
||||
isVerticalSlider = true,
|
||||
currentPageText = currentPageText,
|
||||
)
|
||||
@@ -181,7 +182,7 @@ fun ReaderAppBars(
|
||||
enabledPrevious = enabledPrevious,
|
||||
currentPage = currentPage,
|
||||
totalPages = totalPages,
|
||||
onSliderValueChange = onSliderValueChange,
|
||||
onPageIndexChange = onPageIndexChange,
|
||||
isVerticalSlider = true,
|
||||
currentPageText = currentPageText,
|
||||
)
|
||||
@@ -284,7 +285,7 @@ fun ReaderAppBars(
|
||||
enabledPrevious = enabledPrevious,
|
||||
currentPage = currentPage,
|
||||
totalPages = totalPages,
|
||||
onSliderValueChange = onSliderValueChange,
|
||||
onPageIndexChange = onPageIndexChange,
|
||||
isVerticalSlider = false,
|
||||
currentPageText = currentPageText,
|
||||
)
|
||||
@@ -308,6 +309,7 @@ fun ReaderAppBars(
|
||||
doublePages = doublePages,
|
||||
onClickChapterList = onClickChapterList,
|
||||
onClickWebView = onOpenInWebView,
|
||||
onClickBrowser = onOpenInBrowser,
|
||||
onClickShare = onShare,
|
||||
onClickPageLayout = onClickPageLayout,
|
||||
onClickShiftPage = onClickShiftPage,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user