Compare commits
No commits in common. "master" and "1.10.4" have entirely different histories.
@ -1,32 +1,12 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_size = 2
|
|
||||||
indent_style = space
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.{xml,sq,sqm}]
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
# noinspection EditorConfigKeyCorrectness
|
|
||||||
[*.{kt,kts}]
|
[*.{kt,kts}]
|
||||||
indent_size = 4
|
|
||||||
max_line_length = 120
|
max_line_length = 120
|
||||||
|
indent_size = 4
|
||||||
|
insert_final_newline = true
|
||||||
ij_kotlin_allow_trailing_comma = true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||||
|
|
||||||
ktlint_code_style = intellij_idea
|
|
||||||
ktlint_function_naming_ignore_when_annotated_with = Composable
|
|
||||||
ktlint_standard_class-signature = disabled
|
|
||||||
ktlint_standard_discouraged-comment-location = disabled
|
|
||||||
ktlint_standard_function-expression-body = disabled
|
|
||||||
ktlint_standard_function-signature = disabled
|
|
||||||
# SY
|
|
||||||
ktlint_standard_filename = disabled
|
ktlint_standard_filename = disabled
|
||||||
ktlint_standard_argument-list-wrapping = disabled
|
ktlint_standard_argument-list-wrapping = disabled
|
||||||
ktlint_standard_function-naming = disabled
|
ktlint_standard_function-naming = disabled
|
||||||
@ -34,7 +14,3 @@ ktlint_standard_property-naming = disabled
|
|||||||
ktlint_standard_multiline-expression-wrapping = disabled
|
ktlint_standard_multiline-expression-wrapping = disabled
|
||||||
ktlint_standard_string-template-indent = disabled
|
ktlint_standard_string-template-indent = disabled
|
||||||
ktlint_standard_comment-wrapping = disabled
|
ktlint_standard_comment-wrapping = disabled
|
||||||
ktlint_standard_max-line-length = disabled
|
|
||||||
ktlint_standard_type-argument-comment = disabled
|
|
||||||
ktlint_standard_value-argument-comment = disabled
|
|
||||||
ktlint_standard_value-parameter-comment = disabled
|
|
||||||
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: ❌ Help with Extensions
|
|
||||||
url: https://mihon.app/docs/faq/browse/extensions
|
|
||||||
about: For extension-related questions/issues
|
|
||||||
- name: 🖥️ Mihon website
|
- name: 🖥️ Mihon website
|
||||||
url: https://mihon.app/
|
url: https://mihon.app/
|
||||||
about: Guides, troubleshooting, and answers to common questions
|
about: Guides, troubleshooting, and answers to common questions
|
||||||
|
18
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
18
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -43,9 +43,9 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Crash logs
|
label: Crash logs
|
||||||
description: |
|
description: |
|
||||||
If you're experiencing crashes, if possible, go to the app's **More → Settings → Advanced** page, press **Dump crash logs** and share the crash logs here.
|
If you're experiencing crashes, share the crash logs from **More → Settings → Advanced** then press **Dump crash logs**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
You can upload the crash log file as an attachment, or paste the crash logs in plain text if needed.
|
You can paste the crash logs in plain text or upload it as an attachment.
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: tachiyomisy-version
|
id: tachiyomisy-version
|
||||||
@ -53,7 +53,7 @@ body:
|
|||||||
label: TachiyomiSY version
|
label: TachiyomiSY version
|
||||||
description: You can find your TachiyomiSY version in **More → About**.
|
description: You can find your TachiyomiSY version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "1.12.0"
|
Example: "1.10.4"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ body:
|
|||||||
label: Android version
|
label: Android version
|
||||||
description: You can find this somewhere in your Android settings.
|
description: You can find this somewhere in your Android settings.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "Android 14"
|
Example: "Android 11"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ body:
|
|||||||
label: Device
|
label: Device
|
||||||
description: List your device and model.
|
description: List your device and model.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "Google Pixel 8"
|
Example: "Google Pixel 5"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -94,11 +94,11 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https:/mihon.app/docs/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[1.12.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
- label: I have updated the app to version **[1.10.4](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
- label: I understand that **Mihon does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -31,7 +31,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[1.12.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
- label: I have updated the app to version **[1.10.4](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
7
.github/renovate.json5
vendored
7
.github/renovate.json5
vendored
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": ["config:base"],
|
|
||||||
"labels": ["Dependencies"],
|
|
||||||
"includePaths": [".github/workflows/*", "gradle/sy.versions.toml"],
|
|
||||||
"semanticCommits": "disabled"
|
|
||||||
}
|
|
25
.github/workflows/build_check.yml
vendored
25
.github/workflows/build_check.yml
vendored
@ -6,8 +6,20 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check_wrapper:
|
||||||
|
name: Validate Gradle Wrapper
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Validate Gradle Wrapper
|
||||||
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build app
|
name: Build app
|
||||||
|
needs: check_wrapper
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -15,19 +27,18 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: temurin
|
distribution: adopt
|
||||||
|
|
||||||
- name: Set up gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: ./gradlew spotlessCheck assembleDevDebug
|
uses: gradle/gradle-command-action@v2
|
||||||
|
with:
|
||||||
|
arguments: assembleDevDebug
|
||||||
|
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: TachiyomiSY-${{ github.sha }}.apk
|
name: TachiyomiSY-${{ github.sha }}.apk
|
||||||
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
||||||
|
44
.github/workflows/build_push.yml
vendored
44
.github/workflows/build_push.yml
vendored
@ -17,6 +17,9 @@ jobs:
|
|||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Validate Gradle Wrapper
|
||||||
|
uses: gradle/wrapper-validation-action@v2
|
||||||
|
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
run: |
|
run: |
|
||||||
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
||||||
@ -25,35 +28,22 @@ jobs:
|
|||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: temurin
|
distribution: adopt
|
||||||
|
|
||||||
- name: Set up gradle
|
- name: Set up gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
|
||||||
# SY -->
|
# SY <--
|
||||||
- name: Write google-services.json
|
- name: Write google-services.json
|
||||||
uses: DamianReeves/write-file-action@v1.3
|
uses: DamianReeves/write-file-action@v1.2
|
||||||
with:
|
with:
|
||||||
path: app/google-services.json
|
path: app/google-services.json
|
||||||
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
|
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
|
||||||
write-mode: overwrite
|
write-mode: overwrite
|
||||||
|
# SY -->
|
||||||
|
|
||||||
- name: Write client_secrets.json
|
- name: Build app and run unit tests
|
||||||
uses: DamianReeves/write-file-action@v1.3
|
run: ./gradlew assembleStandardRelease testStandardReleaseUnitTest --stacktrace
|
||||||
with:
|
|
||||||
path: app/src/main/assets/client_secrets.json
|
|
||||||
contents: ${{ secrets.CLIENT_SECRETS_TEXT }}
|
|
||||||
write-mode: overwrite
|
|
||||||
# SY <--
|
|
||||||
|
|
||||||
- name: Check code format
|
|
||||||
run: ./gradlew spotlessCheck
|
|
||||||
|
|
||||||
- name: Build app
|
|
||||||
run: ./gradlew assembleStandardRelease
|
|
||||||
|
|
||||||
- name: Run unit tests
|
|
||||||
run: ./gradlew testReleaseUnitTest testStandardReleaseUnitTest
|
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
@ -63,8 +53,6 @@ jobs:
|
|||||||
alias: ${{ secrets.ALIAS }}
|
alias: ${{ secrets.ALIAS }}
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
env:
|
|
||||||
BUILD_TOOLS_VERSION: '35.0.1'
|
|
||||||
|
|
||||||
- name: Clean up build artifacts
|
- name: Clean up build artifacts
|
||||||
run: |
|
run: |
|
||||||
@ -74,24 +62,24 @@ jobs:
|
|||||||
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
|
||||||
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk
|
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk
|
||||||
sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'`
|
||||||
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk
|
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk
|
||||||
sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'`
|
||||||
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk
|
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk
|
||||||
sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'`
|
||||||
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk
|
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk
|
||||||
sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'`
|
sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'`
|
||||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.run_number }}
|
tag_name: ${{ github.run_number }}
|
||||||
name: TachiyomiSY
|
name: TachiyomiSY
|
||||||
|
33
.github/workflows/build_push_preview.yml
vendored
33
.github/workflows/build_push_preview.yml
vendored
@ -1,32 +1,30 @@
|
|||||||
name: Remote Dispatch Action Initiator
|
name: Remote Dispatch Action Initiator
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'preview'
|
- 'master'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
trigger_preview_build:
|
trigger_preview_build:
|
||||||
name: Trigger preview build
|
name: Trigger preview build
|
||||||
|
if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Validate Gradle Wrapper
|
||||||
uses: actions/setup-java@v4
|
uses: gradle/wrapper-validation-action@v1
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: temurin
|
|
||||||
|
|
||||||
- name: Set up gradle
|
- name: TAG - Bump version and push tag
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: anothrNick/github-tag-action@1.67.0
|
||||||
|
env:
|
||||||
- name: Create Tag
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
WITH_V: true
|
||||||
git tag "preview-${{ github.run_number }}"
|
RELEASE_BRANCHES: master
|
||||||
git push origin "preview-${{ github.run_number }}"
|
DEFAULT_BUMP: patch
|
||||||
|
|
||||||
- name: PING - Dispatch initiating repository event
|
- name: PING - Dispatch initiating repository event
|
||||||
run: |
|
run: |
|
||||||
@ -34,6 +32,3 @@ jobs:
|
|||||||
-H 'Accept: application/vnd.github.everest-preview+json' \
|
-H 'Accept: application/vnd.github.everest-preview+json' \
|
||||||
-u ${{ secrets.ACCESS_TOKEN }} \
|
-u ${{ secrets.ACCESS_TOKEN }} \
|
||||||
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
||||||
|
|
||||||
- name: Run unit tests
|
|
||||||
run: ./gradlew testDebugUnitTest testDevDebugUnitTest
|
|
||||||
|
8
.github/workflows/issue_moderator.yml
vendored
8
.github/workflows/issue_moderator.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Moderate issues
|
- name: Moderate issues
|
||||||
uses: tachiyomiorg/issue-moderator-action@v2.6.1
|
uses: tachiyomiorg/issue-moderator-action@v2.6.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
duplicate-label: Duplicate
|
duplicate-label: Duplicate
|
||||||
@ -40,12 +40,6 @@ jobs:
|
|||||||
"ignoreCase": true,
|
"ignoreCase": true,
|
||||||
"labels": ["Cloudflare protected"],
|
"labels": ["Cloudflare protected"],
|
||||||
"message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
|
"message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "both",
|
|
||||||
"regex": "^.*(myanimelist|mal).*$",
|
|
||||||
"ignoreCase": true,
|
|
||||||
"message": "For issues with linking MyAnimeList, please follow these steps:\n1. Update Mihon to version 0.16.4 or newer\n2. Change your default User-Agent (`More → Settings → Advanced → Default user agent string`)\n3. Close and restart Mihon\n4. Attempt to link MyAnimeList again\n\nIf you had MyAnimeList linked before, try to unlink it first before trying these steps."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
auto-close-ignore-label: do-not-autoclose
|
auto-close-ignore-label: do-not-autoclose
|
||||||
|
19
.github/workflows/lock.yml
vendored
Normal file
19
.github/workflows/lock.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Lock threads
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Daily
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
# Manual trigger
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
issue-inactive-days: '2'
|
||||||
|
pr-inactive-days: '2'
|
26
.github/workflows/pr_label.yml
vendored
26
.github/workflows/pr_label.yml
vendored
@ -1,26 +0,0 @@
|
|||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
36
.gitignore
vendored
36
.gitignore
vendored
@ -1,24 +1,24 @@
|
|||||||
# Build files
|
|
||||||
.gradle
|
.gradle
|
||||||
.kotlin
|
/local.properties
|
||||||
build
|
/.idea/workspace.xml
|
||||||
|
.DS_Store
|
||||||
# IDE files
|
|
||||||
*.iml
|
|
||||||
.idea/*
|
.idea/*
|
||||||
!.idea/icon.png
|
!.idea/icon.png
|
||||||
/captures
|
*iml
|
||||||
|
*.iml
|
||||||
|
/mainframer
|
||||||
|
/.mainframer
|
||||||
|
|
||||||
# Configuration files
|
# Built files
|
||||||
local.properties
|
*/build
|
||||||
|
/build
|
||||||
# macOS specific files
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# SY ignores
|
|
||||||
google-services.json
|
|
||||||
/app/src/main/assets/client_secrets.json
|
|
||||||
*.apk
|
*.apk
|
||||||
|
app/**/output.json
|
||||||
|
|
||||||
# Custom ignores
|
# Unnecessary file
|
||||||
/keys
|
*.swp
|
||||||
|
|
||||||
|
TODO.md
|
||||||
|
CHANGELOG.md
|
||||||
|
/captures
|
||||||
|
build.sh
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Looking to report an issue/bug or make a feature request? Please refer to the [README file](/README.md#issues-feature-requests-and-contributing).
|
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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ Thanks for your interest in contributing to Tachiyomi!
|
|||||||
|
|
||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
If you're interested in taking on [an open issue](https://github.com/jobobby04/TachiyomiSY/issues), please comment on it so others are aware.
|
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
|
||||||
You do not need to ask for permission nor an assignment.
|
You do not need to ask for permission nor an assignment.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
@ -24,41 +24,31 @@ Before you start, please note that the ability to use following technologies is
|
|||||||
- [Android Studio](https://developer.android.com/studio)
|
- [Android Studio](https://developer.android.com/studio)
|
||||||
- Emulator or phone with developer options enabled to test changes.
|
- Emulator or phone with developer options enabled to test changes.
|
||||||
|
|
||||||
|
## Linting
|
||||||
|
|
||||||
|
To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
- Join [the Discord server](https://discord.gg/mihon) for online help and to ask questions while developing.
|
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
|
||||||
Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#translation) for more details.
|
Translations are done externally via Weblate. See [our website](https://tachiyomi.org/docs/contribute#translation) for more details.
|
||||||
|
|
||||||
|
|
||||||
# Forks
|
# Forks
|
||||||
|
|
||||||
Forks are allowed so long as they abide by [the project's LICENSE](/LICENSE).
|
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/tachiyomiorg/tachiyomi/blob/master/LICENSE).
|
||||||
|
|
||||||
When creating a fork, remember to:
|
When creating a fork, remember to:
|
||||||
|
|
||||||
- To avoid confusion with the main app:
|
- To avoid confusion with the main app:
|
||||||
- Change the app name
|
- Change the app name
|
||||||
- Change the app icon
|
- Change the app icon
|
||||||
- Change or disable the [app update checker](/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
- 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)
|
||||||
- To avoid installation conflicts:
|
- To avoid installation conflicts:
|
||||||
- Change the `applicationId` in [`build.gradle.kts`](/app/build.gradle.kts)
|
- 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
|
||||||
### Supporting Cloud Sync - Google Drive Implementation
|
- 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
|
||||||
1. Go to [Google Cloud Console](https://console.cloud.google.com)
|
|
||||||
2. Create a new project
|
|
||||||
3. Go to API & Services -> Library -> Google Drive API and click enable
|
|
||||||
4. Go to API & Services -> Oauth consent screen
|
|
||||||
5. Create it, fill in the app name, user support email, and developer contact information
|
|
||||||
6. In the next screen, click add or remove scopes, and add the `.../auth/drive.appdata` and `.../auth/drive.file` scopes
|
|
||||||
7. Don't add any test users and go back to the dashboard
|
|
||||||
8. Click publish
|
|
||||||
9. Go to API & Services -> Credentials
|
|
||||||
10. Click Create credentials -> Oauth client ID
|
|
||||||
11. Select Android, give it a name, and set `eu.kanade.google.oauth` as the package name
|
|
||||||
12. To get the SHA-1 key, run `keytool -printcert -jarfile app-standard-universal-release.apk` on your apk, and copy the listed SHA-1
|
|
||||||
13. Expand advanced settings, and enable Custom URL scheme
|
|
||||||
14. After that just download the json, name it to `client_secrets.json` and put it in `app/src/main/assets/`
|
|
||||||
|
26
README.md
26
README.md
@ -1,20 +1,20 @@
|
|||||||
| Preview Builds | Release Builds | Mihon Support Server |
|
| Preview Builds | Release Builds | Tachiyomi Support Server |
|
||||||
|-------|----------|----------|
|
|-------|----------|----------|
|
||||||
| [](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [](https://github.com/jobobby04/tachiyomisy/releases/latest) | [](https://discord.gg/mihon) |
|
| [](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [](https://github.com/jobobby04/tachiyomisy/releases/latest) | [](https://discord.gg/mihon) |
|
||||||
|
|
||||||
|
|
||||||
# TachiyomiSY
|
# TachiyomiSY
|
||||||
Mihon is a free and open source manga reader for Android 6.0 and above. This version of Mihon, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko.
|
Tachiyomi is a free and open source manga reader for Android 6.0 and above. This version of Tachiyomi, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
Features of Mihon(original) include:
|
Features of Tachiyomi(original) include:
|
||||||
* Online reading from a variety of sources
|
* Online reading from a variety of sources
|
||||||
* Local reading of downloaded content
|
* Local reading of downloaded content
|
||||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
* Tracker support: [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
|
||||||
* Categories to organize your library
|
* Categories to organize your library
|
||||||
* Light and dark themes
|
* Light and dark themes
|
||||||
* Schedule updating your library for new chapters
|
* Schedule updating your library for new chapters
|
||||||
@ -42,6 +42,7 @@ Features of TachiyomiSY include:
|
|||||||
* Page preload customization
|
* Page preload customization
|
||||||
* Customize image cache size
|
* Customize image cache size
|
||||||
* Batch import of custom sources and featured extensions
|
* Batch import of custom sources and featured extensions
|
||||||
|
* Automatic CAPTCHA solving
|
||||||
* Advanced source settings page, searching, enable/disable all
|
* Advanced source settings page, searching, enable/disable all
|
||||||
* Click tag for local search, long click tag for global search
|
* Click tag for local search, long click tag for global search
|
||||||
* Merge multiple of the same manga from different sources
|
* Merge multiple of the same manga from different sources
|
||||||
@ -67,22 +68,13 @@ 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).
|
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
|
## Issues, Feature Requests and Contributing
|
||||||
|
|
||||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
||||||
|
|
||||||
<details><summary>Issues</summary>
|
<details><summary>Issues</summary>
|
||||||
|
|
||||||
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).**
|
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).**
|
||||||
2. If you are unsure, ask here: [](https://discord.gg/mihon)
|
2. If you are unsure, ask here: [](https://discord.gg/mihon)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@ -97,7 +89,9 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||||
* Don't group unrelated requests into one issue
|
* Don't group unrelated requests into one issue
|
||||||
|
|
||||||
Use the [issue forms](https://github.com/jobobby04/TachiyomiSY/issues/new/choose) to submit a bug.
|
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
||||||
|
|
||||||
|
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@ -122,4 +116,4 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
[See our website.](https://mihon.app/)
|
[See our website.](https://mihon.app/)
|
||||||
You can also reach out to us on [Discord](https://discord.gg/mihon).
|
You can also reach out to us on [Discord](https://discord.gg/mihon).
|
4
app/.gitignore
vendored
Executable file
4
app/.gitignore
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
/build
|
||||||
|
*iml
|
||||||
|
*.iml
|
||||||
|
google-services.json
|
@ -1,29 +1,24 @@
|
|||||||
@file:Suppress("ChromeOsAbiSupport")
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
import mihon.buildlogic.getBuildTime
|
|
||||||
import mihon.buildlogic.getCommitCount
|
|
||||||
import mihon.buildlogic.getGitSha
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("mihon.android.application")
|
id("com.android.application")
|
||||||
id("mihon.android.application.compose")
|
id("com.mikepenz.aboutlibraries.plugin")
|
||||||
|
kotlin("android")
|
||||||
kotlin("plugin.parcelize")
|
kotlin("plugin.parcelize")
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
// id("com.github.zellius.shortcut-helper")
|
// id("com.github.zellius.shortcut-helper")
|
||||||
alias(libs.plugins.aboutLibraries)
|
|
||||||
id("com.github.ben-manes.versions")
|
id("com.github.ben-manes.versions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||||
pluginManager.apply {
|
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
||||||
apply(libs.plugins.google.services.get().pluginId)
|
// Firebase Crashlytics
|
||||||
apply(libs.plugins.firebase.crashlytics.get().pluginId)
|
apply(plugin = "com.google.firebase.crashlytics")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// shortcutHelper.setFilePath("./shortcuts.xml")
|
// shortcutHelper.setFilePath("./shortcuts.xml")
|
||||||
|
|
||||||
val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "eu.kanade.tachiyomi"
|
namespace = "eu.kanade.tachiyomi"
|
||||||
@ -31,16 +26,16 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi.sy"
|
applicationId = "eu.kanade.tachiyomi.sy"
|
||||||
|
|
||||||
versionCode = 75
|
versionCode = 64
|
||||||
versionName = "1.12.0"
|
versionName = "1.10.4"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
|
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += supportedAbis
|
abiFilters += SUPPORTED_ABIS
|
||||||
}
|
}
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@ -49,7 +44,7 @@ android {
|
|||||||
abi {
|
abi {
|
||||||
isEnable = true
|
isEnable = true
|
||||||
reset()
|
reset()
|
||||||
include(*supportedAbis.toTypedArray())
|
include(*SUPPORTED_ABIS.toTypedArray())
|
||||||
isUniversalApk = true
|
isUniversalApk = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,8 +66,6 @@ android {
|
|||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
||||||
|
|
||||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = true)}\"")
|
|
||||||
}
|
}
|
||||||
create("benchmark") {
|
create("benchmark") {
|
||||||
initWith(getByName("release"))
|
initWith(getByName("release"))
|
||||||
@ -101,6 +94,8 @@ android {
|
|||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
create("dev") {
|
create("dev") {
|
||||||
|
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
||||||
|
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,16 +103,13 @@ android {
|
|||||||
packaging {
|
packaging {
|
||||||
resources.excludes.addAll(
|
resources.excludes.addAll(
|
||||||
listOf(
|
listOf(
|
||||||
"kotlin-tooling-metadata.json",
|
|
||||||
"META-INF/DEPENDENCIES",
|
"META-INF/DEPENDENCIES",
|
||||||
"LICENSE.txt",
|
"LICENSE.txt",
|
||||||
"META-INF/LICENSE",
|
"META-INF/LICENSE",
|
||||||
"META-INF/**/LICENSE.txt",
|
"META-INF/LICENSE.txt",
|
||||||
"META-INF/*.properties",
|
|
||||||
"META-INF/**/*.properties",
|
|
||||||
"META-INF/README.md",
|
"META-INF/README.md",
|
||||||
"META-INF/NOTICE",
|
"META-INF/NOTICE",
|
||||||
"META-INF/*.version",
|
"META-INF/*.kotlin_module",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -128,6 +120,7 @@ android {
|
|||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
|
compose = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
|
|
||||||
// Disable some unused things
|
// Disable some unused things
|
||||||
@ -140,23 +133,9 @@ android {
|
|||||||
abortOnError = false
|
abortOnError = false
|
||||||
checkReleaseBuilds = false
|
checkReleaseBuilds = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
composeOptions {
|
||||||
compilerOptions {
|
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
||||||
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",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,24 +154,26 @@ dependencies {
|
|||||||
implementation(projects.presentationWidget)
|
implementation(projects.presentationWidget)
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
|
implementation(platform(compose.bom))
|
||||||
implementation(compose.activity)
|
implementation(compose.activity)
|
||||||
implementation(compose.foundation)
|
implementation(compose.foundation)
|
||||||
implementation(compose.material3.core)
|
implementation(compose.material3.core)
|
||||||
|
implementation(compose.material.core)
|
||||||
implementation(compose.material.icons)
|
implementation(compose.material.icons)
|
||||||
implementation(compose.animation)
|
implementation(compose.animation)
|
||||||
implementation(compose.animation.graphics)
|
implementation(compose.animation.graphics)
|
||||||
debugImplementation(compose.ui.tooling)
|
debugImplementation(compose.ui.tooling)
|
||||||
implementation(compose.ui.tooling.preview)
|
implementation(compose.ui.tooling.preview)
|
||||||
implementation(compose.ui.util)
|
implementation(compose.ui.util)
|
||||||
|
implementation(compose.accompanist.webview)
|
||||||
implementation(androidx.interpolator)
|
implementation(compose.accompanist.systemuicontroller)
|
||||||
|
|
||||||
implementation(androidx.paging.runtime)
|
implementation(androidx.paging.runtime)
|
||||||
implementation(androidx.paging.compose)
|
implementation(androidx.paging.compose)
|
||||||
|
|
||||||
implementation(libs.bundles.sqlite)
|
implementation(libs.bundles.sqlite)
|
||||||
// SY -->
|
// SY -->
|
||||||
implementation(sylibs.sqlcipher)
|
implementation(libs.sqlcipher)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
implementation(kotlinx.reflect)
|
implementation(kotlinx.reflect)
|
||||||
@ -234,12 +215,16 @@ dependencies {
|
|||||||
// Disk
|
// Disk
|
||||||
implementation(libs.disklrucache)
|
implementation(libs.disklrucache)
|
||||||
implementation(libs.unifile)
|
implementation(libs.unifile)
|
||||||
|
implementation(libs.junrar)
|
||||||
|
// SY -->
|
||||||
|
implementation(libs.zip4j)
|
||||||
|
// SY <--
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
implementation(libs.preferencektx)
|
implementation(libs.preferencektx)
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
implementation(libs.injekt)
|
implementation(libs.injekt.core)
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
implementation(platform(libs.coil.bom))
|
implementation(platform(libs.coil.bom))
|
||||||
@ -257,23 +242,18 @@ dependencies {
|
|||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
}
|
}
|
||||||
implementation(libs.insetter)
|
implementation(libs.insetter)
|
||||||
implementation(libs.richeditor.compose)
|
implementation(libs.bundles.richtext)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
implementation(libs.compose.materialmotion)
|
implementation(libs.compose.materialmotion)
|
||||||
implementation(libs.swipe)
|
implementation(libs.swipe)
|
||||||
implementation(libs.compose.webview)
|
|
||||||
implementation(libs.compose.grid)
|
|
||||||
implementation(libs.reorderable)
|
|
||||||
implementation(libs.bundles.markdown)
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
|
|
||||||
// Crash reports/analytics
|
// Crash reports/analytics
|
||||||
// "standardImplementation"(platform(libs.firebase.bom))
|
// implementation(libs.bundles.acra)
|
||||||
// "standardImplementation"(libs.firebase.analytics)
|
// "standardImplementation"(libs.firebase.analytics)
|
||||||
// "standardImplementation"(libs.firebase.crashlytics)
|
|
||||||
|
|
||||||
// Shizuku
|
// Shizuku
|
||||||
implementation(libs.bundles.shizuku)
|
implementation(libs.bundles.shizuku)
|
||||||
@ -285,16 +265,13 @@ dependencies {
|
|||||||
// debugImplementation(libs.leakcanary.android)
|
// debugImplementation(libs.leakcanary.android)
|
||||||
implementation(libs.leakcanary.plumber)
|
implementation(libs.leakcanary.plumber)
|
||||||
|
|
||||||
testImplementation(kotlinx.coroutines.test)
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
// Text distance (EH)
|
// Text distance (EH)
|
||||||
implementation(sylibs.simularity)
|
implementation(sylibs.simularity)
|
||||||
|
|
||||||
// Firebase (EH)
|
// Firebase (EH)
|
||||||
implementation(platform(libs.firebase.bom))
|
implementation(sylibs.firebase.analytics)
|
||||||
implementation(libs.firebase.analytics)
|
implementation(sylibs.firebase.crashlytics.ktx)
|
||||||
implementation(libs.firebase.crashlytics)
|
|
||||||
|
|
||||||
// Better logging (EH)
|
// Better logging (EH)
|
||||||
implementation(sylibs.xlog)
|
implementation(sylibs.xlog)
|
||||||
@ -302,20 +279,17 @@ dependencies {
|
|||||||
// RatingBar (SY)
|
// RatingBar (SY)
|
||||||
implementation(sylibs.ratingbar)
|
implementation(sylibs.ratingbar)
|
||||||
implementation(sylibs.composeRatingbar)
|
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)
|
|
||||||
|
|
||||||
// ZXing Android Embedded
|
|
||||||
implementation(sylibs.zxing.android.embedded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
androidComponents {
|
androidComponents {
|
||||||
|
beforeVariants { variantBuilder ->
|
||||||
|
// Disables standardBenchmark
|
||||||
|
if (variantBuilder.buildType == "benchmark") {
|
||||||
|
variantBuilder.enable = variantBuilder.productFlavors.containsAll(
|
||||||
|
listOf("default" to "dev"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
onVariants(selector().withFlavor("default" to "standard")) {
|
onVariants(selector().withFlavor("default" to "standard")) {
|
||||||
// Only excluding in standard flavor because this breaks
|
// Only excluding in standard flavor because this breaks
|
||||||
// Layout Inspector's Compose tree
|
// Layout Inspector's Compose tree
|
||||||
@ -323,6 +297,42 @@ 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=coil.annotation.ExperimentalCoilApi",
|
||||||
|
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
)
|
||||||
|
|
||||||
|
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
|
||||||
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
|
"-P",
|
||||||
|
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||||
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
|
)
|
||||||
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
|
"-P",
|
||||||
|
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||||
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath(kotlinx.gradle)
|
classpath(kotlinx.gradle)
|
||||||
|
28
app/proguard-rules.pro
vendored
28
app/proguard-rules.pro
vendored
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
-keep,allowoptimization class eu.kanade.**
|
-keep,allowoptimization class eu.kanade.**
|
||||||
-keep,allowoptimization class tachiyomi.**
|
-keep,allowoptimization class tachiyomi.**
|
||||||
-keep,allowoptimization class mihon.**
|
|
||||||
|
|
||||||
# Keep common dependencies used in extensions
|
# Keep common dependencies used in extensions
|
||||||
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
||||||
@ -47,10 +46,6 @@
|
|||||||
-dontnote rx.internal.util.PlatformDependent
|
-dontnote rx.internal.util.PlatformDependent
|
||||||
##---------------End: proguard configuration for RxJava 1.x ----------
|
##---------------End: proguard configuration for RxJava 1.x ----------
|
||||||
|
|
||||||
##---------------Begin: proguard configuration for okhttp ----------
|
|
||||||
-keepclasseswithmembers class okhttp3.MultipartBody$Builder { *; }
|
|
||||||
##---------------End: proguard configuration for okhttp ----------
|
|
||||||
|
|
||||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||||
-keepattributes *Annotation*, InnerClasses
|
-keepattributes *Annotation*, InnerClasses
|
||||||
-dontnote kotlinx.serialization.** # core serialization annotations
|
-dontnote kotlinx.serialization.** # core serialization annotations
|
||||||
@ -131,12 +126,6 @@
|
|||||||
-keep class com.google.firebase.installations.** { *; }
|
-keep class com.google.firebase.installations.** { *; }
|
||||||
-keep interface com.google.firebase.installations.** { *; }
|
-keep interface com.google.firebase.installations.** { *; }
|
||||||
|
|
||||||
# Google Drive
|
|
||||||
-keep class com.google.api.services.** { *; }
|
|
||||||
|
|
||||||
# Google OAuth
|
|
||||||
-keep class com.google.api.client.** { *; }
|
|
||||||
|
|
||||||
# SY -->
|
# SY -->
|
||||||
# SqlCipher
|
# SqlCipher
|
||||||
-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteCustomFunction { *; }
|
-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteCustomFunction { *; }
|
||||||
@ -271,9 +260,6 @@
|
|||||||
-keep,allowoptimization class * extends uy.kohesive.injekt.api.TypeReference
|
-keep,allowoptimization class * extends uy.kohesive.injekt.api.TypeReference
|
||||||
-keep,allowoptimization public class io.requery.android.database.sqlite.SQLiteConnection { *; }
|
-keep,allowoptimization public class io.requery.android.database.sqlite.SQLiteConnection { *; }
|
||||||
|
|
||||||
# Keep apache http client
|
|
||||||
-keep class org.apache.http.** { *; }
|
|
||||||
|
|
||||||
# Suggested rules
|
# Suggested rules
|
||||||
-dontwarn com.oracle.svm.core.annotate.AutomaticFeature
|
-dontwarn com.oracle.svm.core.annotate.AutomaticFeature
|
||||||
-dontwarn com.oracle.svm.core.annotate.Delete
|
-dontwarn com.oracle.svm.core.annotate.Delete
|
||||||
@ -286,16 +272,4 @@
|
|||||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||||
-dontwarn java.lang.Module
|
-dontwarn java.lang.Module
|
||||||
-dontwarn org.graalvm.nativeimage.hosted.RuntimeResourceAccess
|
-dontwarn org.graalvm.nativeimage.hosted.RuntimeResourceAccess
|
||||||
-dontwarn org.jspecify.annotations.NullMarked
|
-dontwarn org.jspecify.annotations.NullMarked
|
||||||
-dontwarn javax.naming.InvalidNameException
|
|
||||||
-dontwarn javax.naming.NamingException
|
|
||||||
-dontwarn javax.naming.directory.Attribute
|
|
||||||
-dontwarn javax.naming.directory.Attributes
|
|
||||||
-dontwarn javax.naming.ldap.LdapName
|
|
||||||
-dontwarn javax.naming.ldap.Rdn
|
|
||||||
-dontwarn org.ietf.jgss.GSSContext
|
|
||||||
-dontwarn org.ietf.jgss.GSSCredential
|
|
||||||
-dontwarn org.ietf.jgss.GSSException
|
|
||||||
-dontwarn org.ietf.jgss.GSSManager
|
|
||||||
-dontwarn org.ietf.jgss.GSSName
|
|
||||||
-dontwarn org.ietf.jgss.Oid
|
|
@ -3,4 +3,4 @@
|
|||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
@ -3,4 +3,4 @@
|
|||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
@ -1,11 +0,0 @@
|
|||||||
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,23 +34,11 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<!-- Remove unnecessary permissions from Firebase dependency -->
|
<!-- Remove permission from Firebase dependency -->
|
||||||
<uses-permission
|
|
||||||
android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE"
|
|
||||||
tools:node="remove" />
|
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="com.google.android.gms.permission.AD_ID"
|
android:name="com.google.android.gms.permission.AD_ID"
|
||||||
tools:node="remove" />
|
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
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
@ -200,20 +188,6 @@
|
|||||||
<data android:host="shikimori-auth" />
|
<data android:host="shikimori-auth" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".ui.setting.track.GoogleDriveLoginActivity"
|
|
||||||
android:label="GoogleDrive"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:scheme="eu.kanade.google.oauth" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="exh.ui.login.EhLoginActivity"
|
android:name="exh.ui.login.EhLoginActivity"
|
||||||
@ -268,14 +242,6 @@
|
|||||||
android:name="android.webkit.WebView.MetricsOptOut"
|
android:name="android.webkit.WebView.MetricsOptOut"
|
||||||
android:value="true" />
|
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 -->
|
<!-- Disable advertising ID collection for Firebase -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="google_analytics_adid_collection_enabled"
|
android:name="google_analytics_adid_collection_enabled"
|
||||||
@ -359,7 +325,7 @@
|
|||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:scheme="http" />
|
<data android:scheme="http" />
|
||||||
|
|
||||||
<data android:host="pururin.me" />
|
<data android:host="pururin.io" />
|
||||||
|
|
||||||
<data android:pathPattern="/gallery/..*" />
|
<data android:pathPattern="/gallery/..*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@ -413,13 +379,6 @@
|
|||||||
android:scheme="tachiyomisy" />
|
android:scheme="tachiyomisy" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
|
||||||
tools:remove="screenOrientation" />
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="rikka.shizuku.api"
|
|
||||||
tools:ignore="ManifestOrder" />
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package eu.kanade.core.util
|
package eu.kanade.core.util
|
||||||
|
|
||||||
import androidx.compose.ui.util.fastFilter
|
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||||
generator: (before: T?, after: T?) -> R?,
|
generator: (T?, T?) -> R?,
|
||||||
): List<R> {
|
): List<R> {
|
||||||
if (isEmpty()) return emptyList()
|
if (isEmpty()) return emptyList()
|
||||||
val newList = mutableListOf<R>()
|
val newList = mutableListOf<R>()
|
||||||
@ -20,24 +19,6 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
|||||||
return newList
|
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) {
|
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||||
if (shouldAdd) {
|
if (shouldAdd) {
|
||||||
add(value)
|
add(value)
|
||||||
@ -46,6 +27,21 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only elements matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val destination = ArrayList<T>()
|
||||||
|
fastForEach { if (predicate(it)) destination.add(it) }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list containing all elements not matching the given [predicate].
|
* Returns a list containing all elements not matching the given [predicate].
|
||||||
*
|
*
|
||||||
@ -56,7 +52,27 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
|||||||
@OptIn(ExperimentalContracts::class)
|
@OptIn(ExperimentalContracts::class)
|
||||||
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
||||||
contract { callsInPlace(predicate) }
|
contract { callsInPlace(predicate) }
|
||||||
return fastFilter { !predicate(it) }
|
val destination = ArrayList<T>()
|
||||||
|
fastForEach { if (!predicate(it)) destination.add(it) }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only the non-null results of applying the
|
||||||
|
* given [transform] function to each element in the original collection.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
|
||||||
|
contract { callsInPlace(transform) }
|
||||||
|
val destination = ArrayList<R>()
|
||||||
|
fastForEach { element ->
|
||||||
|
transform(element)?.let(destination::add)
|
||||||
|
}
|
||||||
|
return destination
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,3 +113,26 @@ inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
|
|||||||
fastForEach { if (predicate(it)) --count }
|
fastForEach { if (predicate(it)) --count }
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only elements from the given collection
|
||||||
|
* having distinct keys returned by the given [selector] function.
|
||||||
|
*
|
||||||
|
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
|
||||||
|
* The elements in the resulting list are in the same order as they were in the source collection.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
|
||||||
|
contract { callsInPlace(selector) }
|
||||||
|
val set = HashSet<K>()
|
||||||
|
val list = ArrayList<T>()
|
||||||
|
fastForEach {
|
||||||
|
val key = selector(it)
|
||||||
|
if (set.add(key)) list.add(it)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
@ -4,7 +4,10 @@ import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
|
|||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
|
import eu.kanade.domain.extension.interactor.CreateExtensionRepo
|
||||||
|
import eu.kanade.domain.extension.interactor.DeleteExtensionRepo
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
|
import eu.kanade.domain.extension.interactor.GetExtensionRepos
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
import eu.kanade.domain.extension.interactor.TrustExtension
|
import eu.kanade.domain.extension.interactor.TrustExtension
|
||||||
@ -13,11 +16,9 @@ import eu.kanade.domain.manga.interactor.SetExcludedScanlators
|
|||||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
import eu.kanade.domain.source.interactor.GetIncognitoState
|
|
||||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.interactor.ToggleIncognito
|
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
@ -25,20 +26,6 @@ import eu.kanade.domain.track.interactor.AddTracks
|
|||||||
import eu.kanade.domain.track.interactor.RefreshTracks
|
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||||
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||||
import eu.kanade.domain.track.interactor.TrackChapter
|
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
|
|
||||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
|
|
||||||
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
|
|
||||||
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
|
||||||
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
|
||||||
import mihon.domain.extensionrepo.service.ExtensionRepoService
|
|
||||||
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
|
||||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||||
import tachiyomi.data.history.HistoryRepositoryImpl
|
import tachiyomi.data.history.HistoryRepositoryImpl
|
||||||
@ -82,7 +69,6 @@ import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
|||||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
||||||
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||||
import tachiyomi.domain.release.service.ReleaseService
|
import tachiyomi.domain.release.service.ReleaseService
|
||||||
@ -97,7 +83,11 @@ import tachiyomi.domain.track.interactor.InsertTrack
|
|||||||
import tachiyomi.domain.track.repository.TrackRepository
|
import tachiyomi.domain.track.repository.TrackRepository
|
||||||
import tachiyomi.domain.updates.interactor.GetUpdates
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
import tachiyomi.domain.updates.repository.UpdatesRepository
|
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||||
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
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 {
|
class DomainModule : InjektModule {
|
||||||
|
|
||||||
@ -111,7 +101,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { RenameCategory(get()) }
|
addFactory { RenameCategory(get()) }
|
||||||
addFactory { ReorderCategory(get()) }
|
addFactory { ReorderCategory(get()) }
|
||||||
addFactory { UpdateCategory(get()) }
|
addFactory { UpdateCategory(get()) }
|
||||||
addFactory { DeleteCategory(get(), get(), get()) }
|
addFactory { DeleteCategory(get()) }
|
||||||
|
|
||||||
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
||||||
addFactory { GetDuplicateLibraryManga(get()) }
|
addFactory { GetDuplicateLibraryManga(get()) }
|
||||||
@ -121,7 +111,6 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetMangaByUrlAndSourceId(get()) }
|
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||||
addFactory { GetManga(get()) }
|
addFactory { GetManga(get()) }
|
||||||
addFactory { GetNextChapters(get(), get(), get(), get()) }
|
addFactory { GetNextChapters(get(), get(), get(), get()) }
|
||||||
addFactory { GetUpcomingManga(get()) }
|
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
addFactory { FetchInterval(get()) }
|
addFactory { FetchInterval(get()) }
|
||||||
@ -129,7 +118,6 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { SetMangaViewerFlags(get()) }
|
addFactory { SetMangaViewerFlags(get()) }
|
||||||
addFactory { NetworkToLocalManga(get()) }
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
addFactory { UpdateManga(get(), get()) }
|
addFactory { UpdateManga(get(), get()) }
|
||||||
addFactory { UpdateMangaNotes(get()) }
|
|
||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
addFactory { GetExcludedScanlators(get()) }
|
addFactory { GetExcludedScanlators(get()) }
|
||||||
addFactory { SetExcludedScanlators(get()) }
|
addFactory { SetExcludedScanlators(get()) }
|
||||||
@ -154,9 +142,8 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { UpdateChapter(get()) }
|
addFactory { UpdateChapter(get()) }
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
|
||||||
addFactory { ShouldUpdateDbChapter() }
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
addFactory { GetAvailableScanlators(get()) }
|
addFactory { GetAvailableScanlators(get()) }
|
||||||
addFactory { FilterChaptersForDownload(get(), get(), get(), get()) }
|
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
addFactory { GetHistory(get()) }
|
addFactory { GetHistory(get()) }
|
||||||
@ -184,17 +171,10 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { ToggleLanguage(get()) }
|
addFactory { ToggleLanguage(get()) }
|
||||||
addFactory { ToggleSource(get()) }
|
addFactory { ToggleSource(get()) }
|
||||||
addFactory { ToggleSourcePin(get()) }
|
addFactory { ToggleSourcePin(get()) }
|
||||||
addFactory { TrustExtension(get(), get()) }
|
addFactory { TrustExtension(get()) }
|
||||||
|
|
||||||
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
|
addFactory { CreateExtensionRepo(get()) }
|
||||||
addFactory { ExtensionRepoService(get(), get()) }
|
|
||||||
addFactory { GetExtensionRepo(get()) }
|
|
||||||
addFactory { GetExtensionRepoCount(get()) }
|
|
||||||
addFactory { CreateExtensionRepo(get(), get()) }
|
|
||||||
addFactory { DeleteExtensionRepo(get()) }
|
addFactory { DeleteExtensionRepo(get()) }
|
||||||
addFactory { ReplaceExtensionRepo(get()) }
|
addFactory { GetExtensionRepos(get()) }
|
||||||
addFactory { UpdateExtensionRepo(get(), get()) }
|
|
||||||
addFactory { ToggleIncognito(get()) }
|
|
||||||
addFactory { GetIncognitoState(get(), get(), get()) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,6 @@ import eu.kanade.domain.source.interactor.GetSourceCategories
|
|||||||
import eu.kanade.domain.source.interactor.RenameSourceCategory
|
import eu.kanade.domain.source.interactor.RenameSourceCategory
|
||||||
import eu.kanade.domain.source.interactor.SetSourceCategories
|
import eu.kanade.domain.source.interactor.SetSourceCategories
|
||||||
import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
|
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 eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
import exh.search.SearchEngine
|
import exh.search.SearchEngine
|
||||||
import tachiyomi.data.manga.CustomMangaRepositoryImpl
|
import tachiyomi.data.manga.CustomMangaRepositoryImpl
|
||||||
@ -28,6 +25,7 @@ import tachiyomi.data.source.SavedSearchRepositoryImpl
|
|||||||
import tachiyomi.domain.chapter.interactor.DeleteChapters
|
import tachiyomi.domain.chapter.interactor.DeleteChapters
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByUrl
|
import tachiyomi.domain.chapter.interactor.GetChapterByUrl
|
||||||
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistoryByMangaId
|
||||||
import tachiyomi.domain.manga.interactor.DeleteByMergeId
|
import tachiyomi.domain.manga.interactor.DeleteByMergeId
|
||||||
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
|
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
|
||||||
import tachiyomi.domain.manga.interactor.DeleteMangaById
|
import tachiyomi.domain.manga.interactor.DeleteMangaById
|
||||||
@ -44,7 +42,7 @@ import tachiyomi.domain.manga.interactor.GetMergedManga
|
|||||||
import tachiyomi.domain.manga.interactor.GetMergedMangaById
|
import tachiyomi.domain.manga.interactor.GetMergedMangaById
|
||||||
import tachiyomi.domain.manga.interactor.GetMergedMangaForDownloading
|
import tachiyomi.domain.manga.interactor.GetMergedMangaForDownloading
|
||||||
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
|
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
|
||||||
import tachiyomi.domain.manga.interactor.GetReadMangaNotInLibraryView
|
import tachiyomi.domain.manga.interactor.GetReadMangaNotInLibrary
|
||||||
import tachiyomi.domain.manga.interactor.GetSearchMetadata
|
import tachiyomi.domain.manga.interactor.GetSearchMetadata
|
||||||
import tachiyomi.domain.manga.interactor.GetSearchTags
|
import tachiyomi.domain.manga.interactor.GetSearchTags
|
||||||
import tachiyomi.domain.manga.interactor.GetSearchTitles
|
import tachiyomi.domain.manga.interactor.GetSearchTitles
|
||||||
@ -73,7 +71,11 @@ import tachiyomi.domain.source.interactor.InsertSavedSearch
|
|||||||
import tachiyomi.domain.source.repository.FeedSavedSearchRepository
|
import tachiyomi.domain.source.repository.FeedSavedSearchRepository
|
||||||
import tachiyomi.domain.source.repository.SavedSearchRepository
|
import tachiyomi.domain.source.repository.SavedSearchRepository
|
||||||
import tachiyomi.domain.track.interactor.IsTrackUnfollowed
|
import tachiyomi.domain.track.interactor.IsTrackUnfollowed
|
||||||
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
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
|
import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
||||||
|
|
||||||
class SYDomainModule : InjektModule {
|
class SYDomainModule : InjektModule {
|
||||||
@ -87,6 +89,7 @@ class SYDomainModule : InjektModule {
|
|||||||
addFactory { DeleteChapters(get()) }
|
addFactory { DeleteChapters(get()) }
|
||||||
addFactory { DeleteMangaById(get()) }
|
addFactory { DeleteMangaById(get()) }
|
||||||
addFactory { FilterSerializer() }
|
addFactory { FilterSerializer() }
|
||||||
|
addFactory { GetHistoryByMangaId(get()) }
|
||||||
addFactory { GetChapterByUrl(get()) }
|
addFactory { GetChapterByUrl(get()) }
|
||||||
addFactory { GetSourceCategories(get()) }
|
addFactory { GetSourceCategories(get()) }
|
||||||
addFactory { CreateSourceCategory(get()) }
|
addFactory { CreateSourceCategory(get()) }
|
||||||
@ -99,7 +102,7 @@ class SYDomainModule : InjektModule {
|
|||||||
addFactory { GetPagePreviews(get(), get()) }
|
addFactory { GetPagePreviews(get(), get()) }
|
||||||
addFactory { SearchEngine() }
|
addFactory { SearchEngine() }
|
||||||
addFactory { IsTrackUnfollowed() }
|
addFactory { IsTrackUnfollowed() }
|
||||||
addFactory { GetReadMangaNotInLibraryView(get()) }
|
addFactory { GetReadMangaNotInLibrary(get()) }
|
||||||
|
|
||||||
// Required for [MetadataSource]
|
// Required for [MetadataSource]
|
||||||
addFactory<MetadataSource.GetMangaId> { GetManga(get()) }
|
addFactory<MetadataSource.GetMangaId> { GetManga(get()) }
|
||||||
|
@ -2,7 +2,8 @@ package eu.kanade.domain.base
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@ -21,6 +22,8 @@ class BasePreferences(
|
|||||||
|
|
||||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||||
|
|
||||||
|
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||||
|
|
||||||
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||||
|
|
||||||
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
|
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
|
||||||
@ -29,10 +32,4 @@ class BasePreferences(
|
|||||||
SHIZUKU(MR.strings.ext_installer_shizuku, false),
|
SHIZUKU(MR.strings.ext_installer_shizuku, false),
|
||||||
PRIVATE(MR.strings.ext_installer_private, false),
|
PRIVATE(MR.strings.ext_installer_private, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
|
||||||
|
|
||||||
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
|
|
||||||
|
|
||||||
fun alwaysDecodeLongStripWithSSIV() = preferenceStore.getBoolean("pref_always_decode_long_strip_with_ssiv", false)
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import tachiyomi.domain.chapter.model.NoChaptersException
|
|||||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
import tachiyomi.domain.chapter.service.ChapterRecognition
|
import tachiyomi.domain.chapter.service.ChapterRecognition
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
@ -36,7 +35,6 @@ class SyncChaptersWithSource(
|
|||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
private val getExcludedScanlators: GetExcludedScanlators,
|
private val getExcludedScanlators: GetExcludedScanlators,
|
||||||
private val libraryPreferences: LibraryPreferences,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,18 +150,12 @@ class SyncChaptersWithSource(
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val changedOrDuplicateReadUrls = mutableSetOf<String>()
|
val reAdded = mutableListOf<Chapter>()
|
||||||
|
|
||||||
val deletedChapterNumbers = TreeSet<Double>()
|
val deletedChapterNumbers = TreeSet<Double>()
|
||||||
val deletedReadChapterNumbers = TreeSet<Double>()
|
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||||
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||||
|
|
||||||
val readChapterNumbers = dbChapters
|
|
||||||
.asSequence()
|
|
||||||
.filter { it.read && it.isRecognizedNumber }
|
|
||||||
.map { it.chapterNumber }
|
|
||||||
.toSet()
|
|
||||||
|
|
||||||
removedChapters.forEach { chapter ->
|
removedChapters.forEach { chapter ->
|
||||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||||
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
||||||
@ -173,20 +165,12 @@ class SyncChaptersWithSource(
|
|||||||
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
||||||
.associate { it.chapterNumber to it.dateFetch }
|
.associate { it.chapterNumber to it.dateFetch }
|
||||||
|
|
||||||
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
|
|
||||||
.contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_NEW)
|
|
||||||
|
|
||||||
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
var itemCount = newChapters.size
|
var itemCount = newChapters.size
|
||||||
var updatedToAdd = newChapters.map { toAddItem ->
|
var updatedToAdd = newChapters.map { toAddItem ->
|
||||||
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||||
|
|
||||||
if (chapter.chapterNumber in readChapterNumbers && markDuplicateAsRead) {
|
|
||||||
changedOrDuplicateReadUrls.add(chapter.url)
|
|
||||||
chapter = chapter.copy(read = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chapter.isRecognizedNumber || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
if (!chapter.isRecognizedNumber || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||||
|
|
||||||
chapter = chapter.copy(
|
chapter = chapter.copy(
|
||||||
@ -199,19 +183,19 @@ class SyncChaptersWithSource(
|
|||||||
chapter = chapter.copy(dateFetch = it)
|
chapter = chapter.copy(dateFetch = it)
|
||||||
}
|
}
|
||||||
|
|
||||||
changedOrDuplicateReadUrls.add(chapter.url)
|
reAdded.add(chapter)
|
||||||
|
|
||||||
chapter
|
chapter
|
||||||
}
|
}
|
||||||
|
|
||||||
// --> EXH (carry over reading progress)
|
// --> EXH (carry over reading progress)
|
||||||
if (manga.isEhBasedManga()) {
|
if (manga.isEhBasedManga()) {
|
||||||
val finalAdded = updatedToAdd.filterNot { it.url in changedOrDuplicateReadUrls }
|
val finalAdded = updatedToAdd.subtract(reAdded)
|
||||||
if (finalAdded.isNotEmpty()) {
|
if (finalAdded.isNotEmpty()) {
|
||||||
val max = dbChapters.maxOfOrNull { it.lastPageRead }
|
val max = dbChapters.maxOfOrNull { it.lastPageRead }
|
||||||
if (max != null && max > 0) {
|
if (max != null && max > 0) {
|
||||||
updatedToAdd = updatedToAdd.map {
|
updatedToAdd = updatedToAdd.map {
|
||||||
if (it.url !in changedOrDuplicateReadUrls) {
|
if (it !in reAdded) {
|
||||||
it.copy(lastPageRead = max)
|
it.copy(lastPageRead = max)
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
@ -241,8 +225,12 @@ class SyncChaptersWithSource(
|
|||||||
// Note that last_update actually represents last time the chapter list changed at all
|
// Note that last_update actually represents last time the chapter list changed at all
|
||||||
updateManga.awaitUpdateLastUpdate(manga.id)
|
updateManga.awaitUpdateLastUpdate(manga.id)
|
||||||
|
|
||||||
|
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
||||||
|
|
||||||
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
||||||
|
|
||||||
return updatedToAdd.filterNot { it.url in changedOrDuplicateReadUrls || it.scanlator in excludedScanlators }
|
return updatedToAdd.filterNot {
|
||||||
|
it.url in reAddedUrls || it.scanlator in excludedScanlators
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,5 +39,4 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
|||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.chapter_number = chapterNumber.toFloat()
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
it.source_order = sourceOrder.toInt()
|
it.source_order = sourceOrder.toInt()
|
||||||
it.last_modified = lastModifiedAt
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import tachiyomi.core.common.preference.plusAssign
|
||||||
|
|
||||||
|
class CreateExtensionRepo(private val preferences: SourcePreferences) {
|
||||||
|
|
||||||
|
fun await(name: String): Result {
|
||||||
|
// Do not allow invalid formats
|
||||||
|
if (!name.matches(repoRegex)) {
|
||||||
|
return Result.InvalidUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences.extensionRepos() += name.removeSuffix("/index.min.json")
|
||||||
|
|
||||||
|
return Result.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Result {
|
||||||
|
data object InvalidUrl : Result
|
||||||
|
data object Success : Result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val repoRegex = """^https://.*/index\.min\.json$""".toRegex()
|
@ -0,0 +1,11 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import tachiyomi.core.common.preference.minusAssign
|
||||||
|
|
||||||
|
class DeleteExtensionRepo(private val preferences: SourcePreferences) {
|
||||||
|
|
||||||
|
fun await(repo: String) {
|
||||||
|
preferences.extensionRepos() -= repo
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class GetExtensionRepos(private val preferences: SourcePreferences) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<Set<String>> {
|
||||||
|
return preferences.extensionRepos().changes()
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ class GetExtensionsByType(
|
|||||||
extensionManager.installedExtensionsFlow,
|
extensionManager.installedExtensionsFlow,
|
||||||
extensionManager.untrustedExtensionsFlow,
|
extensionManager.untrustedExtensionsFlow,
|
||||||
extensionManager.availableExtensionsFlow,
|
extensionManager.availableExtensionsFlow,
|
||||||
) { enabledLanguages, _installed, _untrusted, _available ->
|
) { _activeLanguages, _installed, _untrusted, _available ->
|
||||||
val (updates, installed) = _installed
|
val (updates, installed) = _installed
|
||||||
.filter { (showNsfwSources || !it.isNsfw) }
|
.filter { (showNsfwSources || !it.isNsfw) }
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
@ -41,9 +41,9 @@ class GetExtensionsByType(
|
|||||||
}
|
}
|
||||||
.flatMap { ext ->
|
.flatMap { ext ->
|
||||||
if (ext.sources.isEmpty()) {
|
if (ext.sources.isEmpty()) {
|
||||||
return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
|
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList()
|
||||||
}
|
}
|
||||||
ext.sources.filter { it.lang in enabledLanguages }
|
ext.sources.filter { it.lang in _activeLanguages }
|
||||||
.map {
|
.map {
|
||||||
ext.copy(
|
ext.copy(
|
||||||
name = it.name,
|
name = it.name,
|
||||||
|
@ -3,18 +3,15 @@ package eu.kanade.domain.extension.interactor
|
|||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import androidx.core.content.pm.PackageInfoCompat
|
import androidx.core.content.pm.PackageInfoCompat
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
|
||||||
import tachiyomi.core.common.preference.getAndSet
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
|
|
||||||
class TrustExtension(
|
class TrustExtension(
|
||||||
private val extensionRepoRepository: ExtensionRepoRepository,
|
|
||||||
private val preferences: SourcePreferences,
|
private val preferences: SourcePreferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
|
fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean {
|
||||||
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
|
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash"
|
||||||
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
|
return key in preferences.trustedExtensions().get()
|
||||||
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
|
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
|
||||||
@ -22,7 +19,9 @@ class TrustExtension(
|
|||||||
// Remove previously trusted versions
|
// Remove previously trusted versions
|
||||||
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
|
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
|
||||||
|
|
||||||
removed.also { it += "$pkgName:$versionCode:$signatureHash" }
|
removed.also {
|
||||||
|
it += "$pkgName:$versionCode:$signatureHash"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class GetPagePreviews(
|
|||||||
return try {
|
return try {
|
||||||
val pagePreviews = try {
|
val pagePreviews = try {
|
||||||
pagePreviewCache.getPageListFromCache(manga, chapterIds, page)
|
pagePreviewCache.getPageListFromCache(manga, chapterIds, page)
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also {
|
source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also {
|
||||||
pagePreviewCache.putPageListToCache(manga, chapterIds, it)
|
pagePreviewCache.putPageListToCache(manga, chapterIds, it)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import eu.kanade.domain.manga.model.hasCustomCover
|
|||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
|
||||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
@ -33,8 +32,9 @@ class UpdateManga(
|
|||||||
remoteManga: SManga,
|
remoteManga: SManga,
|
||||||
manualFetch: Boolean,
|
manualFetch: Boolean,
|
||||||
coverCache: CoverCache = Injekt.get(),
|
coverCache: CoverCache = Injekt.get(),
|
||||||
libraryPreferences: LibraryPreferences = Injekt.get(),
|
// SY -->
|
||||||
downloadManager: DownloadManager = Injekt.get(),
|
downloadManager: DownloadManager = Injekt.get(),
|
||||||
|
// SY <--
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val remoteTitle = try {
|
val remoteTitle = try {
|
||||||
remoteManga.title
|
remoteManga.title
|
||||||
@ -42,13 +42,14 @@ class UpdateManga(
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the manga isn't a favorite (or 'update titles' preference is enabled), set its title from source and update in db
|
// SY -->
|
||||||
val title =
|
val title = if (remoteTitle.isNotBlank() && localManga.ogTitle != remoteTitle) {
|
||||||
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles().get())) {
|
downloadManager.renameMangaDir(localManga.ogTitle, remoteTitle, localManga.source)
|
||||||
remoteTitle
|
remoteTitle
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
val coverLastModified =
|
val coverLastModified =
|
||||||
when {
|
when {
|
||||||
@ -68,7 +69,7 @@ class UpdateManga(
|
|||||||
|
|
||||||
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
val success = mangaRepository.update(
|
return mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = localManga.id,
|
id = localManga.id,
|
||||||
title = title,
|
title = title,
|
||||||
@ -83,10 +84,6 @@ class UpdateManga(
|
|||||||
initialized = true,
|
initialized = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (success && title != null) {
|
|
||||||
downloadManager.renameManga(localManga, title)
|
|
||||||
}
|
|
||||||
return success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateFetchInterval(
|
suspend fun awaitUpdateFetchInterval(
|
||||||
|
@ -23,7 +23,7 @@ val Manga.readerOrientation: Long
|
|||||||
|
|
||||||
val Manga.downloadedFilter: TriState
|
val Manga.downloadedFilter: TriState
|
||||||
get() {
|
get() {
|
||||||
if (Injekt.get<BasePreferences>().downloadedOnly().get()) return TriState.ENABLED_IS
|
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||||
return when (downloadedFilterRaw) {
|
return when (downloadedFilterRaw) {
|
||||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||||
@ -35,17 +35,18 @@ fun Manga.chaptersFiltered(): Boolean {
|
|||||||
downloadedFilter != TriState.DISABLED ||
|
downloadedFilter != TriState.DISABLED ||
|
||||||
bookmarkedFilter != TriState.DISABLED
|
bookmarkedFilter != TriState.DISABLED
|
||||||
}
|
}
|
||||||
|
fun Manga.forceDownloaded(): Boolean {
|
||||||
|
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||||
|
}
|
||||||
|
|
||||||
fun Manga.toSManga(): SManga = SManga.create().also {
|
fun Manga.toSManga(): SManga = SManga.create().also {
|
||||||
it.url = url
|
it.url = url
|
||||||
// SY -->
|
it.title = title
|
||||||
it.title = ogTitle
|
it.artist = artist
|
||||||
it.artist = ogArtist
|
it.author = author
|
||||||
it.author = ogAuthor
|
it.description = description
|
||||||
it.description = ogDescription
|
it.genre = genre.orEmpty().joinToString()
|
||||||
it.genre = ogGenre.orEmpty().joinToString()
|
it.status = status.toInt()
|
||||||
it.status = ogStatus.toInt()
|
|
||||||
// SY <--
|
|
||||||
it.thumbnail_url = thumbnailUrl
|
it.thumbnail_url = thumbnailUrl
|
||||||
it.initialized = initialized
|
it.initialized = initialized
|
||||||
}
|
}
|
||||||
@ -78,6 +79,24 @@ fun Manga.copyFrom(other: SManga): Manga {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun SManga.toDomainManga(sourceId: Long): Manga {
|
||||||
|
return Manga.create().copy(
|
||||||
|
url = url,
|
||||||
|
// SY -->
|
||||||
|
ogTitle = title,
|
||||||
|
ogArtist = artist,
|
||||||
|
ogAuthor = author,
|
||||||
|
ogThumbnailUrl = thumbnail_url,
|
||||||
|
ogDescription = description,
|
||||||
|
ogGenre = getGenres(),
|
||||||
|
ogStatus = status.toLong(),
|
||||||
|
// SY <--
|
||||||
|
updateStrategy = update_strategy,
|
||||||
|
initialized = initialized,
|
||||||
|
source = sourceId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
||||||
return coverCache.getCustomCoverFile(id).exists()
|
return coverCache.getCustomCoverFile(id).exists()
|
||||||
}
|
}
|
||||||
@ -85,13 +104,7 @@ fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
|||||||
/**
|
/**
|
||||||
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
||||||
*/
|
*/
|
||||||
fun getComicInfo(
|
fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories: List<String>?) = ComicInfo(
|
||||||
manga: Manga,
|
|
||||||
chapter: Chapter,
|
|
||||||
urls: List<String>,
|
|
||||||
categories: List<String>?,
|
|
||||||
sourceName: String,
|
|
||||||
) = ComicInfo(
|
|
||||||
title = ComicInfo.Title(chapter.name),
|
title = ComicInfo.Title(chapter.name),
|
||||||
series = ComicInfo.Series(manga.title),
|
series = ComicInfo.Series(manga.title),
|
||||||
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
|
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
|
||||||
@ -101,7 +114,7 @@ fun getComicInfo(
|
|||||||
ComicInfo.Number(it.toString())
|
ComicInfo.Number(it.toString())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
web = ComicInfo.Web(urls.joinToString(" ")),
|
web = ComicInfo.Web(chapterUrl),
|
||||||
summary = manga.description?.let { ComicInfo.Summary(it) },
|
summary = manga.description?.let { ComicInfo.Summary(it) },
|
||||||
writer = manga.author?.let { ComicInfo.Writer(it) },
|
writer = manga.author?.let { ComicInfo.Writer(it) },
|
||||||
penciller = manga.artist?.let { ComicInfo.Penciller(it) },
|
penciller = manga.artist?.let { ComicInfo.Penciller(it) },
|
||||||
@ -111,7 +124,6 @@ fun getComicInfo(
|
|||||||
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
||||||
),
|
),
|
||||||
categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) },
|
categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) },
|
||||||
source = ComicInfo.SourceMihon(sourceName),
|
|
||||||
// SY -->
|
// SY -->
|
||||||
padding = CbzCrypto.createComicInfoPadding()?.let { ComicInfo.PaddingTachiyomiSY(it) },
|
padding = CbzCrypto.createComicInfoPadding()?.let { ComicInfo.PaddingTachiyomiSY(it) },
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -32,11 +32,10 @@ class GetEnabledSources(
|
|||||||
) { a, b, c -> Triple(a, b, c) },
|
) { a, b, c -> Triple(a, b, c) },
|
||||||
// SY <--
|
// SY <--
|
||||||
repository.getSources(),
|
repository.getSources(),
|
||||||
) {
|
) { pinnedSourceIds,
|
||||||
pinnedSourceIds,
|
(enabledLanguages, disabledSources, lastUsedSource),
|
||||||
(enabledLanguages, disabledSources, lastUsedSource),
|
(excludedFromDataSaver, sourcesInCategories, sourceCategoriesFilter),
|
||||||
(excludedFromDataSaver, sourcesInCategories, sourceCategoriesFilter),
|
sources,
|
||||||
sources,
|
|
||||||
->
|
->
|
||||||
|
|
||||||
val sourcesAndCategories = sourcesInCategories.map {
|
val sourcesAndCategories = sourcesInCategories.map {
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
|
|
||||||
class GetIncognitoState(
|
|
||||||
private val basePreferences: BasePreferences,
|
|
||||||
private val sourcePreferences: SourcePreferences,
|
|
||||||
private val extensionManager: ExtensionManager,
|
|
||||||
) {
|
|
||||||
fun await(sourceId: Long?): Boolean {
|
|
||||||
if (basePreferences.incognitoMode().get()) return true
|
|
||||||
if (sourceId == null) return false
|
|
||||||
val extensionPackage = extensionManager.getExtensionPackage(sourceId) ?: return false
|
|
||||||
|
|
||||||
return extensionPackage in sourcePreferences.incognitoExtensions().get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun subscribe(sourceId: Long?): Flow<Boolean> {
|
|
||||||
if (sourceId == null) return basePreferences.incognitoMode().changes()
|
|
||||||
|
|
||||||
return combine(
|
|
||||||
basePreferences.incognitoMode().changes(),
|
|
||||||
sourcePreferences.incognitoExtensions().changes(),
|
|
||||||
extensionManager.getExtensionPackageAsFlow(sourceId),
|
|
||||||
) { incognito, incognitoExtensions, extensionPackage ->
|
|
||||||
incognito || (extensionPackage in incognitoExtensions)
|
|
||||||
}
|
|
||||||
.distinctUntilChanged()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import tachiyomi.core.common.preference.getAndSet
|
|
||||||
|
|
||||||
class ToggleIncognito(
|
|
||||||
private val preferences: SourcePreferences,
|
|
||||||
) {
|
|
||||||
fun await(extensions: String, enable: Boolean) {
|
|
||||||
preferences.incognitoExtensions().getAndSet {
|
|
||||||
if (enable) it.plus(extensions) else it.minus(extensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,8 +22,6 @@ class SourcePreferences(
|
|||||||
|
|
||||||
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
|
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
|
||||||
|
|
||||||
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet())
|
|
||||||
|
|
||||||
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
fun lastUsedSource() = preferenceStore.getLong(
|
fun lastUsedSource() = preferenceStore.getLong(
|
||||||
@ -51,11 +49,6 @@ class SourcePreferences(
|
|||||||
emptySet(),
|
emptySet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun globalSearchFilterState() = preferenceStore.getBoolean(
|
|
||||||
Preference.appStateKey("has_filters_toggle_state"),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true)
|
fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true)
|
||||||
|
|
||||||
@ -88,32 +81,5 @@ class SourcePreferences(
|
|||||||
BANDWIDTH_HERO,
|
BANDWIDTH_HERO,
|
||||||
WSRV_NL,
|
WSRV_NL,
|
||||||
}
|
}
|
||||||
|
|
||||||
fun migrateFlags() = preferenceStore.getInt("migrate_flags", Int.MAX_VALUE)
|
|
||||||
|
|
||||||
fun defaultMangaOrder() = preferenceStore.getString("default_manga_order", "")
|
|
||||||
|
|
||||||
fun migrationSources() = preferenceStore.getString("migrate_sources", "")
|
|
||||||
|
|
||||||
fun smartMigration() = preferenceStore.getBoolean("smart_migrate", false)
|
|
||||||
|
|
||||||
fun useSourceWithMost() = preferenceStore.getBoolean("use_source_with_most", false)
|
|
||||||
|
|
||||||
fun skipPreMigration() = preferenceStore.getBoolean(Preference.appStateKey("skip_pre_migration"), false)
|
|
||||||
|
|
||||||
fun hideNotFoundMigration() = preferenceStore.getBoolean("hide_not_found_migration", false)
|
|
||||||
|
|
||||||
fun showOnlyUpdatesMigration() = preferenceStore.getBoolean("show_only_updates_migration", false)
|
|
||||||
|
|
||||||
fun allowLocalSourceHiddenFolders() = preferenceStore.getBoolean("allow_local_source_hidden_folders", false)
|
|
||||||
|
|
||||||
fun preferredMangaDexId() = preferenceStore.getString("preferred_mangaDex_id", "0")
|
|
||||||
|
|
||||||
fun mangadexSyncToLibraryIndexes() = preferenceStore.getStringSet(
|
|
||||||
"pref_mangadex_sync_to_library_indexes",
|
|
||||||
emptySet(),
|
|
||||||
)
|
|
||||||
|
|
||||||
fun recommendationSearchFlags() = preferenceStore.getInt("rec_search_flags", Int.MAX_VALUE)
|
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
package eu.kanade.domain.sync
|
|
||||||
|
|
||||||
import eu.kanade.domain.sync.models.SyncSettings
|
|
||||||
import eu.kanade.tachiyomi.data.sync.models.SyncTriggerOptions
|
|
||||||
import tachiyomi.core.common.preference.Preference
|
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
class SyncPreferences(
|
|
||||||
private val preferenceStore: PreferenceStore,
|
|
||||||
) {
|
|
||||||
fun clientHost() = preferenceStore.getString("sync_client_host", "https://sync.tachiyomi.org")
|
|
||||||
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 syncInterval() = preferenceStore.getInt("sync_interval", 0)
|
|
||||||
fun syncService() = preferenceStore.getInt("sync_service", 0)
|
|
||||||
|
|
||||||
fun googleDriveAccessToken() = preferenceStore.getString(
|
|
||||||
Preference.appStateKey("google_drive_access_token"),
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
fun googleDriveRefreshToken() = preferenceStore.getString(
|
|
||||||
Preference.appStateKey("google_drive_refresh_token"),
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
fun uniqueDeviceID(): String {
|
|
||||||
val uniqueIDPreference = preferenceStore.getString(Preference.appStateKey("unique_device_id"), "")
|
|
||||||
|
|
||||||
// Retrieve the current value of the preference
|
|
||||||
var uniqueID = uniqueIDPreference.get()
|
|
||||||
if (uniqueID.isBlank()) {
|
|
||||||
uniqueID = UUID.randomUUID().toString()
|
|
||||||
uniqueIDPreference.set(uniqueID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return uniqueID
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSyncEnabled(): Boolean {
|
|
||||||
return syncService().get() != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSyncSettings(): SyncSettings {
|
|
||||||
return SyncSettings(
|
|
||||||
libraryEntries = preferenceStore.getBoolean("library_entries", true).get(),
|
|
||||||
categories = preferenceStore.getBoolean("categories", true).get(),
|
|
||||||
chapters = preferenceStore.getBoolean("chapters", true).get(),
|
|
||||||
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(),
|
|
||||||
savedSearches = preferenceStore.getBoolean("savedSearches", true).get(),
|
|
||||||
// SY <--
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSyncSettings(syncSettings: SyncSettings) {
|
|
||||||
preferenceStore.getBoolean("library_entries", true).set(syncSettings.libraryEntries)
|
|
||||||
preferenceStore.getBoolean("categories", true).set(syncSettings.categories)
|
|
||||||
preferenceStore.getBoolean("chapters", true).set(syncSettings.chapters)
|
|
||||||
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 <--
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSyncTriggerOptions(): SyncTriggerOptions {
|
|
||||||
return SyncTriggerOptions(
|
|
||||||
syncOnChapterRead = preferenceStore.getBoolean("sync_on_chapter_read", false).get(),
|
|
||||||
syncOnChapterOpen = preferenceStore.getBoolean("sync_on_chapter_open", false).get(),
|
|
||||||
syncOnAppStart = preferenceStore.getBoolean("sync_on_app_start", false).get(),
|
|
||||||
syncOnAppResume = preferenceStore.getBoolean("sync_on_app_resume", false).get(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSyncTriggerOptions(syncTriggerOptions: SyncTriggerOptions) {
|
|
||||||
preferenceStore.getBoolean("sync_on_chapter_read", false)
|
|
||||||
.set(syncTriggerOptions.syncOnChapterRead)
|
|
||||||
preferenceStore.getBoolean("sync_on_chapter_open", false)
|
|
||||||
.set(syncTriggerOptions.syncOnChapterOpen)
|
|
||||||
preferenceStore.getBoolean("sync_on_app_start", false)
|
|
||||||
.set(syncTriggerOptions.syncOnAppStart)
|
|
||||||
preferenceStore.getBoolean("sync_on_app_resume", false)
|
|
||||||
.set(syncTriggerOptions.syncOnAppResume)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package eu.kanade.domain.sync.models
|
|
||||||
|
|
||||||
data class SyncSettings(
|
|
||||||
val libraryEntries: Boolean = true,
|
|
||||||
val categories: Boolean = true,
|
|
||||||
val chapters: Boolean = true,
|
|
||||||
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 savedSearches: Boolean = true,
|
|
||||||
// SY <--
|
|
||||||
)
|
|
@ -5,7 +5,6 @@ import eu.kanade.domain.track.model.toDomainTrack
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
@ -15,16 +14,17 @@ import tachiyomi.core.common.util.system.logcat
|
|||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.history.interactor.GetHistory
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
class AddTracks(
|
class AddTracks(
|
||||||
|
private val getTracks: GetTracks,
|
||||||
private val insertTrack: InsertTrack,
|
private val insertTrack: InsertTrack,
|
||||||
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
private val trackerManager: TrackerManager,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO: update all trackers based on common data
|
// TODO: update all trackers based on common data
|
||||||
@ -79,7 +79,7 @@ class AddTracks(
|
|||||||
|
|
||||||
suspend fun bindEnhancedTrackers(manga: Manga, source: Source) = withNonCancellableContext {
|
suspend fun bindEnhancedTrackers(manga: Manga, source: Source) = withNonCancellableContext {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
trackerManager.loggedInTrackers()
|
getTracks.await(manga.id)
|
||||||
.filterIsInstance<EnhancedTracker>()
|
.filterIsInstance<EnhancedTracker>()
|
||||||
.filter { it.accept(source) }
|
.filter { it.accept(source) }
|
||||||
.forEach { service ->
|
.forEach { service ->
|
||||||
@ -87,11 +87,11 @@ class AddTracks(
|
|||||||
service.match(manga)?.let { track ->
|
service.match(manga)?.let { track ->
|
||||||
track.manga_id = manga.id
|
track.manga_id = manga.id
|
||||||
(service as Tracker).bind(track)
|
(service as Tracker).bind(track)
|
||||||
insertTrack.await(track.toDomainTrack(idRequired = false)!!)
|
insertTrack.await(track.toDomainTrack()!!)
|
||||||
|
|
||||||
syncChapterProgressWithTrack.await(
|
syncChapterProgressWithTrack.await(
|
||||||
manga.id,
|
manga.id,
|
||||||
track.toDomainTrack(idRequired = false)!!,
|
track.toDomainTrack()!!,
|
||||||
service,
|
service,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import tachiyomi.domain.chapter.interactor.UpdateChapter
|
|||||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import tachiyomi.domain.track.model.Track
|
import tachiyomi.domain.track.model.Track
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class SyncChapterProgressWithTrack(
|
class SyncChapterProgressWithTrack(
|
||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
@ -37,8 +36,7 @@ class SyncChapterProgressWithTrack(
|
|||||||
|
|
||||||
// only take into account continuous reading
|
// only take into account continuous reading
|
||||||
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
|
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
|
||||||
val lastRead = max(remoteTrack.lastChapterRead, localLastRead.toDouble())
|
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
||||||
val updatedTrack = remoteTrack.copy(lastChapterRead = lastRead)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tracker.update(updatedTrack.toDbTrack())
|
tracker.update(updatedTrack.toDbTrack())
|
||||||
|
@ -23,7 +23,7 @@ class TrackChapter(
|
|||||||
private val delayedTrackingStore: DelayedTrackingStore,
|
private val delayedTrackingStore: DelayedTrackingStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double, setupJobOnFailure: Boolean = true) {
|
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) {
|
||||||
withNonCancellableContext {
|
withNonCancellableContext {
|
||||||
val tracks = getTracks.await(mangaId)
|
val tracks = getTracks.await(mangaId)
|
||||||
if (tracks.isEmpty()) return@withNonCancellableContext
|
if (tracks.isEmpty()) return@withNonCancellableContext
|
||||||
@ -34,7 +34,7 @@ class TrackChapter(
|
|||||||
service == null ||
|
service == null ||
|
||||||
!service.isLoggedIn ||
|
!service.isLoggedIn ||
|
||||||
chapterNumber <= track.lastChapterRead /* SY --> */ ||
|
chapterNumber <= track.lastChapterRead /* SY --> */ ||
|
||||||
(service is MdList && track.status == FollowStatus.UNFOLLOWED.long)/* SY <-- */
|
(service is MdList && track.status == FollowStatus.UNFOLLOWED.int.toLong())/* SY <-- */
|
||||||
) {
|
) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
@ -50,9 +50,7 @@ class TrackChapter(
|
|||||||
delayedTrackingStore.remove(track.id)
|
delayedTrackingStore.remove(track.id)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
delayedTrackingStore.add(track.id, chapterNumber)
|
delayedTrackingStore.add(track.id, chapterNumber)
|
||||||
if (setupJobOnFailure) {
|
DelayedTrackingUpdateJob.setupTask(context)
|
||||||
DelayedTrackingUpdateJob.setupTask(context)
|
|
||||||
}
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
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),
|
|
||||||
}
|
|
@ -10,7 +10,6 @@ fun Track.copyPersonalFrom(other: Track): Track {
|
|||||||
status = other.status,
|
status = other.status,
|
||||||
startDate = other.startDate,
|
startDate = other.startDate,
|
||||||
finishDate = other.finishDate,
|
finishDate = other.finishDate,
|
||||||
private = other.private,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also {
|
|||||||
it.tracking_url = remoteUrl
|
it.tracking_url = remoteUrl
|
||||||
it.started_reading_date = startDate
|
it.started_reading_date = startDate
|
||||||
it.finished_reading_date = finishDate
|
it.finished_reading_date = finishDate
|
||||||
it.private = private
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
||||||
@ -46,6 +44,5 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
|||||||
remoteUrl = tracking_url,
|
remoteUrl = tracking_url,
|
||||||
startDate = started_reading_date,
|
startDate = started_reading_date,
|
||||||
finishDate = finished_reading_date,
|
finishDate = finished_reading_date,
|
||||||
private = private,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
|
|||||||
logcat(LogPriority.DEBUG) {
|
logcat(LogPriority.DEBUG) {
|
||||||
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
||||||
}
|
}
|
||||||
trackChapter.await(context, track.mangaId, track.lastChapterRead, setupJobOnFailure = false)
|
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package eu.kanade.domain.track.service
|
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.Tracker
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
import tachiyomi.core.common.preference.getEnum
|
|
||||||
|
|
||||||
class TrackPreferences(
|
class TrackPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
@ -37,16 +35,4 @@ class TrackPreferences(
|
|||||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||||
|
|
||||||
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||||
|
|
||||||
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum(
|
|
||||||
"pref_auto_update_manga_on_mark_read",
|
|
||||||
AutoTrackState.ALWAYS,
|
|
||||||
)
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
fun resolveUsingSourceMetadata() = preferenceStore.getBoolean(
|
|
||||||
"pref_resolve_using_source_metadata_key",
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|||||||
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
import tachiyomi.core.common.preference.getEnum
|
import tachiyomi.core.common.preference.getEnum
|
||||||
import java.time.format.DateTimeFormatter
|
import java.text.DateFormat
|
||||||
import java.time.format.FormatStyle
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class UiPreferences(
|
class UiPreferences(
|
||||||
@ -18,20 +18,12 @@ class UiPreferences(
|
|||||||
|
|
||||||
fun themeMode() = preferenceStore.getEnum(
|
fun themeMode() = preferenceStore.getEnum(
|
||||||
"pref_theme_mode_key",
|
"pref_theme_mode_key",
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ThemeMode.SYSTEM } else { ThemeMode.LIGHT },
|
||||||
ThemeMode.SYSTEM
|
|
||||||
} else {
|
|
||||||
ThemeMode.LIGHT
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun appTheme() = preferenceStore.getEnum(
|
fun appTheme() = preferenceStore.getEnum(
|
||||||
"pref_app_theme",
|
"pref_app_theme",
|
||||||
if (DeviceUtil.isDynamicColorAvailable) {
|
if (DeviceUtil.isDynamicColorAvailable) { AppTheme.MONET } else { AppTheme.DEFAULT },
|
||||||
AppTheme.MONET
|
|
||||||
} else {
|
|
||||||
AppTheme.DEFAULT
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||||
@ -54,8 +46,6 @@ class UiPreferences(
|
|||||||
|
|
||||||
fun mergeInOverflow() = preferenceStore.getBoolean("merge_in_overflow", true)
|
fun mergeInOverflow() = preferenceStore.getBoolean("merge_in_overflow", true)
|
||||||
|
|
||||||
fun previewsRowCount() = preferenceStore.getInt("pref_previews_row_count", 4)
|
|
||||||
|
|
||||||
fun useNewSourceNavigation() = preferenceStore.getBoolean("use_new_source_navigation", true)
|
fun useNewSourceNavigation() = preferenceStore.getBoolean("use_new_source_navigation", true)
|
||||||
|
|
||||||
fun bottomBarLabels() = preferenceStore.getBoolean("pref_show_bottom_bar_labels", true)
|
fun bottomBarLabels() = preferenceStore.getBoolean("pref_show_bottom_bar_labels", true)
|
||||||
@ -67,9 +57,9 @@ class UiPreferences(
|
|||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun dateFormat(format: String): DateTimeFormatter = when (format) {
|
fun dateFormat(format: String): DateFormat = when (format) {
|
||||||
"" -> DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
|
||||||
else -> DateTimeFormatter.ofPattern(format, Locale.getDefault())
|
else -> SimpleDateFormat(format, Locale.getDefault())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.domain.ui.model
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
enum class AppTheme(val titleRes: StringResource?) {
|
enum class AppTheme(val titleRes: StringResource?) {
|
||||||
@ -9,14 +11,15 @@ enum class AppTheme(val titleRes: StringResource?) {
|
|||||||
GREEN_APPLE(MR.strings.theme_greenapple),
|
GREEN_APPLE(MR.strings.theme_greenapple),
|
||||||
LAVENDER(MR.strings.theme_lavender),
|
LAVENDER(MR.strings.theme_lavender),
|
||||||
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
||||||
NORD(MR.strings.theme_nord),
|
|
||||||
|
// TODO: re-enable for preview
|
||||||
|
NORD(MR.strings.theme_nord.takeIf { isDevFlavor || isPreviewBuildType }),
|
||||||
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
||||||
TAKO(MR.strings.theme_tako),
|
TAKO(MR.strings.theme_tako),
|
||||||
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
||||||
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
||||||
YINYANG(MR.strings.theme_yinyang),
|
YINYANG(MR.strings.theme_yinyang),
|
||||||
YOTSUBA(MR.strings.theme_yotsuba),
|
YOTSUBA(MR.strings.theme_yotsuba),
|
||||||
MONOCHROME(MR.strings.theme_monochrome),
|
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
DARK_BLUE(null),
|
DARK_BLUE(null),
|
||||||
|
@ -82,18 +82,10 @@ fun BrowseSourceContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
LoadingScreen(Modifier.padding(contentPadding))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mangaList.itemCount == 0) {
|
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
message = when (errorState) {
|
message = getErrorMessage(errorState),
|
||||||
is LoadState.Error -> getErrorMessage(errorState)
|
|
||||||
else -> stringResource(MR.strings.no_results_found)
|
|
||||||
},
|
|
||||||
actions = if (source is LocalSource /* SY --> */ && onLocalSourceHelpClick != null /* SY <-- */) {
|
actions = if (source is LocalSource /* SY --> */ && onLocalSourceHelpClick != null /* SY <-- */) {
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
@ -112,7 +104,7 @@ fun BrowseSourceContent(
|
|||||||
// SY -->
|
// SY -->
|
||||||
if (onWebViewClick != null) {
|
if (onWebViewClick != null) {
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringRes = MR.strings.action_open_in_web_view,
|
MR.strings.action_open_in_web_view,
|
||||||
icon = Icons.Outlined.Public,
|
icon = Icons.Outlined.Public,
|
||||||
onClick = onWebViewClick,
|
onClick = onWebViewClick,
|
||||||
)
|
)
|
||||||
@ -121,7 +113,7 @@ fun BrowseSourceContent(
|
|||||||
},
|
},
|
||||||
if (onHelpClick != null) {
|
if (onHelpClick != null) {
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringRes = MR.strings.label_help,
|
MR.strings.label_help,
|
||||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
)
|
)
|
||||||
@ -136,6 +128,13 @@ fun BrowseSourceContent(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
||||||
|
LoadingScreen(
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
if (source?.isEhBasedSource() == true && ehentaiBrowseDisplayMode) {
|
if (source?.isEhBasedSource() == true && ehentaiBrowseDisplayMode) {
|
||||||
BrowseSourceEHentaiList(
|
BrowseSourceEHentaiList(
|
||||||
|
@ -11,7 +11,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseTabWrapper(tab: TabContent, onBackPressed: (() -> Unit)? = null) {
|
fun BrowseTabWrapper(tab: TabContent) {
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
@ -20,7 +20,6 @@ fun BrowseTabWrapper(tab: TabContent, onBackPressed: (() -> Unit)? = null) {
|
|||||||
actions = {
|
actions = {
|
||||||
AppBarActions(tab.actions)
|
AppBarActions(tab.actions)
|
||||||
},
|
},
|
||||||
navigateUp = onBackPressed,
|
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -5,6 +5,7 @@ import android.net.Uri
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@ -35,10 +36,8 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.vectorResource
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@ -50,7 +49,6 @@ import eu.kanade.presentation.components.AppBarActions
|
|||||||
import eu.kanade.presentation.components.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
||||||
@ -76,7 +74,6 @@ fun ExtensionDetailsScreen(
|
|||||||
onClickClearCookies: () -> Unit,
|
onClickClearCookies: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
onClickIncognito: (Boolean) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val url = remember(state.extension) {
|
val url = remember(state.extension) {
|
||||||
@ -145,11 +142,9 @@ fun ExtensionDetailsScreen(
|
|||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
extension = state.extension,
|
extension = state.extension,
|
||||||
sources = state.sources,
|
sources = state.sources,
|
||||||
incognitoMode = state.isIncognito,
|
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
onClickUninstall = onClickUninstall,
|
onClickUninstall = onClickUninstall,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
onClickIncognito = onClickIncognito,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,11 +154,9 @@ private fun ExtensionDetails(
|
|||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
extension: Extension.Installed,
|
extension: Extension.Installed,
|
||||||
sources: ImmutableList<ExtensionSourceItem>,
|
sources: ImmutableList<ExtensionSourceItem>,
|
||||||
incognitoMode: Boolean,
|
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
onClickIncognito: (Boolean) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var showNsfwWarning by remember { mutableStateOf(false) }
|
var showNsfwWarning by remember { mutableStateOf(false) }
|
||||||
@ -187,7 +180,6 @@ private fun ExtensionDetails(
|
|||||||
item {
|
item {
|
||||||
DetailsHeader(
|
DetailsHeader(
|
||||||
extension = extension,
|
extension = extension,
|
||||||
extIncognitoMode = incognitoMode,
|
|
||||||
onClickUninstall = onClickUninstall,
|
onClickUninstall = onClickUninstall,
|
||||||
onClickAppInfo = {
|
onClickAppInfo = {
|
||||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
@ -199,7 +191,6 @@ private fun ExtensionDetails(
|
|||||||
onClickAgeRating = {
|
onClickAgeRating = {
|
||||||
showNsfwWarning = true
|
showNsfwWarning = true
|
||||||
},
|
},
|
||||||
onExtIncognitoChange = onClickIncognito,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +199,7 @@ private fun ExtensionDetails(
|
|||||||
key = { it.source.id },
|
key = { it.source.id },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourceSwitchPreference(
|
SourceSwitchPreference(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = source,
|
source = source,
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
@ -227,11 +218,9 @@ private fun ExtensionDetails(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DetailsHeader(
|
private fun DetailsHeader(
|
||||||
extension: Extension,
|
extension: Extension,
|
||||||
extIncognitoMode: Boolean,
|
|
||||||
onClickAgeRating: () -> Unit,
|
onClickAgeRating: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickAppInfo: (() -> Unit)?,
|
onClickAppInfo: (() -> Unit)?,
|
||||||
onExtIncognitoChange: (Boolean) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -239,8 +228,9 @@ private fun DetailsHeader(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = MaterialTheme.padding.medium)
|
|
||||||
.padding(
|
.padding(
|
||||||
|
start = MaterialTheme.padding.medium,
|
||||||
|
end = MaterialTheme.padding.medium,
|
||||||
top = MaterialTheme.padding.medium,
|
top = MaterialTheme.padding.medium,
|
||||||
bottom = MaterialTheme.padding.small,
|
bottom = MaterialTheme.padding.small,
|
||||||
)
|
)
|
||||||
@ -251,7 +241,7 @@ private fun DetailsHeader(
|
|||||||
Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName})
|
Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName})
|
||||||
Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode})
|
Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode})
|
||||||
NSFW: ${extension.isNsfw}
|
NSFW: ${extension.isNsfw}
|
||||||
""".trimIndent(),
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
|
|
||||||
if (extension is Extension.Installed) {
|
if (extension is Extension.Installed) {
|
||||||
@ -261,8 +251,8 @@ private fun DetailsHeader(
|
|||||||
Update available: ${extension.hasUpdate}
|
Update available: ${extension.hasUpdate}
|
||||||
Obsolete: ${extension.isObsolete}
|
Obsolete: ${extension.isObsolete}
|
||||||
Shared: ${extension.isShared}
|
Shared: ${extension.isShared}
|
||||||
Repository: ${extension.repoUrl}
|
Repository: ${extension.repoUrl}
|
||||||
""".trimIndent(),
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,9 +322,12 @@ private fun DetailsHeader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(
|
||||||
.padding(horizontal = MaterialTheme.padding.medium)
|
start = MaterialTheme.padding.medium,
|
||||||
.padding(top = MaterialTheme.padding.small),
|
end = MaterialTheme.padding.medium,
|
||||||
|
top = MaterialTheme.padding.small,
|
||||||
|
bottom = MaterialTheme.padding.medium,
|
||||||
|
),
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
@ -357,24 +350,6 @@ private fun DetailsHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextPreferenceWidget(
|
|
||||||
modifier = Modifier.padding(horizontal = MaterialTheme.padding.small),
|
|
||||||
title = stringResource(MR.strings.pref_incognito_mode),
|
|
||||||
subtitle = stringResource(MR.strings.pref_incognito_mode_extension_summary),
|
|
||||||
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
|
|
||||||
widget = {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Switch(
|
|
||||||
checked = extIncognitoMode,
|
|
||||||
onCheckedChange = onExtIncognitoChange,
|
|
||||||
modifier = Modifier.padding(start = TrailingWidgetBuffer),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,8 +362,10 @@ private fun InfoText(
|
|||||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
|
||||||
val clickableModifier = if (onClick != null) {
|
val clickableModifier = if (onClick != null) {
|
||||||
Modifier.clickable(interactionSource = null, indication = null, onClick = onClick)
|
Modifier.clickable(interactionSource, indication = null) { onClick() }
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ private fun ExtensionFilterContent(
|
|||||||
) {
|
) {
|
||||||
items(state.languages) { language ->
|
items(state.languages) { language ->
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
checked = language in state.enabledLanguages,
|
checked = language in state.enabledLanguages,
|
||||||
onCheckedChanged = { onClickLang(language) },
|
onCheckedChanged = { onClickLang(language) },
|
||||||
|
@ -48,7 +48,6 @@ import eu.kanade.presentation.browse.components.ExtensionIcon
|
|||||||
import eu.kanade.presentation.components.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
|
||||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
@ -92,7 +91,7 @@ fun ExtensionScreen(
|
|||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.isRefreshing,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !state.isLoading,
|
enabled = { !state.isLoading },
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
@ -189,14 +188,14 @@ private fun ExtensionContent(
|
|||||||
}
|
}
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
textRes = header.textRes,
|
textRes = header.textRes,
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
action = action,
|
action = action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExtensionUiModel.Header.Text -> {
|
is ExtensionUiModel.Header.Text -> {
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
text = header.text,
|
text = header.text,
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,15 +213,13 @@ private fun ExtensionContent(
|
|||||||
},
|
},
|
||||||
) { item ->
|
) { item ->
|
||||||
ExtensionItem(
|
ExtensionItem(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
item = item,
|
item = item,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Extension.Available -> onInstallExtension(it)
|
is Extension.Available -> onInstallExtension(it)
|
||||||
is Extension.Installed -> onOpenExtension(it)
|
is Extension.Installed -> onOpenExtension(it)
|
||||||
is Extension.Untrusted -> {
|
is Extension.Untrusted -> { trustState = it }
|
||||||
trustState = it
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
@ -244,9 +241,7 @@ private fun ExtensionContent(
|
|||||||
onOpenExtension(it)
|
onOpenExtension(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Extension.Untrusted -> {
|
is Extension.Untrusted -> { trustState = it }
|
||||||
trustState = it
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -70,7 +70,7 @@ fun FeedScreen(
|
|||||||
onClickDelete: (FeedSavedSearch) -> Unit,
|
onClickDelete: (FeedSavedSearch) -> Unit,
|
||||||
onClickManga: (Manga) -> Unit,
|
onClickManga: (Manga) -> Unit,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
getMangaState: @Composable (Manga) -> State<Manga>,
|
getMangaState: @Composable (Manga, CatalogueSource?) -> State<Manga>,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen()
|
state.isLoading -> LoadingScreen()
|
||||||
@ -92,7 +92,7 @@ fun FeedScreen(
|
|||||||
refreshing = true
|
refreshing = true
|
||||||
onRefresh()
|
onRefresh()
|
||||||
},
|
},
|
||||||
enabled = !state.isLoadingItems,
|
enabled = { !state.isLoadingItems },
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
contentPadding = contentPadding + topSmallPaddingValues,
|
contentPadding = contentPadding + topSmallPaddingValues,
|
||||||
@ -103,6 +103,7 @@ fun FeedScreen(
|
|||||||
key = { it.feed.id },
|
key = { it.feed.id },
|
||||||
) { item ->
|
) { item ->
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
title = item.title,
|
title = item.title,
|
||||||
subtitle = item.subtitle,
|
subtitle = item.subtitle,
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
@ -115,11 +116,10 @@ fun FeedScreen(
|
|||||||
onClickSource(item.source)
|
onClickSource(item.source)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.animateItem(),
|
|
||||||
) {
|
) {
|
||||||
FeedItem(
|
FeedItem(
|
||||||
item = item,
|
item = item,
|
||||||
getMangaState = { getMangaState(it) },
|
getMangaState = { getMangaState(it, item.source) },
|
||||||
onClickManga = onClickManga,
|
onClickManga = onClickManga,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||||
@ -81,7 +80,6 @@ internal fun GlobalSearchContent(
|
|||||||
} ?: source.name,
|
} ?: source.name,
|
||||||
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
modifier = Modifier.animateItem(),
|
|
||||||
) {
|
) {
|
||||||
when (result) {
|
when (result) {
|
||||||
SearchItemResult.Loading -> {
|
SearchItemResult.Loading -> {
|
||||||
|
@ -144,7 +144,7 @@ private fun MigrateSourceList(
|
|||||||
key = { (source, _) -> "migrate-${source.id}" },
|
key = { (source, _) -> "migrate-${source.id}" },
|
||||||
) { (source, count) ->
|
) { (source, count) ->
|
||||||
MigrateSourceItem(
|
MigrateSourceItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = source,
|
source = source,
|
||||||
count = count,
|
count = count,
|
||||||
onClickItem = { onClickItem(source) },
|
onClickItem = { onClickItem(source) },
|
||||||
|
@ -28,7 +28,6 @@ import eu.kanade.presentation.browse.components.MigrationItem
|
|||||||
import eu.kanade.presentation.browse.components.MigrationItemResult
|
import eu.kanade.presentation.browse.components.MigrationItemResult
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@ -96,7 +95,7 @@ fun MigrationListScreen(
|
|||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.animateItemFastScroll()
|
.animateItemPlacement()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.height(IntrinsicSize.Min),
|
.height(IntrinsicSize.Min),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
@ -15,7 +15,6 @@ import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
|||||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||||
import eu.kanade.presentation.components.AppBarTitle
|
import eu.kanade.presentation.components.AppBarTitle
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.source.model.FeedSavedSearch
|
import tachiyomi.domain.source.model.FeedSavedSearch
|
||||||
@ -154,7 +153,7 @@ fun SourceFeedList(
|
|||||||
key = { it.id },
|
key = { it.id },
|
||||||
) { item ->
|
) { item ->
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
title = item.title,
|
title = item.title,
|
||||||
subtitle = null,
|
subtitle = null,
|
||||||
onLongClick = if (item is SourceFeedUI.SourceSavedSearch) {
|
onLongClick = if (item is SourceFeedUI.SourceSavedSearch) {
|
||||||
|
@ -11,7 +11,6 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
@ -80,7 +79,7 @@ private fun SourcesFilterContent(
|
|||||||
contentType = "source-filter-header",
|
contentType = "source-filter-header",
|
||||||
) {
|
) {
|
||||||
SourcesFilterHeader(
|
SourcesFilterHeader(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
language = language,
|
language = language,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onClickItem = onClickLanguage,
|
onClickItem = onClickLanguage,
|
||||||
@ -96,7 +95,7 @@ private fun SourcesFilterContent(
|
|||||||
sources.none { it.id.toString() in state.disabledSources }
|
sources.none { it.id.toString() in state.disabledSources }
|
||||||
}
|
}
|
||||||
SourcesFilterToggle(
|
SourcesFilterToggle(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
isEnabled = toggleEnabled,
|
isEnabled = toggleEnabled,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
onClickSources(!toggleEnabled, sources)
|
onClickSources(!toggleEnabled, sources)
|
||||||
@ -110,7 +109,7 @@ private fun SourcesFilterContent(
|
|||||||
contentType = { "source-filter-item" },
|
contentType = { "source-filter-item" },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourcesFilterItem(
|
SourcesFilterItem(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = source,
|
source = source,
|
||||||
enabled = "${source.id}" !in state.disabledSources,
|
enabled = "${source.id}" !in state.disabledSources,
|
||||||
onClickItem = onClickSource,
|
onClickItem = onClickSource,
|
||||||
|
@ -35,7 +35,7 @@ import tachiyomi.i18n.MR
|
|||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
@ -81,7 +81,7 @@ fun SourcesScreen(
|
|||||||
when (model) {
|
when (model) {
|
||||||
is SourceUiModel.Header -> {
|
is SourceUiModel.Header -> {
|
||||||
SourceHeader(
|
SourceHeader(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
language = model.language,
|
language = model.language,
|
||||||
// SY -->
|
// SY -->
|
||||||
isCategory = model.isCategory,
|
isCategory = model.isCategory,
|
||||||
@ -89,7 +89,7 @@ fun SourcesScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SourceUiModel.Item -> SourceItem(
|
is SourceUiModel.Item -> SourceItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = model.source,
|
source = model.source,
|
||||||
// SY -->
|
// SY -->
|
||||||
showLatest = state.showLatest,
|
showLatest = state.showLatest,
|
||||||
@ -179,7 +179,7 @@ private fun SourcePinButton(
|
|||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.onBackground.copy(
|
MaterialTheme.colorScheme.onBackground.copy(
|
||||||
alpha = SECONDARY_ALPHA,
|
alpha = SecondaryItemAlpha,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
|
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
|
||||||
|
@ -25,7 +25,7 @@ import androidx.compose.ui.res.imageResource
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import coil3.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import eu.kanade.domain.source.model.icon
|
import eu.kanade.domain.source.model.icon
|
||||||
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -127,7 +127,7 @@ private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): St
|
|||||||
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
value = try {
|
value = try {
|
||||||
val appInfo = ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo!!
|
val appInfo = ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
|
||||||
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
||||||
Result.Success(
|
Result.Success(
|
||||||
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
||||||
|
@ -19,7 +19,6 @@ import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
import exh.metadata.metadata.RankedSearchMetadata
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@ -120,14 +119,6 @@ private fun BrowseSourceComfortableGridItem(
|
|||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (metadata is RankedSearchMetadata) {
|
|
||||||
metadata.rank?.let {
|
|
||||||
Badge(
|
|
||||||
text = "+$it",
|
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
|
||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -19,7 +19,6 @@ import eu.kanade.presentation.library.components.MangaCompactGridItem
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
import exh.metadata.metadata.RankedSearchMetadata
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@ -120,14 +119,6 @@ private fun BrowseSourceCompactGridItem(
|
|||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (metadata is RankedSearchMetadata) {
|
|
||||||
metadata.rank?.let {
|
|
||||||
Badge(
|
|
||||||
text = "+$it",
|
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
|
||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -50,8 +50,7 @@ import tachiyomi.presentation.core.components.Badge
|
|||||||
import tachiyomi.presentation.core.components.BadgeGroup
|
import tachiyomi.presentation.core.components.BadgeGroup
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import java.time.Instant
|
import java.util.Date
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceEHentaiList(
|
fun BrowseSourceEHentaiList(
|
||||||
@ -129,11 +128,9 @@ fun BrowseSourceEHentaiListItem(
|
|||||||
}
|
}
|
||||||
val datePosted by produceState("", metadata) {
|
val datePosted by produceState("", metadata) {
|
||||||
value = withIOContext {
|
value = withIOContext {
|
||||||
runCatching {
|
runCatching { metadata.datePosted?.let { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) } }
|
||||||
metadata.datePosted?.let {
|
.getOrNull()
|
||||||
MetadataUtil.EX_DATE_FORMAT.format(Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault()))
|
.orEmpty()
|
||||||
}
|
|
||||||
}.getOrNull().orEmpty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val genre by produceState<Pair<GenreColor, StringResource>?>(null, metadata) {
|
val genre by produceState<Pair<GenreColor, StringResource>?>(null, metadata) {
|
||||||
|
@ -16,7 +16,6 @@ import eu.kanade.presentation.library.components.MangaListItem
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
import exh.metadata.metadata.RankedSearchMetadata
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
@ -111,14 +110,6 @@ private fun BrowseSourceListItem(
|
|||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (metadata is RankedSearchMetadata) {
|
|
||||||
metadata.rank?.let {
|
|
||||||
Badge(
|
|
||||||
text = "+$it",
|
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
|
||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
},
|
},
|
||||||
|
@ -30,6 +30,9 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchResultItem(
|
fun GlobalSearchResultItem(
|
||||||
|
// SY -->
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
// SY <--
|
||||||
title: String,
|
title: String,
|
||||||
// SY -->
|
// SY -->
|
||||||
subtitle: String?,
|
subtitle: String?,
|
||||||
@ -38,10 +41,9 @@ fun GlobalSearchResultItem(
|
|||||||
// SY -->
|
// SY -->
|
||||||
onLongClick: (() -> Unit)? = null,
|
onLongClick: (() -> Unit)? = null,
|
||||||
// SY <--
|
// SY <--
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
|
@ -2,25 +2,23 @@ package eu.kanade.presentation.category
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.SortByAlpha
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.toMutableStateList
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
||||||
import eu.kanade.presentation.category.components.CategoryListItem
|
import eu.kanade.presentation.category.components.CategoryListItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreenState
|
import eu.kanade.tachiyomi.ui.category.CategoryScreenState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import sh.calvin.reorderable.ReorderableItem
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@ -34,9 +32,11 @@ import tachiyomi.presentation.core.util.plus
|
|||||||
fun CategoryScreen(
|
fun CategoryScreen(
|
||||||
state: CategoryScreenState.Success,
|
state: CategoryScreenState.Success,
|
||||||
onClickCreate: () -> Unit,
|
onClickCreate: () -> Unit,
|
||||||
|
onClickSortAlphabetically: () -> Unit,
|
||||||
onClickRename: (Category) -> Unit,
|
onClickRename: (Category) -> Unit,
|
||||||
onClickDelete: (Category) -> Unit,
|
onClickDelete: (Category) -> Unit,
|
||||||
onChangeOrder: (Category, Int) -> Unit,
|
onClickMoveUp: (Category) -> Unit,
|
||||||
|
onClickMoveDown: (Category) -> Unit,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
@ -45,6 +45,17 @@ fun CategoryScreen(
|
|||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(MR.strings.action_edit_categories),
|
title = stringResource(MR.strings.action_edit_categories),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
persistentListOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_sort),
|
||||||
|
icon = Icons.Outlined.SortByAlpha,
|
||||||
|
onClick = onClickSortAlphabetically,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -66,10 +77,12 @@ fun CategoryScreen(
|
|||||||
CategoryContent(
|
CategoryContent(
|
||||||
categories = state.categories,
|
categories = state.categories,
|
||||||
lazyListState = lazyListState,
|
lazyListState = lazyListState,
|
||||||
paddingValues = paddingValues,
|
paddingValues = paddingValues + topSmallPaddingValues +
|
||||||
|
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||||
onClickRename = onClickRename,
|
onClickRename = onClickRename,
|
||||||
onClickDelete = onClickDelete,
|
onClickDelete = onClickDelete,
|
||||||
onChangeOrder = onChangeOrder,
|
onMoveUp = onClickMoveUp,
|
||||||
|
onMoveDown = onClickMoveDown,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,44 +94,28 @@ private fun CategoryContent(
|
|||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
onClickRename: (Category) -> Unit,
|
onClickRename: (Category) -> Unit,
|
||||||
onClickDelete: (Category) -> Unit,
|
onClickDelete: (Category) -> Unit,
|
||||||
onChangeOrder: (Category, Int) -> Unit,
|
onMoveUp: (Category) -> Unit,
|
||||||
|
onMoveDown: (Category) -> Unit,
|
||||||
) {
|
) {
|
||||||
val categoriesState = remember { categories.toMutableStateList() }
|
|
||||||
val reorderableState = rememberReorderableLazyListState(lazyListState, paddingValues) { from, to ->
|
|
||||||
val item = categoriesState.removeAt(from.index)
|
|
||||||
categoriesState.add(to.index, item)
|
|
||||||
onChangeOrder(item, to.index)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(categories) {
|
|
||||||
if (!reorderableState.isAnyItemDragging) {
|
|
||||||
categoriesState.clear()
|
|
||||||
categoriesState.addAll(categories)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
contentPadding = paddingValues +
|
contentPadding = paddingValues,
|
||||||
topSmallPaddingValues +
|
|
||||||
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
items(
|
itemsIndexed(
|
||||||
items = categoriesState,
|
items = categories,
|
||||||
key = { category -> category.key },
|
key = { _, category -> "category-${category.id}" },
|
||||||
) { category ->
|
) { index, category ->
|
||||||
ReorderableItem(reorderableState, category.key) {
|
CategoryListItem(
|
||||||
CategoryListItem(
|
modifier = Modifier.animateItemPlacement(),
|
||||||
modifier = Modifier.animateItem(),
|
category = category,
|
||||||
category = category,
|
canMoveUp = index != 0,
|
||||||
onRename = { onClickRename(category) },
|
canMoveDown = index != categories.lastIndex,
|
||||||
onDelete = { onClickDelete(category) },
|
onMoveUp = onMoveUp,
|
||||||
)
|
onMoveDown = onMoveDown,
|
||||||
}
|
onRename = { onClickRename(category) },
|
||||||
|
onDelete = { onClickDelete(category) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val Category.key inline get() = "category-$id"
|
|
||||||
|
@ -219,6 +219,35 @@ fun CategoryDeleteDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CategorySortAlphabeticallyDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onSort: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
onSort()
|
||||||
|
onDismissRequest()
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(MR.strings.action_sort_category))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(MR.strings.sort_category_confirmation))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChangeCategoryDialog(
|
fun ChangeCategoryDialog(
|
||||||
initialSelection: ImmutableList<CheckboxState<Category>>,
|
initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||||
|
@ -10,7 +10,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
|
import tachiyomi.presentation.core.util.isScrollingUp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryFloatingActionButton(
|
fun CategoryFloatingActionButton(
|
||||||
@ -22,7 +23,7 @@ fun CategoryFloatingActionButton(
|
|||||||
text = { Text(text = stringResource(MR.strings.action_add)) },
|
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||||
onClick = onCreate,
|
onClick = onCreate,
|
||||||
expanded = lazyListState.shouldExpandFAB(),
|
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,14 @@ package eu.kanade.presentation.category.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowDropDown
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowDropUp
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.DragHandle
|
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -16,42 +19,57 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import sh.calvin.reorderable.ReorderableCollectionItemScope
|
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReorderableCollectionItemScope.CategoryListItem(
|
fun CategoryListItem(
|
||||||
category: Category,
|
category: Category,
|
||||||
|
canMoveUp: Boolean,
|
||||||
|
canMoveDown: Boolean,
|
||||||
|
onMoveUp: (Category) -> Unit,
|
||||||
|
onMoveDown: (Category) -> Unit,
|
||||||
onRename: () -> Unit,
|
onRename: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
ElevatedCard(modifier = modifier) {
|
ElevatedCard(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onRename)
|
.clickable { onRename() }
|
||||||
.padding(vertical = MaterialTheme.padding.small)
|
|
||||||
.padding(
|
.padding(
|
||||||
start = MaterialTheme.padding.small,
|
start = MaterialTheme.padding.medium,
|
||||||
|
top = MaterialTheme.padding.medium,
|
||||||
end = MaterialTheme.padding.medium,
|
end = MaterialTheme.padding.medium,
|
||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
||||||
imageVector = Icons.Outlined.DragHandle,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(MaterialTheme.padding.medium)
|
|
||||||
.draggableHandle(),
|
|
||||||
)
|
|
||||||
Text(
|
Text(
|
||||||
text = category.name,
|
text = category.name,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier
|
||||||
|
.padding(start = MaterialTheme.padding.medium),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
IconButton(
|
||||||
|
onClick = { onMoveUp(category) },
|
||||||
|
enabled = canMoveUp,
|
||||||
|
) {
|
||||||
|
Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = null)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = { onMoveDown(category) },
|
||||||
|
enabled = canMoveDown,
|
||||||
|
) {
|
||||||
|
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = null)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
IconButton(onClick = onRename) {
|
IconButton(onClick = onRename) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Edit,
|
imageVector = Icons.Outlined.Edit,
|
||||||
@ -59,10 +77,7 @@ fun ReorderableCollectionItemScope.CategoryListItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onDelete) {
|
IconButton(onClick = onDelete) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(MR.strings.action_delete))
|
||||||
imageVector = Icons.Outlined.Delete,
|
|
||||||
contentDescription = stringResource(MR.strings.action_delete),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ fun BiometricTimesContent(
|
|||||||
) {
|
) {
|
||||||
items(timeRanges, key = { it.formattedString }) { timeRange ->
|
items(timeRanges, key = { it.formattedString }) { timeRange ->
|
||||||
BiometricTimesListItem(
|
BiometricTimesListItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
timeRange = timeRange,
|
timeRange = timeRange,
|
||||||
onDelete = { onClickDelete(timeRange) },
|
onDelete = { onClickDelete(timeRange) },
|
||||||
)
|
)
|
||||||
|
@ -27,7 +27,7 @@ fun SortTagContent(
|
|||||||
) {
|
) {
|
||||||
itemsIndexed(tags, key = { _, tag -> tag }) { index, tag ->
|
itemsIndexed(tags, key = { _, tag -> tag }) { index, tag ->
|
||||||
SortTagListItem(
|
SortTagListItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
tag = tag,
|
tag = tag,
|
||||||
canMoveUp = index != 0,
|
canMoveUp = index != 0,
|
||||||
canMoveDown = index != tags.lastIndex,
|
canMoveDown = index != tags.lastIndex,
|
||||||
|
@ -26,7 +26,7 @@ fun SourceCategoryContent(
|
|||||||
) {
|
) {
|
||||||
items(categories, key = { it }) { category ->
|
items(categories, key = { it }) { category ->
|
||||||
SourceCategoryListItem(
|
SourceCategoryListItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
category = category,
|
category = category,
|
||||||
onRename = { onClickRename(category) },
|
onRename = { onClickRename(category) },
|
||||||
onDelete = { onClickDelete(category) },
|
onDelete = { onClickDelete(category) },
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.compose.animation.SizeTransform
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
|
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
|
||||||
@ -20,6 +23,7 @@ import tachiyomi.presentation.core.components.AdaptiveSheet as AdaptiveSheetImpl
|
|||||||
@Composable
|
@Composable
|
||||||
fun NavigatorAdaptiveSheet(
|
fun NavigatorAdaptiveSheet(
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
|
tonalElevation: Dp = 1.dp,
|
||||||
enableSwipeDismiss: (Navigator) -> Boolean = { true },
|
enableSwipeDismiss: (Navigator) -> Boolean = { true },
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -27,14 +31,21 @@ fun NavigatorAdaptiveSheet(
|
|||||||
screen = screen,
|
screen = screen,
|
||||||
content = { sheetNavigator ->
|
content = { sheetNavigator ->
|
||||||
AdaptiveSheet(
|
AdaptiveSheet(
|
||||||
onDismissRequest = onDismissRequest,
|
tonalElevation = tonalElevation,
|
||||||
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
) {
|
) {
|
||||||
ScreenTransition(
|
ScreenTransition(
|
||||||
navigator = sheetNavigator,
|
navigator = sheetNavigator,
|
||||||
enterTransition = { fadeIn(animationSpec = tween(220, delayMillis = 90)) },
|
transition = {
|
||||||
exitTransition = { fadeOut(animationSpec = tween(90)) },
|
fadeIn(animationSpec = tween(220, delayMillis = 90)) togetherWith
|
||||||
sizeTransform = { SizeTransform() },
|
fadeOut(animationSpec = tween(90))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
BackHandler(
|
||||||
|
enabled = sheetNavigator.size > 1,
|
||||||
|
onBack = sheetNavigator::pop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +73,7 @@ fun NavigatorAdaptiveSheet(
|
|||||||
fun AdaptiveSheet(
|
fun AdaptiveSheet(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
tonalElevation: Dp = 1.dp,
|
||||||
enableSwipeDismiss: Boolean = true,
|
enableSwipeDismiss: Boolean = true,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -72,10 +84,11 @@ fun AdaptiveSheet(
|
|||||||
properties = dialogProperties,
|
properties = dialogProperties,
|
||||||
) {
|
) {
|
||||||
AdaptiveSheetImpl(
|
AdaptiveSheetImpl(
|
||||||
|
modifier = modifier,
|
||||||
isTabletUi = isTabletUi,
|
isTabletUi = isTabletUi,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
enableSwipeDismiss = enableSwipeDismiss,
|
enableSwipeDismiss = enableSwipeDismiss,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
modifier = modifier,
|
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.TextFieldDefaults
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
@ -20,7 +21,6 @@ import androidx.compose.material3.LocalContentColor
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PlainTooltip
|
import androidx.compose.material3.PlainTooltip
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
|
||||||
import androidx.compose.material3.TooltipBox
|
import androidx.compose.material3.TooltipBox
|
||||||
import androidx.compose.material3.TooltipDefaults
|
import androidx.compose.material3.TooltipDefaults
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
@ -36,7 +36,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusDirection
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@ -180,7 +179,7 @@ fun AppBarTitle(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.basicMarquee(
|
modifier = Modifier.basicMarquee(
|
||||||
repeatDelayMillis = 2_000,
|
delayMillis = 2_000,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -202,7 +201,6 @@ fun AppBarActions(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
focusable = false,
|
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = it.onClick,
|
onClick = it.onClick,
|
||||||
@ -227,7 +225,6 @@ fun AppBarActions(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
focusable = false,
|
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { showMenu = !showMenu },
|
onClick = { showMenu = !showMenu },
|
||||||
@ -292,7 +289,6 @@ fun SearchToolbar(
|
|||||||
onSearch(searchQuery)
|
onSearch(searchQuery)
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
focusManager.moveFocus(FocusDirection.Next)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
@ -316,7 +312,7 @@ fun SearchToolbar(
|
|||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
decorationBox = { innerTextField ->
|
decorationBox = { innerTextField ->
|
||||||
TextFieldDefaults.DecorationBox(
|
TextFieldDefaults.TextFieldDecorationBox(
|
||||||
value = searchQuery,
|
value = searchQuery,
|
||||||
innerTextField = innerTextField,
|
innerTextField = innerTextField,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
@ -335,7 +331,6 @@ fun SearchToolbar(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
container = {},
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -356,7 +351,6 @@ fun SearchToolbar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
focusable = false,
|
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
@ -376,7 +370,6 @@ fun SearchToolbar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
focusable = false,
|
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
@ -9,26 +9,20 @@ import tachiyomi.i18n.MR
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import java.util.Date
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun relativeDateText(
|
fun relativeDateText(
|
||||||
dateEpochMillis: Long,
|
dateEpochMillis: Long,
|
||||||
): String {
|
): String {
|
||||||
return relativeDateText(
|
return relativeDateText(
|
||||||
localDate = LocalDate.ofInstant(
|
date = Date(dateEpochMillis).takeIf { dateEpochMillis > 0L },
|
||||||
Instant.ofEpochMilli(dateEpochMillis),
|
|
||||||
ZoneId.systemDefault(),
|
|
||||||
)
|
|
||||||
.takeIf { dateEpochMillis > 0L },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun relativeDateText(
|
fun relativeDateText(
|
||||||
localDate: LocalDate?,
|
date: Date?,
|
||||||
): String {
|
): String {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -36,10 +30,11 @@ fun relativeDateText(
|
|||||||
val relativeTime = remember { preferences.relativeTime().get() }
|
val relativeTime = remember { preferences.relativeTime().get() }
|
||||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
||||||
|
|
||||||
return localDate?.toRelativeString(
|
return date
|
||||||
context = context,
|
?.toRelativeString(
|
||||||
relative = relativeTime,
|
context = context,
|
||||||
dateFormat = dateFormat,
|
relative = relativeTime,
|
||||||
)
|
dateFormat = dateFormat,
|
||||||
|
)
|
||||||
?: stringResource(MR.strings.not_applicable)
|
?: stringResource(MR.strings.not_applicable)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -30,6 +29,7 @@ import androidx.compose.ui.util.fastForEachIndexed
|
|||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.components.material.TabText
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@ -58,7 +58,6 @@ fun TabbedDialog(
|
|||||||
PrimaryTabRow(
|
PrimaryTabRow(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
|
||||||
divider = {},
|
divider = {},
|
||||||
) {
|
) {
|
||||||
tabTitles.fastForEachIndexed { index, tab ->
|
tabTitles.fastForEachIndexed { index, tab ->
|
||||||
@ -79,8 +78,9 @@ fun TabbedDialog(
|
|||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
verticalAlignment = Alignment.Top,
|
verticalAlignment = Alignment.Top,
|
||||||
pageContent = { page -> content(page) },
|
) { page ->
|
||||||
)
|
content(page)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.calculateEndPadding
|
|||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
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.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryTabRow
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
@ -15,6 +13,7 @@ import androidx.compose.material3.SnackbarHost
|
|||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -25,6 +24,7 @@ import dev.icerock.moko.resources.StringResource
|
|||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.TabText
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
@ -33,13 +33,20 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
fun TabbedScreen(
|
fun TabbedScreen(
|
||||||
titleRes: StringResource,
|
titleRes: StringResource,
|
||||||
tabs: ImmutableList<TabContent>,
|
tabs: ImmutableList<TabContent>,
|
||||||
state: PagerState = rememberPagerState { tabs.size },
|
startIndex: Int? = null,
|
||||||
searchQuery: String? = null,
|
searchQuery: String? = null,
|
||||||
onChangeSearchQuery: (String?) -> Unit = {},
|
onChangeSearchQuery: (String?) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val state = rememberPagerState { tabs.size }
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(startIndex) {
|
||||||
|
if (startIndex != null) {
|
||||||
|
state.scrollToPage(startIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
val tab = tabs[state.currentPage]
|
val tab = tabs[state.currentPage]
|
||||||
|
@ -37,7 +37,7 @@ fun CrashScreen(
|
|||||||
acceptText = stringResource(MR.strings.pref_dump_crash_logs),
|
acceptText = stringResource(MR.strings.pref_dump_crash_logs),
|
||||||
onAcceptClick = {
|
onAcceptClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
CrashLogUtil(context).dumpLogs(exception)
|
CrashLogUtil(context).dumpLogs()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rejectText = stringResource(MR.strings.crash_screen_restart_application),
|
rejectText = stringResource(MR.strings.crash_screen_restart_application),
|
||||||
|
@ -18,7 +18,6 @@ import eu.kanade.presentation.components.SearchToolbar
|
|||||||
import eu.kanade.presentation.components.relativeDateText
|
import eu.kanade.presentation.components.relativeDateText
|
||||||
import eu.kanade.presentation.history.components.HistoryItem
|
import eu.kanade.presentation.history.components.HistoryItem
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@ -30,7 +29,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import java.time.LocalDate
|
import java.util.Date
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryScreen(
|
fun HistoryScreen(
|
||||||
@ -39,7 +38,6 @@ fun HistoryScreen(
|
|||||||
onSearchQueryChange: (String?) -> Unit,
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
onClickCover: (mangaId: Long) -> Unit,
|
onClickCover: (mangaId: Long) -> Unit,
|
||||||
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
||||||
onClickFavorite: (mangaId: Long) -> Unit,
|
|
||||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -86,7 +84,6 @@ fun HistoryScreen(
|
|||||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
||||||
onClickFavorite = { history -> onClickFavorite(history.mangaId) },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +97,6 @@ private fun HistoryScreenContent(
|
|||||||
onClickCover: (HistoryWithRelations) -> Unit,
|
onClickCover: (HistoryWithRelations) -> Unit,
|
||||||
onClickResume: (HistoryWithRelations) -> Unit,
|
onClickResume: (HistoryWithRelations) -> Unit,
|
||||||
onClickDelete: (HistoryWithRelations) -> Unit,
|
onClickDelete: (HistoryWithRelations) -> Unit,
|
||||||
onClickFavorite: (HistoryWithRelations) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
@ -118,19 +114,18 @@ private fun HistoryScreenContent(
|
|||||||
when (item) {
|
when (item) {
|
||||||
is HistoryUiModel.Header -> {
|
is HistoryUiModel.Header -> {
|
||||||
ListGroupHeader(
|
ListGroupHeader(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
text = relativeDateText(item.date),
|
text = relativeDateText(item.date),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HistoryUiModel.Item -> {
|
is HistoryUiModel.Item -> {
|
||||||
val value = item.item
|
val value = item.item
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
history = value,
|
history = value,
|
||||||
onClickCover = { onClickCover(value) },
|
onClickCover = { onClickCover(value) },
|
||||||
onClickResume = { onClickResume(value) },
|
onClickResume = { onClickResume(value) },
|
||||||
onClickDelete = { onClickDelete(value) },
|
onClickDelete = { onClickDelete(value) },
|
||||||
onClickFavorite = { onClickFavorite(value) },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +134,7 @@ private fun HistoryScreenContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed interface HistoryUiModel {
|
sealed interface HistoryUiModel {
|
||||||
data class Header(val date: LocalDate) : HistoryUiModel
|
data class Header(val date: Date) : HistoryUiModel
|
||||||
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +152,6 @@ internal fun HistoryScreenPreviews(
|
|||||||
onClickCover = {},
|
onClickCover = {},
|
||||||
onClickResume = { _, _ -> run {} },
|
onClickResume = { _, _ -> run {} },
|
||||||
onDialogChange = {},
|
onDialogChange = {},
|
||||||
onClickFavorite = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import kotlinx.collections.immutable.toImmutableList
|
|||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@ -74,10 +73,10 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
|||||||
private object HistoryUiModelExamples {
|
private object HistoryUiModelExamples {
|
||||||
val headerToday = header()
|
val headerToday = header()
|
||||||
val headerTomorrow =
|
val headerTomorrow =
|
||||||
HistoryUiModel.Header(LocalDate.now().plusDays(1))
|
HistoryUiModel.Header(Date.from(Instant.now().plus(1, ChronoUnit.DAYS)))
|
||||||
|
|
||||||
fun header(instantBuilder: (Instant) -> Instant = { it }) =
|
fun header(instantBuilder: (Instant) -> Instant = { it }) =
|
||||||
HistoryUiModel.Header(LocalDate.from(instantBuilder(Instant.now())))
|
HistoryUiModel.Header(Date.from(instantBuilder(Instant.now())))
|
||||||
|
|
||||||
fun items() = sequence {
|
fun items() = sequence {
|
||||||
var count = 1
|
var count = 1
|
||||||
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.FavoriteBorder
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -40,7 +39,6 @@ fun HistoryItem(
|
|||||||
onClickCover: () -> Unit,
|
onClickCover: () -> Unit,
|
||||||
onClickResume: () -> Unit,
|
onClickResume: () -> Unit,
|
||||||
onClickDelete: () -> Unit,
|
onClickDelete: () -> Unit,
|
||||||
onClickFavorite: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@ -84,16 +82,6 @@ fun HistoryItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!history.coverData.isMangaFavorite) {
|
|
||||||
IconButton(onClick = onClickFavorite) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.FavoriteBorder,
|
|
||||||
contentDescription = stringResource(MR.strings.add_to_library),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(onClick = onClickDelete) {
|
IconButton(onClick = onClickDelete) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Delete,
|
imageVector = Icons.Outlined.Delete,
|
||||||
@ -117,7 +105,6 @@ private fun HistoryItemPreviews(
|
|||||||
onClickCover = {},
|
onClickCover = {},
|
||||||
onClickResume = {},
|
onClickResume = {},
|
||||||
onClickDelete = {},
|
onClickDelete = {},
|
||||||
onClickFavorite = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,7 @@ import androidx.compose.foundation.layout.ColumnScope
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@ -38,7 +35,6 @@ import tachiyomi.domain.library.model.sort
|
|||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.components.BaseSortItem
|
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.HeadingItem
|
||||||
import tachiyomi.presentation.core.components.IconItem
|
import tachiyomi.presentation.core.components.IconItem
|
||||||
@ -159,7 +155,7 @@ private fun ColumnScope.FilterPage(
|
|||||||
)
|
)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
val trackers by screenModel.trackersFlow.collectAsState()
|
val trackers = remember { screenModel.trackers }
|
||||||
when (trackers.size) {
|
when (trackers.size) {
|
||||||
0 -> {
|
0 -> {
|
||||||
// No trackers
|
// No trackers
|
||||||
@ -192,7 +188,6 @@ private fun ColumnScope.SortPage(
|
|||||||
category: Category?,
|
category: Category?,
|
||||||
screenModel: LibrarySettingsScreenModel,
|
screenModel: LibrarySettingsScreenModel,
|
||||||
) {
|
) {
|
||||||
val trackers by screenModel.trackersFlow.collectAsState()
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val globalSortMode by screenModel.libraryPreferences.sortingMode().collectAsState()
|
val globalSortMode by screenModel.libraryPreferences.sortingMode().collectAsState()
|
||||||
val sortingMode = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
|
val sortingMode = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
|
||||||
@ -201,58 +196,40 @@ private fun ColumnScope.SortPage(
|
|||||||
globalSortMode.type
|
globalSortMode.type
|
||||||
}
|
}
|
||||||
val sortDescending = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
|
val sortDescending = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
|
||||||
!category.sort.isAscending
|
category.sort.isAscending
|
||||||
} else {
|
} else {
|
||||||
!globalSortMode.isAscending
|
globalSortMode.isAscending
|
||||||
}
|
}.not()
|
||||||
val hasSortTags by remember {
|
val hasSortTags by remember {
|
||||||
screenModel.libraryPreferences.sortTagsForLibrary().changes()
|
screenModel.libraryPreferences.sortTagsForLibrary().changes()
|
||||||
.map { it.isNotEmpty() }
|
.map { it.isNotEmpty() }
|
||||||
}.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty())
|
}.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty())
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
val options = remember(trackers.isEmpty()/* SY --> */, hasSortTags/* SY <-- */) {
|
val trackerSortOption =
|
||||||
val trackerMeanPair = if (trackers.isNotEmpty()) {
|
if (screenModel.trackers.isEmpty()) {
|
||||||
MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
null
|
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 -->
|
// SY -->
|
||||||
val tagSortPair = if (hasSortTags) {
|
if (hasSortTags) {
|
||||||
SYMR.strings.tag_sorting to LibrarySort.Type.TagList
|
SYMR.strings.tag_sorting to LibrarySort.Type.TagList
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
},
|
||||||
// SY <--
|
// SY <--
|
||||||
listOfNotNull(
|
).plus(trackerSortOption).map { (titleRes, mode) ->
|
||||||
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(
|
SortItem(
|
||||||
label = stringResource(titleRes),
|
label = stringResource(titleRes),
|
||||||
sortDescending = sortDescending.takeIf { sortingMode == mode },
|
sortDescending = sortDescending.takeIf { sortingMode == mode },
|
||||||
@ -264,11 +241,7 @@ private fun ColumnScope.SortPage(
|
|||||||
} else {
|
} else {
|
||||||
LibrarySort.Direction.Descending
|
LibrarySort.Direction.Descending
|
||||||
}
|
}
|
||||||
else -> if (sortDescending) {
|
else -> if (sortDescending) LibrarySort.Direction.Descending else LibrarySort.Direction.Ascending
|
||||||
LibrarySort.Direction.Descending
|
|
||||||
} else {
|
|
||||||
LibrarySort.Direction.Ascending
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
screenModel.setSort(category, mode, direction)
|
screenModel.setSort(category, mode, direction)
|
||||||
},
|
},
|
||||||
@ -310,16 +283,15 @@ private fun ColumnScope.DisplayPage(
|
|||||||
|
|
||||||
val columns by columnPreference.collectAsState()
|
val columns by columnPreference.collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = columns,
|
|
||||||
valueRange = 0..10,
|
|
||||||
label = stringResource(MR.strings.pref_library_columns),
|
label = stringResource(MR.strings.pref_library_columns),
|
||||||
|
max = 10,
|
||||||
|
value = columns,
|
||||||
valueText = if (columns > 0) {
|
valueText = if (columns > 0) {
|
||||||
columns.toString()
|
stringResource(MR.strings.pref_library_columns_per_row, columns)
|
||||||
} else {
|
} else {
|
||||||
stringResource(MR.strings.label_auto)
|
stringResource(MR.strings.label_default)
|
||||||
},
|
},
|
||||||
onChange = columnPreference::set,
|
onChange = columnPreference::set,
|
||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,10 +300,6 @@ private fun ColumnScope.DisplayPage(
|
|||||||
label = stringResource(MR.strings.action_display_download_badge),
|
label = stringResource(MR.strings.action_display_download_badge),
|
||||||
pref = screenModel.libraryPreferences.downloadBadge(),
|
pref = screenModel.libraryPreferences.downloadBadge(),
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
|
||||||
label = stringResource(MR.strings.action_display_unread_badge),
|
|
||||||
pref = screenModel.libraryPreferences.unreadBadge(),
|
|
||||||
)
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_local_badge),
|
label = stringResource(MR.strings.action_display_local_badge),
|
||||||
pref = screenModel.libraryPreferences.localBadge(),
|
pref = screenModel.libraryPreferences.localBadge(),
|
||||||
@ -378,13 +346,12 @@ private fun ColumnScope.GroupPage(
|
|||||||
screenModel: LibrarySettingsScreenModel,
|
screenModel: LibrarySettingsScreenModel,
|
||||||
hasCategories: Boolean,
|
hasCategories: Boolean,
|
||||||
) {
|
) {
|
||||||
val trackers by screenModel.trackersFlow.collectAsState()
|
val groups = remember(hasCategories, screenModel.trackers) {
|
||||||
val groups = remember(hasCategories, trackers) {
|
|
||||||
buildList {
|
buildList {
|
||||||
add(LibraryGroup.BY_DEFAULT)
|
add(LibraryGroup.BY_DEFAULT)
|
||||||
add(LibraryGroup.BY_SOURCE)
|
add(LibraryGroup.BY_SOURCE)
|
||||||
add(LibraryGroup.BY_STATUS)
|
add(LibraryGroup.BY_STATUS)
|
||||||
if (trackers.isNotEmpty()) {
|
if (screenModel.trackers.isNotEmpty()) {
|
||||||
add(LibraryGroup.BY_TRACK_STATUS)
|
add(LibraryGroup.BY_TRACK_STATUS)
|
||||||
}
|
}
|
||||||
if (hasCategories) {
|
if (hasCategories) {
|
||||||
|
@ -35,7 +35,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.Shadow
|
import androidx.compose.ui.graphics.Shadow
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
@ -43,26 +42,19 @@ import tachiyomi.i18n.MR
|
|||||||
import tachiyomi.presentation.core.components.BadgeGroup
|
import tachiyomi.presentation.core.components.BadgeGroup
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
import tachiyomi.domain.manga.model.MangaCover as MangaCoverModel
|
|
||||||
|
|
||||||
object CommonMangaItemDefaults {
|
object CommonMangaItemDefaults {
|
||||||
val GridHorizontalSpacer = 4.dp
|
val GridHorizontalSpacer = 4.dp
|
||||||
val GridVerticalSpacer = 4.dp
|
val GridVerticalSpacer = 4.dp
|
||||||
|
|
||||||
@Suppress("ConstPropertyName")
|
|
||||||
const val BrowseFavoriteCoverAlpha = 0.34f
|
const val BrowseFavoriteCoverAlpha = 0.34f
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ContinueReadingButtonSizeSmall = 28.dp
|
private val ContinueReadingButtonSize = 28.dp
|
||||||
private val ContinueReadingButtonSizeLarge = 32.dp
|
|
||||||
|
|
||||||
private val ContinueReadingButtonIconSizeSmall = 16.dp
|
|
||||||
private val ContinueReadingButtonIconSizeLarge = 20.dp
|
|
||||||
|
|
||||||
private val ContinueReadingButtonGridPadding = 6.dp
|
private val ContinueReadingButtonGridPadding = 6.dp
|
||||||
private val ContinueReadingButtonListSpacing = 8.dp
|
private val ContinueReadingButtonListSpacing = 8.dp
|
||||||
|
|
||||||
private const val GRID_SELECTED_COVER_ALPHA = 0.76f
|
private const val GridSelectedCoverAlpha = 0.76f
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout of grid list item with title overlaying the cover.
|
* Layout of grid list item with title overlaying the cover.
|
||||||
@ -70,7 +62,7 @@ private const val GRID_SELECTED_COVER_ALPHA = 0.76f
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaCompactGridItem(
|
fun MangaCompactGridItem(
|
||||||
coverData: MangaCoverModel,
|
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
isSelected: Boolean = false,
|
isSelected: Boolean = false,
|
||||||
@ -90,7 +82,7 @@ fun MangaCompactGridItem(
|
|||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
|
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
||||||
data = coverData,
|
data = coverData,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -104,12 +96,10 @@ fun MangaCompactGridItem(
|
|||||||
)
|
)
|
||||||
} else if (onClickContinueReading != null) {
|
} else if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(
|
ContinueReadingButton(
|
||||||
size = ContinueReadingButtonSizeLarge,
|
|
||||||
iconSize = ContinueReadingButtonIconSizeLarge,
|
|
||||||
onClick = onClickContinueReading,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(ContinueReadingButtonGridPadding)
|
.padding(ContinueReadingButtonGridPadding)
|
||||||
.align(Alignment.BottomEnd),
|
.align(Alignment.BottomEnd),
|
||||||
|
onClickContinueReading = onClickContinueReading,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -158,13 +148,11 @@ private fun BoxScope.CoverTextOverlay(
|
|||||||
)
|
)
|
||||||
if (onClickContinueReading != null) {
|
if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(
|
ContinueReadingButton(
|
||||||
size = ContinueReadingButtonSizeSmall,
|
|
||||||
iconSize = ContinueReadingButtonIconSizeSmall,
|
|
||||||
onClick = onClickContinueReading,
|
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
end = ContinueReadingButtonGridPadding,
|
end = ContinueReadingButtonGridPadding,
|
||||||
bottom = ContinueReadingButtonGridPadding,
|
bottom = ContinueReadingButtonGridPadding,
|
||||||
),
|
),
|
||||||
|
onClickContinueReading = onClickContinueReading,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +163,7 @@ private fun BoxScope.CoverTextOverlay(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaComfortableGridItem(
|
fun MangaComfortableGridItem(
|
||||||
coverData: MangaCoverModel,
|
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||||
title: String,
|
title: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
@ -197,7 +185,7 @@ fun MangaComfortableGridItem(
|
|||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
|
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
||||||
data = coverData,
|
data = coverData,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -206,12 +194,10 @@ fun MangaComfortableGridItem(
|
|||||||
content = {
|
content = {
|
||||||
if (onClickContinueReading != null) {
|
if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(
|
ContinueReadingButton(
|
||||||
size = ContinueReadingButtonSizeLarge,
|
|
||||||
iconSize = ContinueReadingButtonIconSizeLarge,
|
|
||||||
onClick = onClickContinueReading,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(ContinueReadingButtonGridPadding)
|
.padding(ContinueReadingButtonGridPadding)
|
||||||
.align(Alignment.BottomEnd),
|
.align(Alignment.BottomEnd),
|
||||||
|
onClickContinueReading = onClickContinueReading,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -323,14 +309,14 @@ private fun GridItemSelectable(
|
|||||||
private fun Modifier.selectedOutline(
|
private fun Modifier.selectedOutline(
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
color: Color,
|
color: Color,
|
||||||
) = drawBehind { if (isSelected) drawRect(color = color) }
|
) = this then drawBehind { if (isSelected) drawRect(color = color) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout of list item.
|
* Layout of list item.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaListItem(
|
fun MangaListItem(
|
||||||
coverData: MangaCoverModel,
|
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||||
title: String,
|
title: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
@ -368,10 +354,8 @@ fun MangaListItem(
|
|||||||
BadgeGroup(content = badge)
|
BadgeGroup(content = badge)
|
||||||
if (onClickContinueReading != null) {
|
if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(
|
ContinueReadingButton(
|
||||||
size = ContinueReadingButtonSizeSmall,
|
|
||||||
iconSize = ContinueReadingButtonIconSizeSmall,
|
|
||||||
onClick = onClickContinueReading,
|
|
||||||
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
|
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
|
||||||
|
onClickContinueReading = onClickContinueReading,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,25 +363,23 @@ fun MangaListItem(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContinueReadingButton(
|
private fun ContinueReadingButton(
|
||||||
size: Dp,
|
|
||||||
iconSize: Dp,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onClickContinueReading: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
onClick = onClick,
|
onClick = onClickContinueReading,
|
||||||
|
modifier = Modifier.size(ContinueReadingButtonSize),
|
||||||
shape = MaterialTheme.shapes.small,
|
shape = MaterialTheme.shapes.small,
|
||||||
colors = IconButtonDefaults.filledIconButtonColors(
|
colors = IconButtonDefaults.filledIconButtonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
|
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
|
||||||
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
|
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
|
||||||
),
|
),
|
||||||
modifier = Modifier.size(size),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.PlayArrow,
|
imageVector = Icons.Filled.PlayArrow,
|
||||||
contentDescription = stringResource(MR.strings.action_resume),
|
contentDescription = stringResource(MR.strings.action_resume),
|
||||||
modifier = Modifier.size(iconSize),
|
modifier = Modifier.size(16.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ fun LibraryContent(
|
|||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = notSelectionMode,
|
enabled = { notSelectionMode },
|
||||||
) {
|
) {
|
||||||
LibraryPager(
|
LibraryPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@ -23,6 +22,7 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
|
|||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@ internal fun LibraryTabs(
|
|||||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||||
onTabItemClick: (Int) -> Unit,
|
onTabItemClick: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
// SY -->
|
||||||
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
||||||
|
// SY <--
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.zIndex(1f),
|
modifier = Modifier.zIndex(1f),
|
||||||
) {
|
) {
|
||||||
|
@ -38,10 +38,8 @@ fun LibraryToolbar(
|
|||||||
onClickRefresh: () -> Unit,
|
onClickRefresh: () -> Unit,
|
||||||
onClickGlobalUpdate: () -> Unit,
|
onClickGlobalUpdate: () -> Unit,
|
||||||
onClickOpenRandomManga: () -> Unit,
|
onClickOpenRandomManga: () -> Unit,
|
||||||
onClickSyncNow: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickSyncExh: (() -> Unit)?,
|
onClickSyncExh: (() -> Unit)?,
|
||||||
isSyncEnabled: Boolean,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onSearchQueryChange: (String?) -> Unit,
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
@ -62,10 +60,8 @@ fun LibraryToolbar(
|
|||||||
onClickRefresh = onClickRefresh,
|
onClickRefresh = onClickRefresh,
|
||||||
onClickGlobalUpdate = onClickGlobalUpdate,
|
onClickGlobalUpdate = onClickGlobalUpdate,
|
||||||
onClickOpenRandomManga = onClickOpenRandomManga,
|
onClickOpenRandomManga = onClickOpenRandomManga,
|
||||||
onClickSyncNow = onClickSyncNow,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickSyncExh = onClickSyncExh,
|
onClickSyncExh = onClickSyncExh,
|
||||||
isSyncEnabled = isSyncEnabled,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@ -81,10 +77,8 @@ private fun LibraryRegularToolbar(
|
|||||||
onClickRefresh: () -> Unit,
|
onClickRefresh: () -> Unit,
|
||||||
onClickGlobalUpdate: () -> Unit,
|
onClickGlobalUpdate: () -> Unit,
|
||||||
onClickOpenRandomManga: () -> Unit,
|
onClickOpenRandomManga: () -> Unit,
|
||||||
onClickSyncNow: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickSyncExh: (() -> Unit)?,
|
onClickSyncExh: (() -> Unit)?,
|
||||||
isSyncEnabled: Boolean,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
scrollBehavior: TopAppBarScrollBehavior?,
|
scrollBehavior: TopAppBarScrollBehavior?,
|
||||||
) {
|
) {
|
||||||
@ -131,6 +125,7 @@ private fun LibraryRegularToolbar(
|
|||||||
title = stringResource(MR.strings.action_open_random_manga),
|
title = stringResource(MR.strings.action_open_random_manga),
|
||||||
onClick = onClickOpenRandomManga,
|
onClick = onClickOpenRandomManga,
|
||||||
),
|
),
|
||||||
|
|
||||||
).builder().apply {
|
).builder().apply {
|
||||||
// SY -->
|
// SY -->
|
||||||
if (onClickSyncExh != null) {
|
if (onClickSyncExh != null) {
|
||||||
@ -141,14 +136,6 @@ private fun LibraryRegularToolbar(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (isSyncEnabled) {
|
|
||||||
add(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(SYMR.strings.sync_library),
|
|
||||||
onClick = onClickSyncNow,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// SY <--
|
// SY <--
|
||||||
}.build(),
|
}.build(),
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,7 @@ import androidx.compose.ui.window.DialogProperties
|
|||||||
import exh.favorites.FavoritesSyncStatus
|
import exh.favorites.FavoritesSyncStatus
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@ -22,6 +23,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
data class SyncFavoritesProgressProperties(
|
data class SyncFavoritesProgressProperties(
|
||||||
val title: String,
|
val title: String,
|
||||||
val text: String,
|
val text: String,
|
||||||
|
val canDismiss: Boolean,
|
||||||
val positiveButtonText: String? = null,
|
val positiveButtonText: String? = null,
|
||||||
val positiveButton: (() -> Unit)? = null,
|
val positiveButton: (() -> Unit)? = null,
|
||||||
val negativeButtonText: String? = null,
|
val negativeButtonText: String? = null,
|
||||||
@ -32,23 +34,18 @@ data class SyncFavoritesProgressProperties(
|
|||||||
fun SyncFavoritesProgressDialog(
|
fun SyncFavoritesProgressDialog(
|
||||||
status: FavoritesSyncStatus,
|
status: FavoritesSyncStatus,
|
||||||
setStatusIdle: () -> Unit,
|
setStatusIdle: () -> Unit,
|
||||||
openManga: (Long) -> Unit,
|
openManga: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
|
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
|
||||||
when (status) {
|
when (status) {
|
||||||
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
|
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
||||||
text = context.stringResource(
|
text = context.stringResource(SYMR.strings.favorites_sync_bad_library_state, status.message),
|
||||||
SYMR.strings.favorites_sync_bad_library_state,
|
canDismiss = false,
|
||||||
context.stringResource(
|
|
||||||
SYMR.strings.favorites_sync_gallery_in_multiple_categories, status.mangaTitle,
|
|
||||||
status.categories.joinToString(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
|
positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
|
||||||
positiveButton = {
|
positiveButton = {
|
||||||
openManga(status.mangaId)
|
openManga(status.manga)
|
||||||
setStatusIdle()
|
setStatusIdle()
|
||||||
},
|
},
|
||||||
negativeButtonText = context.stringResource(MR.strings.action_ok),
|
negativeButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
@ -56,122 +53,31 @@ fun SyncFavoritesProgressDialog(
|
|||||||
)
|
)
|
||||||
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
|
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
|
title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
|
||||||
text = context.stringResource(
|
text = context.stringResource(SYMR.strings.favorites_sync_done_errors_message, status.message),
|
||||||
SYMR.strings.favorites_sync_done_errors_message,
|
canDismiss = false,
|
||||||
status.messages.joinToString(separator = "\n") {
|
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
when (it) {
|
positiveButton = setStatusIdle,
|
||||||
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
|
)
|
||||||
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
is FavoritesSyncStatus.Error -> value = SyncFavoritesProgressProperties(
|
||||||
context.stringResource(
|
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
||||||
SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, it.reason,
|
text = context.stringResource(SYMR.strings.favorites_sync_error_string, status.message),
|
||||||
)
|
canDismiss = false,
|
||||||
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
|
||||||
context.stringResource(
|
|
||||||
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, it.url,
|
|
||||||
)
|
|
||||||
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, it.title, it.gid)
|
|
||||||
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
positiveButton = setStatusIdle,
|
positiveButton = setStatusIdle,
|
||||||
)
|
)
|
||||||
is FavoritesSyncStatus.Idle -> value = null
|
is FavoritesSyncStatus.Idle -> value = null
|
||||||
is FavoritesSyncStatus.Initializing -> {
|
is FavoritesSyncStatus.Initializing, is FavoritesSyncStatus.Processing -> {
|
||||||
value = SyncFavoritesProgressProperties(
|
value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_syncing),
|
title = context.stringResource(SYMR.strings.favorites_syncing),
|
||||||
text = context.stringResource(SYMR.strings.favorites_sync_initializing),
|
text = status.message,
|
||||||
|
canDismiss = false,
|
||||||
)
|
)
|
||||||
}
|
if (status is FavoritesSyncStatus.Processing && status.title != null) {
|
||||||
|
|
||||||
is FavoritesSyncStatus.SyncError -> value = SyncFavoritesProgressProperties(
|
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
|
||||||
text = context.stringResource(
|
|
||||||
SYMR.strings.favorites_sync_error_string,
|
|
||||||
when (status) {
|
|
||||||
FavoritesSyncStatus.SyncError.NotLoggedInSyncError -> context.stringResource(SYMR.strings.please_login)
|
|
||||||
FavoritesSyncStatus.SyncError.FailedToFetchFavorites ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_failed_to_featch)
|
|
||||||
is FavoritesSyncStatus.SyncError.UnknownSyncError ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_unknown_error, status.message)
|
|
||||||
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
|
||||||
context.stringResource(
|
|
||||||
SYMR.strings.favorites_sync_failed_to_add_to_local_error, status.title, status.reason,
|
|
||||||
)
|
|
||||||
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
|
||||||
context.stringResource(
|
|
||||||
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, status.title, status.url,
|
|
||||||
)
|
|
||||||
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, status.title, status.gid)
|
|
||||||
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
|
||||||
positiveButton = setStatusIdle,
|
|
||||||
)
|
|
||||||
is FavoritesSyncStatus.Processing -> {
|
|
||||||
val properties = SyncFavoritesProgressProperties(
|
|
||||||
title = context.stringResource(SYMR.strings.favorites_syncing),
|
|
||||||
text = when (status) {
|
|
||||||
FavoritesSyncStatus.Processing.VerifyingLibrary ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_verifying_library)
|
|
||||||
FavoritesSyncStatus.Processing.DownloadingFavorites ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_downloading)
|
|
||||||
FavoritesSyncStatus.Processing.CalculatingRemoteChanges ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes)
|
|
||||||
FavoritesSyncStatus.Processing.CalculatingLocalChanges ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes)
|
|
||||||
FavoritesSyncStatus.Processing.SyncingCategoryNames ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names)
|
|
||||||
is FavoritesSyncStatus.Processing.RemovingRemoteGalleries ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, status.galleryCount)
|
|
||||||
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
|
|
||||||
if (status.isThrottling) {
|
|
||||||
context.stringResource(
|
|
||||||
SYMR.strings.favorites_sync_processing_throttle,
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total)
|
|
||||||
}
|
|
||||||
is FavoritesSyncStatus.Processing.RemovingGalleryFromLocal ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, status.index, status.total)
|
|
||||||
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
|
|
||||||
if (status.isThrottling) {
|
|
||||||
context.stringResource(
|
|
||||||
SYMR.strings.favorites_sync_processing_throttle,
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total)
|
|
||||||
}
|
|
||||||
|
|
||||||
FavoritesSyncStatus.Processing.CleaningUp ->
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_cleaning_up)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
value = properties
|
|
||||||
if (
|
|
||||||
status is FavoritesSyncStatus.Processing.AddingGalleryToRemote ||
|
|
||||||
status is FavoritesSyncStatus.Processing.AddingGalleryToLocal
|
|
||||||
) {
|
|
||||||
delay(5.seconds)
|
delay(5.seconds)
|
||||||
value = properties.copy(
|
value = SyncFavoritesProgressProperties(
|
||||||
text = when (status) {
|
title = context.stringResource(SYMR.strings.favorites_syncing),
|
||||||
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
|
text = status.delayedMessage ?: status.message,
|
||||||
properties.text + "\n\n" + status.title
|
canDismiss = false,
|
||||||
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
|
|
||||||
properties.text + "\n\n" + status.title
|
|
||||||
else -> properties.text
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,8 +112,8 @@ fun SyncFavoritesProgressDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
properties = DialogProperties(
|
properties = DialogProperties(
|
||||||
dismissOnClickOutside = false,
|
dismissOnClickOutside = dialog.canDismiss,
|
||||||
dismissOnBackPress = false,
|
dismissOnBackPress = dialog.canDismiss,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,13 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.base.BasePreferences
|
|
||||||
import eu.kanade.domain.manga.model.downloadedFilter
|
import eu.kanade.domain.manga.model.downloadedFilter
|
||||||
|
import eu.kanade.domain.manga.model.forceDownloaded
|
||||||
import eu.kanade.presentation.components.TabbedDialog
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@ -41,8 +40,6 @@ import tachiyomi.presentation.core.components.SortItem
|
|||||||
import tachiyomi.presentation.core.components.TriStateItem
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.theme.active
|
import tachiyomi.presentation.core.theme.active
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterSettingsDialog(
|
fun ChapterSettingsDialog(
|
||||||
@ -66,8 +63,6 @@ fun ChapterSettingsDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly().get() }
|
|
||||||
|
|
||||||
TabbedDialog(
|
TabbedDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
tabTitles = persistentListOf(
|
tabTitles = persistentListOf(
|
||||||
@ -102,7 +97,7 @@ fun ChapterSettingsDialog(
|
|||||||
FilterPage(
|
FilterPage(
|
||||||
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
||||||
onDownloadFilterChanged = onDownloadFilterChanged
|
onDownloadFilterChanged = onDownloadFilterChanged
|
||||||
.takeUnless { downloadedOnly },
|
.takeUnless { manga?.forceDownloaded() == true },
|
||||||
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
||||||
onUnreadFilterChanged = onUnreadFilterChanged,
|
onUnreadFilterChanged = onUnreadFilterChanged,
|
||||||
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
||||||
|
@ -1,406 +1,59 @@
|
|||||||
package eu.kanade.presentation.manga
|
package eu.kanade.presentation.manga
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Brush
|
|
||||||
import androidx.compose.material.icons.filled.PersonOutline
|
|
||||||
import androidx.compose.material.icons.filled.Warning
|
|
||||||
import androidx.compose.material.icons.outlined.Add
|
|
||||||
import androidx.compose.material.icons.outlined.AttachMoney
|
|
||||||
import androidx.compose.material.icons.outlined.Block
|
|
||||||
import androidx.compose.material.icons.outlined.Close
|
|
||||||
import androidx.compose.material.icons.outlined.Done
|
|
||||||
import androidx.compose.material.icons.outlined.DoneAll
|
|
||||||
import androidx.compose.material.icons.outlined.Pause
|
|
||||||
import androidx.compose.material.icons.outlined.Schedule
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.text.TextMeasurer
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.Constraints
|
|
||||||
import androidx.compose.ui.unit.Density
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.util.fastMaxOfOrNull
|
|
||||||
import coil3.request.ImageRequest
|
|
||||||
import coil3.request.crossfade
|
|
||||||
import eu.kanade.presentation.components.AdaptiveSheet
|
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
|
||||||
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
|
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.domain.manga.model.MangaWithChapterCount
|
|
||||||
import tachiyomi.domain.source.model.StubSource
|
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.Badge
|
|
||||||
import tachiyomi.presentation.core.components.BadgeGroup
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DuplicateMangaDialog(
|
fun DuplicateMangaDialog(
|
||||||
duplicates: List<MangaWithChapterCount>,
|
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onConfirm: () -> Unit,
|
onConfirm: () -> Unit,
|
||||||
onOpenManga: (manga: Manga) -> Unit,
|
onOpenManga: () -> Unit,
|
||||||
onMigrate: (manga: Manga) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
) {
|
||||||
val sourceManager = remember { Injekt.get<SourceManager>() }
|
AlertDialog(
|
||||||
val minHeight = LocalPreferenceMinHeight.current
|
|
||||||
val horizontalPadding = PaddingValues(horizontal = TabbedDialogPaddings.Horizontal)
|
|
||||||
val horizontalPaddingModifier = Modifier.padding(horizontalPadding)
|
|
||||||
|
|
||||||
AdaptiveSheet(
|
|
||||||
modifier = modifier,
|
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
) {
|
title = {
|
||||||
Column(
|
Text(text = stringResource(MR.strings.are_you_sure))
|
||||||
modifier = Modifier
|
},
|
||||||
.padding(vertical = TabbedDialogPaddings.Vertical)
|
text = {
|
||||||
.verticalScroll(rememberScrollState())
|
Text(text = stringResource(MR.strings.confirm_add_duplicate_manga))
|
||||||
.fillMaxWidth(),
|
},
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
confirmButton = {
|
||||||
) {
|
FlowRow(
|
||||||
Text(
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
text = stringResource(MR.strings.possible_duplicates_title),
|
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
|
||||||
modifier = Modifier
|
|
||||||
.then(horizontalPaddingModifier)
|
|
||||||
.padding(top = MaterialTheme.padding.small),
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(MR.strings.possible_duplicates_summary),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.then(horizontalPaddingModifier),
|
|
||||||
)
|
|
||||||
|
|
||||||
LazyRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
|
||||||
modifier = Modifier.height(getMaximumMangaCardHeight(duplicates)),
|
|
||||||
contentPadding = horizontalPadding,
|
|
||||||
) {
|
) {
|
||||||
items(
|
TextButton(
|
||||||
items = duplicates,
|
onClick = {
|
||||||
key = { it.manga.id },
|
onDismissRequest()
|
||||||
|
onOpenManga()
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
DuplicateMangaListItem(
|
Text(text = stringResource(MR.strings.action_show_manga))
|
||||||
duplicate = it,
|
|
||||||
getSource = { sourceManager.getOrStub(it.manga.source) },
|
|
||||||
onMigrate = { onMigrate(it.manga) },
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onOpenManga = { onOpenManga(it.manga) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = horizontalPaddingModifier) {
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
HorizontalDivider()
|
|
||||||
|
|
||||||
TextPreferenceWidget(
|
TextButton(onClick = onDismissRequest) {
|
||||||
title = stringResource(MR.strings.action_add_anyway),
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
icon = Icons.Outlined.Add,
|
}
|
||||||
onPreferenceClick = {
|
TextButton(
|
||||||
|
onClick = {
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
onConfirm()
|
onConfirm()
|
||||||
},
|
},
|
||||||
modifier = Modifier.clip(CircleShape),
|
) {
|
||||||
)
|
Text(text = stringResource(MR.strings.action_add))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
OutlinedButton(
|
)
|
||||||
onClick = onDismissRequest,
|
|
||||||
modifier = Modifier
|
|
||||||
.then(horizontalPaddingModifier)
|
|
||||||
.padding(bottom = MaterialTheme.padding.medium)
|
|
||||||
.heightIn(min = minHeight)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(vertical = MaterialTheme.padding.extraSmall),
|
|
||||||
text = stringResource(MR.strings.action_cancel),
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun DuplicateMangaListItem(
|
|
||||||
duplicate: MangaWithChapterCount,
|
|
||||||
getSource: () -> Source,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onOpenManga: () -> Unit,
|
|
||||||
onMigrate: () -> Unit,
|
|
||||||
) {
|
|
||||||
val source = getSource()
|
|
||||||
val manga = duplicate.manga
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(MangaCardWidth)
|
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
|
||||||
.combinedClickable(
|
|
||||||
onLongClick = { onOpenManga() },
|
|
||||||
onClick = {
|
|
||||||
onDismissRequest()
|
|
||||||
onMigrate()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.padding(MaterialTheme.padding.small),
|
|
||||||
) {
|
|
||||||
Box {
|
|
||||||
MangaCover.Book(
|
|
||||||
data = ImageRequest.Builder(LocalContext.current)
|
|
||||||
.data(manga)
|
|
||||||
.crossfade(true)
|
|
||||||
.build(),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
BadgeGroup(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.align(Alignment.TopStart),
|
|
||||||
) {
|
|
||||||
Badge(
|
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
|
||||||
textColor = MaterialTheme.colorScheme.onSecondary,
|
|
||||||
text = pluralStringResource(
|
|
||||||
MR.plurals.manga_num_chapters,
|
|
||||||
duplicate.chapterCount.toInt(),
|
|
||||||
duplicate.chapterCount,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(MaterialTheme.padding.extraSmall))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = manga.title,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 2,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!manga.author.isNullOrBlank()) {
|
|
||||||
MangaDetailRow(
|
|
||||||
text = manga.author!!,
|
|
||||||
iconImageVector = Icons.Filled.PersonOutline,
|
|
||||||
maxLines = 2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!manga.artist.isNullOrBlank() && manga.author != manga.artist) {
|
|
||||||
MangaDetailRow(
|
|
||||||
text = manga.artist!!,
|
|
||||||
iconImageVector = Icons.Filled.Brush,
|
|
||||||
maxLines = 2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
MangaDetailRow(
|
|
||||||
text = when (manga.status) {
|
|
||||||
SManga.ONGOING.toLong() -> stringResource(MR.strings.ongoing)
|
|
||||||
SManga.COMPLETED.toLong() -> stringResource(MR.strings.completed)
|
|
||||||
SManga.LICENSED.toLong() -> stringResource(MR.strings.licensed)
|
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(MR.strings.publishing_finished)
|
|
||||||
SManga.CANCELLED.toLong() -> stringResource(MR.strings.cancelled)
|
|
||||||
SManga.ON_HIATUS.toLong() -> stringResource(MR.strings.on_hiatus)
|
|
||||||
else -> stringResource(MR.strings.unknown)
|
|
||||||
},
|
|
||||||
iconImageVector = when (manga.status) {
|
|
||||||
SManga.ONGOING.toLong() -> Icons.Outlined.Schedule
|
|
||||||
SManga.COMPLETED.toLong() -> Icons.Outlined.DoneAll
|
|
||||||
SManga.LICENSED.toLong() -> Icons.Outlined.AttachMoney
|
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done
|
|
||||||
SManga.CANCELLED.toLong() -> Icons.Outlined.Close
|
|
||||||
SManga.ON_HIATUS.toLong() -> Icons.Outlined.Pause
|
|
||||||
else -> Icons.Outlined.Block
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
if (source is StubSource) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Warning,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = source.name,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun MangaDetailRow(
|
|
||||||
text: String,
|
|
||||||
iconImageVector: ImageVector,
|
|
||||||
maxLines: Int = 1,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = MaterialTheme.padding.extraSmall),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = iconImageVector,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(MangaDetailsIconWidth),
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = maxLines,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getMaximumMangaCardHeight(duplicates: List<MangaWithChapterCount>): Dp {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val typography = MaterialTheme.typography
|
|
||||||
val textMeasurer = rememberTextMeasurer()
|
|
||||||
|
|
||||||
val smallPadding = with(density) { MaterialTheme.padding.small.roundToPx() }
|
|
||||||
val extraSmallPadding = with(density) { MaterialTheme.padding.extraSmall.roundToPx() }
|
|
||||||
|
|
||||||
val width = with(density) { MangaCardWidth.roundToPx() - (2 * smallPadding) }
|
|
||||||
val iconWidth = with(density) { MangaDetailsIconWidth.roundToPx() }
|
|
||||||
|
|
||||||
val coverHeight = width / MangaCover.Book.ratio
|
|
||||||
val constraints = Constraints(maxWidth = width)
|
|
||||||
val detailsConstraints = Constraints(maxWidth = width - iconWidth - extraSmallPadding)
|
|
||||||
|
|
||||||
return remember(
|
|
||||||
duplicates,
|
|
||||||
density,
|
|
||||||
typography,
|
|
||||||
textMeasurer,
|
|
||||||
smallPadding,
|
|
||||||
extraSmallPadding,
|
|
||||||
coverHeight,
|
|
||||||
constraints,
|
|
||||||
detailsConstraints,
|
|
||||||
) {
|
|
||||||
duplicates.fastMaxOfOrNull {
|
|
||||||
calculateMangaCardHeight(
|
|
||||||
manga = it.manga,
|
|
||||||
density = density,
|
|
||||||
typography = typography,
|
|
||||||
textMeasurer = textMeasurer,
|
|
||||||
smallPadding = smallPadding,
|
|
||||||
extraSmallPadding = extraSmallPadding,
|
|
||||||
coverHeight = coverHeight,
|
|
||||||
constraints = constraints,
|
|
||||||
detailsConstraints = detailsConstraints,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
?: 0.dp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateMangaCardHeight(
|
|
||||||
manga: Manga,
|
|
||||||
density: Density,
|
|
||||||
typography: Typography,
|
|
||||||
textMeasurer: TextMeasurer,
|
|
||||||
smallPadding: Int,
|
|
||||||
extraSmallPadding: Int,
|
|
||||||
coverHeight: Float,
|
|
||||||
constraints: Constraints,
|
|
||||||
detailsConstraints: Constraints,
|
|
||||||
): Dp {
|
|
||||||
val titleHeight = textMeasurer.measureHeight(manga.title, typography.titleSmall, 2, constraints)
|
|
||||||
val authorHeight = if (!manga.author.isNullOrBlank()) {
|
|
||||||
textMeasurer.measureHeight(manga.author!!, typography.bodySmall, 2, detailsConstraints)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
val artistHeight = if (!manga.artist.isNullOrBlank() && manga.author != manga.artist) {
|
|
||||||
textMeasurer.measureHeight(manga.artist!!, typography.bodySmall, 2, detailsConstraints)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
val statusHeight = textMeasurer.measureHeight("", typography.bodySmall, 2, detailsConstraints)
|
|
||||||
val sourceHeight = textMeasurer.measureHeight("", typography.labelSmall, 1, constraints)
|
|
||||||
|
|
||||||
val totalHeight = coverHeight + titleHeight + authorHeight + artistHeight + statusHeight + sourceHeight
|
|
||||||
return with(density) { ((2 * smallPadding) + totalHeight + (5 * extraSmallPadding)).toDp() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TextMeasurer.measureHeight(
|
|
||||||
text: String,
|
|
||||||
style: TextStyle,
|
|
||||||
maxLines: Int,
|
|
||||||
constraints: Constraints,
|
|
||||||
): Int = measure(
|
|
||||||
text = text,
|
|
||||||
style = style,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = maxLines,
|
|
||||||
constraints = constraints,
|
|
||||||
)
|
|
||||||
.size
|
|
||||||
.height
|
|
||||||
|
|
||||||
private val MangaCardWidth = 150.dp
|
|
||||||
private val MangaDetailsIconWidth = 16.dp
|
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
package eu.kanade.presentation.manga
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
|
||||||
import androidx.compose.foundation.layout.imePadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
|
||||||
import eu.kanade.presentation.components.AppBarTitle
|
|
||||||
import eu.kanade.presentation.manga.components.MangaNotesTextArea
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreen
|
|
||||||
import tachiyomi.i18n.MR
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaNotesScreen(
|
|
||||||
state: MangaNotesScreen.State,
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
onUpdate: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
topBar = { topBarScrollBehavior ->
|
|
||||||
AppBar(
|
|
||||||
titleContent = {
|
|
||||||
AppBarTitle(
|
|
||||||
title = stringResource(MR.strings.action_edit_notes),
|
|
||||||
subtitle = state.manga.title,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigateUp = navigateUp,
|
|
||||||
scrollBehavior = topBarScrollBehavior,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { contentPadding ->
|
|
||||||
MangaNotesTextArea(
|
|
||||||
state = state,
|
|
||||||
onUpdate = onUpdate,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(contentPadding)
|
|
||||||
.consumeWindowInsets(contentPadding)
|
|
||||||
.imePadding(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -77,7 +77,6 @@ import eu.kanade.tachiyomi.source.online.english.Pururin
|
|||||||
import eu.kanade.tachiyomi.source.online.english.Tsumino
|
import eu.kanade.tachiyomi.source.online.english.Tsumino
|
||||||
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.manga.MergedMangaData
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.PagePreviewState
|
import eu.kanade.tachiyomi.ui.manga.PagePreviewState
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import exh.metadata.MetadataUtil
|
import exh.metadata.MetadataUtil
|
||||||
@ -103,11 +102,11 @@ import tachiyomi.presentation.core.components.material.ExtendedFloatingActionBut
|
|||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
|
import tachiyomi.presentation.core.util.isScrollingUp
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.util.Date
|
||||||
import java.time.ZonedDateTime
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaScreen(
|
fun MangaScreen(
|
||||||
@ -117,7 +116,7 @@ fun MangaScreen(
|
|||||||
isTabletUi: Boolean,
|
isTabletUi: Boolean,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
navigateUp: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
@ -142,7 +141,6 @@ fun MangaScreen(
|
|||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
onEditFetchIntervalClicked: (() -> Unit)?,
|
onEditFetchIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
onEditNotesClicked: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked: () -> Unit,
|
onMetadataViewerClicked: () -> Unit,
|
||||||
onEditInfoClicked: () -> Unit,
|
onEditInfoClicked: () -> Unit,
|
||||||
@ -152,7 +150,6 @@ fun MangaScreen(
|
|||||||
onMergeWithAnotherClicked: () -> Unit,
|
onMergeWithAnotherClicked: () -> Unit,
|
||||||
onOpenPagePreview: (Int) -> Unit,
|
onOpenPagePreview: (Int) -> Unit,
|
||||||
onMorePreviewsClicked: () -> Unit,
|
onMorePreviewsClicked: () -> Unit,
|
||||||
previewsRowCount: Int,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// For bottom action menu
|
// For bottom action menu
|
||||||
@ -183,7 +180,7 @@ fun MangaScreen(
|
|||||||
nextUpdate = nextUpdate,
|
nextUpdate = nextUpdate,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
navigateUp = navigateUp,
|
onBackClicked = onBackClicked,
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
onDownloadChapter = onDownloadChapter,
|
onDownloadChapter = onDownloadChapter,
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
@ -202,7 +199,6 @@ fun MangaScreen(
|
|||||||
onEditCategoryClicked = onEditCategoryClicked,
|
onEditCategoryClicked = onEditCategoryClicked,
|
||||||
onEditIntervalClicked = onEditFetchIntervalClicked,
|
onEditIntervalClicked = onEditFetchIntervalClicked,
|
||||||
onMigrateClicked = onMigrateClicked,
|
onMigrateClicked = onMigrateClicked,
|
||||||
onEditNotesClicked = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked = onMetadataViewerClicked,
|
onMetadataViewerClicked = onMetadataViewerClicked,
|
||||||
onEditInfoClicked = onEditInfoClicked,
|
onEditInfoClicked = onEditInfoClicked,
|
||||||
@ -212,7 +208,6 @@ fun MangaScreen(
|
|||||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||||
onOpenPagePreview = onOpenPagePreview,
|
onOpenPagePreview = onOpenPagePreview,
|
||||||
onMorePreviewsClicked = onMorePreviewsClicked,
|
onMorePreviewsClicked = onMorePreviewsClicked,
|
||||||
previewsRowCount = previewsRowCount,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||||
@ -230,7 +225,7 @@ fun MangaScreen(
|
|||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
nextUpdate = nextUpdate,
|
nextUpdate = nextUpdate,
|
||||||
navigateUp = navigateUp,
|
onBackClicked = onBackClicked,
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
onDownloadChapter = onDownloadChapter,
|
onDownloadChapter = onDownloadChapter,
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
@ -249,7 +244,6 @@ fun MangaScreen(
|
|||||||
onEditCategoryClicked = onEditCategoryClicked,
|
onEditCategoryClicked = onEditCategoryClicked,
|
||||||
onEditIntervalClicked = onEditFetchIntervalClicked,
|
onEditIntervalClicked = onEditFetchIntervalClicked,
|
||||||
onMigrateClicked = onMigrateClicked,
|
onMigrateClicked = onMigrateClicked,
|
||||||
onEditNotesClicked = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked = onMetadataViewerClicked,
|
onMetadataViewerClicked = onMetadataViewerClicked,
|
||||||
onEditInfoClicked = onEditInfoClicked,
|
onEditInfoClicked = onEditInfoClicked,
|
||||||
@ -259,7 +253,6 @@ fun MangaScreen(
|
|||||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||||
onOpenPagePreview = onOpenPagePreview,
|
onOpenPagePreview = onOpenPagePreview,
|
||||||
onMorePreviewsClicked = onMorePreviewsClicked,
|
onMorePreviewsClicked = onMorePreviewsClicked,
|
||||||
previewsRowCount = previewsRowCount,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||||
@ -280,7 +273,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
nextUpdate: Instant?,
|
nextUpdate: Instant?,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
navigateUp: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
@ -306,7 +299,6 @@ private fun MangaScreenSmallImpl(
|
|||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
onEditIntervalClicked: (() -> Unit)?,
|
onEditIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
onEditNotesClicked: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked: () -> Unit,
|
onMetadataViewerClicked: () -> Unit,
|
||||||
onEditInfoClicked: () -> Unit,
|
onEditInfoClicked: () -> Unit,
|
||||||
@ -316,7 +308,6 @@ private fun MangaScreenSmallImpl(
|
|||||||
onMergeWithAnotherClicked: () -> Unit,
|
onMergeWithAnotherClicked: () -> Unit,
|
||||||
onOpenPagePreview: (Int) -> Unit,
|
onOpenPagePreview: (Int) -> Unit,
|
||||||
onMorePreviewsClicked: () -> Unit,
|
onMorePreviewsClicked: () -> Unit,
|
||||||
previewsRowCount: Int,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// For bottom action menu
|
// For bottom action menu
|
||||||
@ -349,9 +340,14 @@ private fun MangaScreenSmallImpl(
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
BackHandler(enabled = isAnySelected) {
|
val internalOnBackPressed = {
|
||||||
onAllChapterSelected(false)
|
if (isAnySelected) {
|
||||||
|
onAllChapterSelected(false)
|
||||||
|
} else {
|
||||||
|
onBackClicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BackHandler(onBack = internalOnBackPressed)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -364,25 +360,26 @@ private fun MangaScreenSmallImpl(
|
|||||||
val isFirstItemScrolled by remember {
|
val isFirstItemScrolled by remember {
|
||||||
derivedStateOf { chapterListState.firstVisibleItemScrollOffset > 0 }
|
derivedStateOf { chapterListState.firstVisibleItemScrollOffset > 0 }
|
||||||
}
|
}
|
||||||
val titleAlpha by animateFloatAsState(
|
val animatedTitleAlpha by animateFloatAsState(
|
||||||
if (!isFirstItemVisible) 1f else 0f,
|
if (!isFirstItemVisible) 1f else 0f,
|
||||||
label = "Top Bar Title",
|
label = "Top Bar Title",
|
||||||
)
|
)
|
||||||
val backgroundAlpha by animateFloatAsState(
|
val animatedBgAlpha by animateFloatAsState(
|
||||||
if (!isFirstItemVisible || isFirstItemScrolled) 1f else 0f,
|
if (!isFirstItemVisible || isFirstItemScrolled) 1f else 0f,
|
||||||
label = "Top Bar Background",
|
label = "Top Bar Background",
|
||||||
)
|
)
|
||||||
MangaToolbar(
|
MangaToolbar(
|
||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
|
titleAlphaProvider = { animatedTitleAlpha },
|
||||||
|
backgroundAlphaProvider = { animatedBgAlpha },
|
||||||
hasFilters = state.filterActive,
|
hasFilters = state.filterActive,
|
||||||
navigateUp = navigateUp,
|
onBackClicked = internalOnBackPressed,
|
||||||
onClickFilter = onFilterClicked,
|
onClickFilter = onFilterClicked,
|
||||||
onClickShare = onShareClicked,
|
onClickShare = onShareClicked,
|
||||||
onClickDownload = onDownloadActionClicked,
|
onClickDownload = onDownloadActionClicked,
|
||||||
onClickEditCategory = onEditCategoryClicked,
|
onClickEditCategory = onEditCategoryClicked,
|
||||||
onClickRefresh = onRefresh,
|
onClickRefresh = onRefresh,
|
||||||
onClickMigrate = onMigrateClicked,
|
onClickMigrate = onMigrateClicked,
|
||||||
onClickEditNotes = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
||||||
onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow },
|
onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow },
|
||||||
@ -390,11 +387,8 @@ private fun MangaScreenSmallImpl(
|
|||||||
onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow },
|
onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow },
|
||||||
// SY <--
|
// SY <--
|
||||||
actionModeCounter = selectedChapterCount,
|
actionModeCounter = selectedChapterCount,
|
||||||
onCancelActionMode = { onAllChapterSelected(false) },
|
|
||||||
onSelectAll = { onAllChapterSelected(true) },
|
onSelectAll = { onAllChapterSelected(true) },
|
||||||
onInvertSelection = { onInvertSelection() },
|
onInvertSelection = { onInvertSelection() },
|
||||||
titleAlphaProvider = { titleAlpha },
|
|
||||||
backgroundAlphaProvider = { backgroundAlpha },
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
@ -432,7 +426,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
expanded = chapterListState.shouldExpandFAB(),
|
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -442,7 +436,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !isAnySelected,
|
enabled = { !isAnySelected },
|
||||||
indicatorPadding = PaddingValues(top = topPadding),
|
indicatorPadding = PaddingValues(top = topPadding),
|
||||||
) {
|
) {
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
@ -467,9 +461,13 @@ private fun MangaScreenSmallImpl(
|
|||||||
MangaInfoBox(
|
MangaInfoBox(
|
||||||
isTabletUi = false,
|
isTabletUi = false,
|
||||||
appBarPadding = topPadding,
|
appBarPadding = topPadding,
|
||||||
manga = state.manga,
|
title = state.manga.title,
|
||||||
|
author = state.manga.author,
|
||||||
|
artist = state.manga.artist,
|
||||||
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
|
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
|
||||||
isStubSource = remember { state.source is StubSource },
|
isStubSource = remember { state.source is StubSource },
|
||||||
|
coverDataProvider = { state.manga },
|
||||||
|
status = state.manga.status,
|
||||||
onCoverClick = onCoverClicked,
|
onCoverClick = onCoverClicked,
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
)
|
)
|
||||||
@ -520,10 +518,8 @@ private fun MangaScreenSmallImpl(
|
|||||||
defaultExpandState = state.isFromSource,
|
defaultExpandState = state.isFromSource,
|
||||||
description = state.manga.description,
|
description = state.manga.description,
|
||||||
tagsProvider = { state.manga.genre },
|
tagsProvider = { state.manga.genre },
|
||||||
notes = state.manga.notes,
|
|
||||||
onTagSearch = onTagSearch,
|
onTagSearch = onTagSearch,
|
||||||
onCopyTagToClipboard = onCopyTagToClipboard,
|
onCopyTagToClipboard = onCopyTagToClipboard,
|
||||||
onEditNotes = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
||||||
@ -548,14 +544,13 @@ private fun MangaScreenSmallImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.pagePreviewsState !is PagePreviewState.Unused && previewsRowCount > 0) {
|
if (state.pagePreviewsState !is PagePreviewState.Unused) {
|
||||||
PagePreviewItems(
|
PagePreviewItems(
|
||||||
pagePreviewState = state.pagePreviewsState,
|
pagePreviewState = state.pagePreviewsState,
|
||||||
onOpenPage = onOpenPagePreview,
|
onOpenPage = onOpenPagePreview,
|
||||||
onMorePreviewsClicked = onMorePreviewsClicked,
|
onMorePreviewsClicked = onMorePreviewsClicked,
|
||||||
maxWidth = maxWidth,
|
maxWidth = maxWidth,
|
||||||
setMaxWidth = { maxWidth = it },
|
setMaxWidth = { maxWidth = it }
|
||||||
rowCount = previewsRowCount,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -577,7 +572,6 @@ private fun MangaScreenSmallImpl(
|
|||||||
|
|
||||||
sharedChapterItems(
|
sharedChapterItems(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
mergedData = state.mergedData,
|
|
||||||
chapters = listItem,
|
chapters = listItem,
|
||||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
@ -603,7 +597,7 @@ fun MangaScreenLargeImpl(
|
|||||||
nextUpdate: Instant?,
|
nextUpdate: Instant?,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
navigateUp: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
@ -629,7 +623,6 @@ fun MangaScreenLargeImpl(
|
|||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
onEditIntervalClicked: (() -> Unit)?,
|
onEditIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
onEditNotesClicked: () -> Unit,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onMetadataViewerClicked: () -> Unit,
|
onMetadataViewerClicked: () -> Unit,
|
||||||
onEditInfoClicked: () -> Unit,
|
onEditInfoClicked: () -> Unit,
|
||||||
@ -639,7 +632,6 @@ fun MangaScreenLargeImpl(
|
|||||||
onMergeWithAnotherClicked: () -> Unit,
|
onMergeWithAnotherClicked: () -> Unit,
|
||||||
onOpenPagePreview: (Int) -> Unit,
|
onOpenPagePreview: (Int) -> Unit,
|
||||||
onMorePreviewsClicked: () -> Unit,
|
onMorePreviewsClicked: () -> Unit,
|
||||||
previewsRowCount: Int,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// For bottom action menu
|
// For bottom action menu
|
||||||
@ -676,9 +668,14 @@ fun MangaScreenLargeImpl(
|
|||||||
|
|
||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
BackHandler(enabled = isAnySelected) {
|
val internalOnBackPressed = {
|
||||||
onAllChapterSelected(false)
|
if (isAnySelected) {
|
||||||
|
onAllChapterSelected(false)
|
||||||
|
} else {
|
||||||
|
onBackClicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BackHandler(onBack = internalOnBackPressed)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -688,27 +685,25 @@ fun MangaScreenLargeImpl(
|
|||||||
MangaToolbar(
|
MangaToolbar(
|
||||||
modifier = Modifier.onSizeChanged { topBarHeight = it.height },
|
modifier = Modifier.onSizeChanged { topBarHeight = it.height },
|
||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
|
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
|
||||||
|
backgroundAlphaProvider = { 1f },
|
||||||
hasFilters = state.filterActive,
|
hasFilters = state.filterActive,
|
||||||
navigateUp = navigateUp,
|
onBackClicked = internalOnBackPressed,
|
||||||
onClickFilter = onFilterButtonClicked,
|
onClickFilter = onFilterButtonClicked,
|
||||||
onClickShare = onShareClicked,
|
onClickShare = onShareClicked,
|
||||||
onClickDownload = onDownloadActionClicked,
|
onClickDownload = onDownloadActionClicked,
|
||||||
onClickEditCategory = onEditCategoryClicked,
|
onClickEditCategory = onEditCategoryClicked,
|
||||||
onClickRefresh = onRefresh,
|
onClickRefresh = onRefresh,
|
||||||
onClickMigrate = onMigrateClicked,
|
onClickMigrate = onMigrateClicked,
|
||||||
onClickEditNotes = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
||||||
onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow },
|
onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow },
|
||||||
onClickMergedSettings = onMergedSettingsClicked.takeIf { state.manga.source == MERGED_SOURCE_ID },
|
onClickMergedSettings = onMergedSettingsClicked.takeIf { state.manga.source == MERGED_SOURCE_ID },
|
||||||
onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow },
|
onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow },
|
||||||
// SY <--
|
// SY <--
|
||||||
onCancelActionMode = { onAllChapterSelected(false) },
|
|
||||||
actionModeCounter = selectedChapterCount,
|
actionModeCounter = selectedChapterCount,
|
||||||
onSelectAll = { onAllChapterSelected(true) },
|
onSelectAll = { onAllChapterSelected(true) },
|
||||||
onInvertSelection = { onInvertSelection() },
|
onInvertSelection = { onInvertSelection() },
|
||||||
titleAlphaProvider = { 1f },
|
|
||||||
backgroundAlphaProvider = { 1f },
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
@ -753,7 +748,7 @@ fun MangaScreenLargeImpl(
|
|||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
expanded = chapterListState.shouldExpandFAB(),
|
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -761,7 +756,7 @@ fun MangaScreenLargeImpl(
|
|||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !isAnySelected,
|
enabled = { !isAnySelected },
|
||||||
indicatorPadding = PaddingValues(
|
indicatorPadding = PaddingValues(
|
||||||
start = insetPadding.calculateStartPadding(layoutDirection),
|
start = insetPadding.calculateStartPadding(layoutDirection),
|
||||||
top = with(density) { topBarHeight.toDp() },
|
top = with(density) { topBarHeight.toDp() },
|
||||||
@ -782,9 +777,13 @@ fun MangaScreenLargeImpl(
|
|||||||
MangaInfoBox(
|
MangaInfoBox(
|
||||||
isTabletUi = true,
|
isTabletUi = true,
|
||||||
appBarPadding = contentPadding.calculateTopPadding(),
|
appBarPadding = contentPadding.calculateTopPadding(),
|
||||||
manga = state.manga,
|
title = state.manga.title,
|
||||||
|
author = state.manga.author,
|
||||||
|
artist = state.manga.artist,
|
||||||
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
|
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
|
||||||
isStubSource = remember { state.source is StubSource },
|
isStubSource = remember { state.source is StubSource },
|
||||||
|
coverDataProvider = { state.manga },
|
||||||
|
status = state.manga.status,
|
||||||
onCoverClick = onCoverClicked,
|
onCoverClick = onCoverClicked,
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
)
|
)
|
||||||
@ -815,10 +814,8 @@ fun MangaScreenLargeImpl(
|
|||||||
defaultExpandState = true,
|
defaultExpandState = true,
|
||||||
description = state.manga.description,
|
description = state.manga.description,
|
||||||
tagsProvider = { state.manga.genre },
|
tagsProvider = { state.manga.genre },
|
||||||
notes = state.manga.notes,
|
|
||||||
onTagSearch = onTagSearch,
|
onTagSearch = onTagSearch,
|
||||||
onCopyTagToClipboard = onCopyTagToClipboard,
|
onCopyTagToClipboard = onCopyTagToClipboard,
|
||||||
onEditNotes = onEditNotesClicked,
|
|
||||||
// SY -->
|
// SY -->
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
|
||||||
@ -835,12 +832,11 @@ fun MangaScreenLargeImpl(
|
|||||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.pagePreviewsState !is PagePreviewState.Unused && previewsRowCount > 0) {
|
if (state.pagePreviewsState !is PagePreviewState.Unused) {
|
||||||
PagePreviews(
|
PagePreviews(
|
||||||
pagePreviewState = state.pagePreviewsState,
|
pagePreviewState = state.pagePreviewsState,
|
||||||
onOpenPage = onOpenPagePreview,
|
onOpenPage = onOpenPagePreview,
|
||||||
onMorePreviewsClicked = onMorePreviewsClicked,
|
onMorePreviewsClicked = onMorePreviewsClicked,
|
||||||
rowCount = previewsRowCount,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -876,7 +872,6 @@ fun MangaScreenLargeImpl(
|
|||||||
|
|
||||||
sharedChapterItems(
|
sharedChapterItems(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
mergedData = state.mergedData,
|
|
||||||
chapters = listItem,
|
chapters = listItem,
|
||||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
@ -941,7 +936,6 @@ private fun SharedMangaBottomActionMenu(
|
|||||||
|
|
||||||
private fun LazyListScope.sharedChapterItems(
|
private fun LazyListScope.sharedChapterItems(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
mergedData: MergedMangaData?,
|
|
||||||
chapters: List<ChapterList>,
|
chapters: List<ChapterList>,
|
||||||
isAnyChapterSelected: Boolean,
|
isAnyChapterSelected: Boolean,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
@ -985,17 +979,14 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
?.let {
|
?.let {
|
||||||
// SY -->
|
// SY -->
|
||||||
if (manga.isEhBasedManga()) {
|
if (manga.isEhBasedManga()) {
|
||||||
MetadataUtil.EX_DATE_FORMAT
|
MetadataUtil.EX_DATE_FORMAT.format(Date(it))
|
||||||
.format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault()))
|
|
||||||
} else {
|
} else {
|
||||||
relativeDateText(item.chapter.dateUpload)
|
relativeDateText(Date(item.chapter.dateUpload))
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
},
|
},
|
||||||
readProgress = item.chapter.lastPageRead
|
readProgress = item.chapter.lastPageRead
|
||||||
.takeIf {
|
.takeIf { /* SY --> */(!item.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L }
|
||||||
/* SY --> */(!item.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L
|
|
||||||
}
|
|
||||||
?.let {
|
?.let {
|
||||||
stringResource(
|
stringResource(
|
||||||
MR.strings.chapter_progress,
|
MR.strings.chapter_progress,
|
||||||
@ -1011,8 +1002,7 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
read = item.chapter.read,
|
read = item.chapter.read,
|
||||||
bookmark = item.chapter.bookmark,
|
bookmark = item.chapter.bookmark,
|
||||||
selected = item.selected,
|
selected = item.selected,
|
||||||
downloadIndicatorEnabled =
|
downloadIndicatorEnabled = !isAnyChapterSelected && !manga.isLocal(),
|
||||||
!isAnyChapterSelected && !(mergedData?.manga?.get(item.chapter.mangaId) ?: manga).isLocal(),
|
|
||||||
downloadStateProvider = { item.downloadState },
|
downloadStateProvider = { item.downloadState },
|
||||||
downloadProgressProvider = { item.downloadProgress },
|
downloadProgressProvider = { item.downloadProgress },
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.manga.components
|
|||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
@ -9,13 +10,13 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.outlined.ArrowDownward
|
import androidx.compose.material.icons.outlined.ArrowDownward
|
||||||
import androidx.compose.material.icons.outlined.ErrorOutline
|
import androidx.compose.material.icons.outlined.ErrorOutline
|
||||||
|
import androidx.compose.material.ripple
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.ripple
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -23,9 +24,8 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.StrokeCap
|
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@ -91,7 +91,6 @@ private fun NotDownloadedIndicator(
|
|||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
hapticFeedback = LocalHapticFeedback.current,
|
|
||||||
onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
|
onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
|
||||||
onClick = { onClick(ChapterDownloadAction.START) },
|
onClick = { onClick(ChapterDownloadAction.START) },
|
||||||
)
|
)
|
||||||
@ -121,7 +120,6 @@ private fun DownloadingIndicator(
|
|||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
hapticFeedback = LocalHapticFeedback.current,
|
|
||||||
onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
|
onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
|
||||||
onClick = { isMenuExpanded = true },
|
onClick = { isMenuExpanded = true },
|
||||||
),
|
),
|
||||||
@ -138,8 +136,6 @@ private fun DownloadingIndicator(
|
|||||||
modifier = IndicatorModifier,
|
modifier = IndicatorModifier,
|
||||||
color = strokeColor,
|
color = strokeColor,
|
||||||
strokeWidth = IndicatorStrokeWidth,
|
strokeWidth = IndicatorStrokeWidth,
|
||||||
trackColor = Color.Transparent,
|
|
||||||
strokeCap = StrokeCap.Butt,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val animatedProgress by animateFloatAsState(
|
val animatedProgress by animateFloatAsState(
|
||||||
@ -156,9 +152,6 @@ private fun DownloadingIndicator(
|
|||||||
modifier = IndicatorModifier,
|
modifier = IndicatorModifier,
|
||||||
color = strokeColor,
|
color = strokeColor,
|
||||||
strokeWidth = IndicatorSize / 2,
|
strokeWidth = IndicatorSize / 2,
|
||||||
trackColor = Color.Transparent,
|
|
||||||
strokeCap = StrokeCap.Butt,
|
|
||||||
gapSize = 0.dp,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
||||||
@ -198,7 +191,6 @@ private fun DownloadedIndicator(
|
|||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
hapticFeedback = LocalHapticFeedback.current,
|
|
||||||
onLongClick = { isMenuExpanded = true },
|
onLongClick = { isMenuExpanded = true },
|
||||||
onClick = { isMenuExpanded = true },
|
onClick = { isMenuExpanded = true },
|
||||||
),
|
),
|
||||||
@ -233,7 +225,6 @@ private fun ErrorIndicator(
|
|||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
hapticFeedback = LocalHapticFeedback.current,
|
|
||||||
onLongClick = { onClick(ChapterDownloadAction.START) },
|
onLongClick = { onClick(ChapterDownloadAction.START) },
|
||||||
onClick = { onClick(ChapterDownloadAction.START) },
|
onClick = { onClick(ChapterDownloadAction.START) },
|
||||||
),
|
),
|
||||||
@ -250,23 +241,26 @@ private fun ErrorIndicator(
|
|||||||
|
|
||||||
private fun Modifier.commonClickable(
|
private fun Modifier.commonClickable(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
hapticFeedback: HapticFeedback,
|
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) = this.combinedClickable(
|
) = composed {
|
||||||
enabled = enabled,
|
val haptic = LocalHapticFeedback.current
|
||||||
onLongClick = {
|
|
||||||
onLongClick()
|
Modifier.combinedClickable(
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
enabled = enabled,
|
||||||
},
|
onLongClick = {
|
||||||
onClick = onClick,
|
onLongClick()
|
||||||
role = Role.Button,
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
interactionSource = null,
|
},
|
||||||
indication = ripple(
|
onClick = onClick,
|
||||||
bounded = false,
|
role = Role.Button,
|
||||||
radius = IconButtonTokens.StateLayerSize / 2,
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
),
|
indication = ripple(
|
||||||
)
|
bounded = false,
|
||||||
|
radius = IconButtonTokens.StateLayerSize / 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val IndicatorSize = 26.dp
|
private val IndicatorSize = 26.dp
|
||||||
private val IndicatorPadding = 2.dp
|
private val IndicatorPadding = 2.dp
|
||||||
|
@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
@ -60,6 +60,6 @@ private fun MissingChaptersWarning(count: Int) {
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.error.copy(alpha = SECONDARY_ALPHA),
|
color = MaterialTheme.colorScheme.error.copy(alpha = SecondaryItemAlpha),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.animation.fadeIn
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -28,15 +29,16 @@ import androidx.compose.material.icons.outlined.BookmarkRemove
|
|||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.DoneAll
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
import androidx.compose.material.icons.outlined.Download
|
import androidx.compose.material.icons.outlined.Download
|
||||||
|
import androidx.compose.material.icons.outlined.Label
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
import androidx.compose.material.icons.outlined.RemoveDone
|
import androidx.compose.material.icons.outlined.RemoveDone
|
||||||
import androidx.compose.material.icons.outlined.SwapCalls
|
import androidx.compose.material.icons.outlined.SwapCalls
|
||||||
|
import androidx.compose.material.ripple
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.ripple
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
@ -88,7 +90,7 @@ fun MangaBottomActionMenu(
|
|||||||
Surface(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
||||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
tonalElevation = 3.dp,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
|
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
|
||||||
@ -197,7 +199,7 @@ private fun RowScope.Button(
|
|||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.weight(animatedWeight)
|
.weight(animatedWeight)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
interactionSource = null,
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = ripple(bounded = false),
|
indication = ripple(bounded = false),
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
@ -236,9 +238,7 @@ fun LibraryBottomActionMenu(
|
|||||||
// SY -->
|
// SY -->
|
||||||
onClickCleanTitles: (() -> Unit)?,
|
onClickCleanTitles: (() -> Unit)?,
|
||||||
onClickMigrate: (() -> Unit)?,
|
onClickMigrate: (() -> Unit)?,
|
||||||
onClickCollectRecommendations: (() -> Unit)?,
|
|
||||||
onClickAddToMangaDex: (() -> Unit)?,
|
onClickAddToMangaDex: (() -> Unit)?,
|
||||||
onClickResetInfo: (() -> Unit)?,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@ -251,7 +251,7 @@ fun LibraryBottomActionMenu(
|
|||||||
Surface(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
||||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
tonalElevation = 3.dp,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val confirm =
|
val confirm =
|
||||||
@ -267,10 +267,7 @@ fun LibraryBottomActionMenu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SY -->
|
// SY -->
|
||||||
val showOverflow = onClickCleanTitles != null ||
|
val showOverflow = onClickCleanTitles != null || onClickAddToMangaDex != null
|
||||||
onClickAddToMangaDex != null ||
|
|
||||||
onClickResetInfo != null ||
|
|
||||||
onClickCollectRecommendations != null
|
|
||||||
val configuration = LocalConfiguration.current
|
val configuration = LocalConfiguration.current
|
||||||
val moveMarkPrev = remember { !configuration.isTabletUi() }
|
val moveMarkPrev = remember { !configuration.isTabletUi() }
|
||||||
var overFlowOpen by remember { mutableStateOf(false) }
|
var overFlowOpen by remember { mutableStateOf(false) }
|
||||||
@ -361,24 +358,12 @@ fun LibraryBottomActionMenu(
|
|||||||
onClick = onClickMigrate,
|
onClick = onClickMigrate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (onClickCollectRecommendations != null) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(SYMR.strings.rec_search_short)) },
|
|
||||||
onClick = onClickCollectRecommendations,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (onClickAddToMangaDex != null) {
|
if (onClickAddToMangaDex != null) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(SYMR.strings.mangadex_add_to_follows)) },
|
text = { Text(stringResource(SYMR.strings.mangadex_add_to_follows)) },
|
||||||
onClick = onClickAddToMangaDex,
|
onClick = onClickAddToMangaDex,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (onClickResetInfo != null) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = stringResource(SYMR.strings.reset_info)) },
|
|
||||||
onClick = onClickResetInfo,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Button(
|
Button(
|
||||||
|
@ -24,26 +24,36 @@ import androidx.compose.material3.ProvideTextStyle
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.contentColorFor
|
import androidx.compose.material3.contentColorFor
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||||
|
import androidx.compose.ui.platform.ViewConfiguration
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import me.saket.swipe.SwipeableActionsBox
|
import me.saket.swipe.SwipeableActionsBox
|
||||||
|
import me.saket.swipe.rememberSwipeableActionsState
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
||||||
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaChapterListItem(
|
fun MangaChapterListItem(
|
||||||
@ -68,134 +78,158 @@ fun MangaChapterListItem(
|
|||||||
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val start = getSwipeAction(
|
val haptic = LocalHapticFeedback.current
|
||||||
action = chapterSwipeStartAction,
|
val density = LocalDensity.current
|
||||||
read = read,
|
|
||||||
bookmark = bookmark,
|
|
||||||
downloadState = downloadStateProvider(),
|
|
||||||
background = MaterialTheme.colorScheme.primaryContainer,
|
|
||||||
onSwipe = { onChapterSwipe(chapterSwipeStartAction) },
|
|
||||||
)
|
|
||||||
val end = getSwipeAction(
|
|
||||||
action = chapterSwipeEndAction,
|
|
||||||
read = read,
|
|
||||||
bookmark = bookmark,
|
|
||||||
downloadState = downloadStateProvider(),
|
|
||||||
background = MaterialTheme.colorScheme.primaryContainer,
|
|
||||||
onSwipe = { onChapterSwipe(chapterSwipeEndAction) },
|
|
||||||
)
|
|
||||||
|
|
||||||
SwipeableActionsBox(
|
val textAlpha = if (read) ReadItemAlpha else 1f
|
||||||
modifier = Modifier.clipToBounds(),
|
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
|
||||||
startActions = listOfNotNull(start),
|
|
||||||
endActions = listOfNotNull(end),
|
// Increase touch slop of swipe action to reduce accidental trigger
|
||||||
swipeThreshold = swipeActionThreshold,
|
val configuration = LocalViewConfiguration.current
|
||||||
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
|
CompositionLocalProvider(
|
||||||
|
LocalViewConfiguration provides object : ViewConfiguration by configuration {
|
||||||
|
override val touchSlop: Float = configuration.touchSlop * 3f
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Row(
|
val start = getSwipeAction(
|
||||||
modifier = modifier
|
action = chapterSwipeStartAction,
|
||||||
.selectedBackground(selected)
|
read = read,
|
||||||
.combinedClickable(
|
bookmark = bookmark,
|
||||||
onClick = onClick,
|
downloadState = downloadStateProvider(),
|
||||||
onLongClick = onLongClick,
|
background = MaterialTheme.colorScheme.primaryContainer,
|
||||||
)
|
onSwipe = { onChapterSwipe(chapterSwipeStartAction) },
|
||||||
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
|
)
|
||||||
) {
|
val end = getSwipeAction(
|
||||||
Column(
|
action = chapterSwipeEndAction,
|
||||||
modifier = Modifier.weight(1f),
|
read = read,
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
bookmark = bookmark,
|
||||||
) {
|
downloadState = downloadStateProvider(),
|
||||||
Row(
|
background = MaterialTheme.colorScheme.primaryContainer,
|
||||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
onSwipe = { onChapterSwipe(chapterSwipeEndAction) },
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
)
|
||||||
) {
|
|
||||||
var textHeight by remember { mutableIntStateOf(0) }
|
|
||||||
if (!read) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Circle,
|
|
||||||
contentDescription = stringResource(MR.strings.unread),
|
|
||||||
modifier = Modifier
|
|
||||||
.height(8.dp)
|
|
||||||
.padding(end = 4.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (bookmark) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Bookmark,
|
|
||||||
contentDescription = stringResource(MR.strings.action_filter_bookmarked),
|
|
||||||
modifier = Modifier
|
|
||||||
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
onTextLayout = { textHeight = it.size.height },
|
|
||||||
color = LocalContentColor.current.copy(alpha = if (read) DISABLED_ALPHA else 1f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
val swipeableActionsState = rememberSwipeableActionsState()
|
||||||
val subtitleStyle = MaterialTheme.typography.bodySmall
|
LaunchedEffect(Unit) {
|
||||||
.merge(
|
// Haptic effect when swipe over threshold
|
||||||
color = LocalContentColor.current
|
val swipeActionThresholdPx = with(density) { swipeActionThreshold.toPx() }
|
||||||
.copy(alpha = if (read) DISABLED_ALPHA else SECONDARY_ALPHA),
|
snapshotFlow { swipeableActionsState.offset.value.absoluteValue > swipeActionThresholdPx }
|
||||||
)
|
.collect { if (it) haptic.performHapticFeedback(HapticFeedbackType.LongPress) }
|
||||||
ProvideTextStyle(value = subtitleStyle) {
|
}
|
||||||
if (date != null) {
|
|
||||||
Text(
|
SwipeableActionsBox(
|
||||||
text = date,
|
modifier = Modifier.clipToBounds(),
|
||||||
maxLines = 1,
|
state = swipeableActionsState,
|
||||||
overflow = TextOverflow.Ellipsis,
|
startActions = listOfNotNull(start),
|
||||||
|
endActions = listOfNotNull(end),
|
||||||
|
swipeThreshold = swipeActionThreshold,
|
||||||
|
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.selectedBackground(selected)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
)
|
||||||
|
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
var textHeight by remember { mutableIntStateOf(0) }
|
||||||
|
if (!read) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Circle,
|
||||||
|
contentDescription = stringResource(MR.strings.unread),
|
||||||
|
modifier = Modifier
|
||||||
|
.height(8.dp)
|
||||||
|
.padding(end = 4.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
if (readProgress != null ||
|
}
|
||||||
scanlator != null/* SY --> */ ||
|
if (bookmark) {
|
||||||
sourceName != null/* SY <-- */
|
Icon(
|
||||||
) {
|
imageVector = Icons.Filled.Bookmark,
|
||||||
DotSeparatorText()
|
contentDescription = stringResource(MR.strings.action_filter_bookmarked),
|
||||||
|
modifier = Modifier
|
||||||
|
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = LocalContentColor.current.copy(alpha = textAlpha),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
onTextLayout = { textHeight = it.size.height },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
ProvideTextStyle(
|
||||||
|
value = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
if (date != null) {
|
||||||
|
Text(
|
||||||
|
text = date,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText()
|
||||||
|
}
|
||||||
|
// SY -->
|
||||||
|
if (sourceName != null) {
|
||||||
|
Text(
|
||||||
|
text = sourceName,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
if (scanlator != null) DotSeparatorText()
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
if (scanlator != null) {
|
||||||
|
Text(
|
||||||
|
text = scanlator,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (readProgress != null) {
|
|
||||||
Text(
|
|
||||||
text = readProgress,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
|
|
||||||
)
|
|
||||||
if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText()
|
|
||||||
}
|
|
||||||
// SY -->
|
|
||||||
if (sourceName != null) {
|
|
||||||
Text(
|
|
||||||
text = sourceName,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
if (scanlator != null) DotSeparatorText()
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
if (scanlator != null) {
|
|
||||||
Text(
|
|
||||||
text = scanlator,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ChapterDownloadIndicator(
|
ChapterDownloadIndicator(
|
||||||
enabled = downloadIndicatorEnabled,
|
enabled = downloadIndicatorEnabled,
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
downloadStateProvider = downloadStateProvider,
|
downloadStateProvider = downloadStateProvider,
|
||||||
downloadProgressProvider = downloadProgressProvider,
|
downloadProgressProvider = downloadProgressProvider,
|
||||||
onClick = { onDownloadClick?.invoke(it) },
|
onClick = { onDownloadClick?.invoke(it) },
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import androidx.compose.ui.graphics.Shape
|
|||||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import coil3.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user