Merge branch 'main' into main

This commit is contained in:
sdaqo 2024-10-30 14:02:54 +01:00 committed by GitHub
commit b47b3bfdd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
545 changed files with 6868 additions and 5049 deletions

View file

@ -53,7 +53,7 @@ body:
label: Mihon version label: Mihon version
description: You can find your Mihon version in **More → About**. description: You can find your Mihon version in **More → About**.
placeholder: | placeholder: |
Example: "0.16.5" Example: "0.17.0"
validations: validations:
required: true required: true
@ -96,7 +96,7 @@ body:
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 **[0.16.5](https://github.com/mihonapp/mihon/releases/latest)**. - label: I have updated the app to version **[0.17.0](https://github.com/mihonapp/mihon/releases/latest)**.
required: true required: true
- label: I have updated all installed extensions. - label: I have updated all installed extensions.
required: true required: true

View file

@ -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 **[0.16.5](https://github.com/mihonapp/mihon/releases/latest)**. - label: I have updated the app to version **[0.17.0](https://github.com/mihonapp/mihon/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

10
.github/mergify.yml vendored
View file

@ -1,10 +0,0 @@
#pull_request_rules:
# - name: Automatically merge translations
# conditions:
# - "author = weblate"
# - "-conflict"
# - "current-day-of-week = Sat"
# - "created-at < 1 day ago"
# actions:
# merge:
# method: squash

View file

@ -1,26 +1,13 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": ["config:base"],
"config:base"
],
"schedule": ["every friday"],
"labels": ["Dependencies"], "labels": ["Dependencies"],
"semanticCommits": "disabled",
"packageRules": [ "packageRules": [
{ {
"groupName": "Compose BOM", "groupName": "GitHub Actions",
"matchPackageNames": [ "matchManagers": ["github-actions"],
"dev.chrisbanes.compose:compose-bom" "pinDigests": true,
],
"ignoreUnstable": false
},
{
// Compiler plugins are tightly coupled to Kotlin version
"groupName": "Kotlin",
"matchPackagePrefixes": [
"androidx.compose.compiler",
"org.jetbrains.kotlin.",
"org.jetbrains.kotlin:"
],
} }
] ]
} }

View file

@ -1,10 +1,13 @@
name: PR build check name: PR build check
on: on:
pull_request: pull_request:
paths-ignore: paths:
- '**.md' - '**'
- 'i18n/src/commonMain/resources/**/strings.xml' - '!**.md'
- 'i18n/src/commonMain/resources/**/plurals.xml' - '!i18n/src/commonMain/moko-resources/**/strings.xml'
- '!i18n/src/commonMain/moko-resources/**/plurals.xml'
- 'i18n/src/commonMain/moko-resources/base/strings.xml'
- 'i18n/src/commonMain/moko-resources/base/plurals.xml'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }} group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
@ -20,22 +23,34 @@ jobs:
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@216d1ad2b3710bf005dc39237337b9673fd8fcd5 # v3.3.2 uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Dependency Review - name: Dependency Review
uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2 uses: actions/dependency-review-action@a6993e2c61fd5dc440b409aa1d6904921c5e1894 # v4.3.5
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with: with:
java-version: 17 java-version: 17
distribution: adopt distribution: adopt
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Build app and run unit tests - name: Build app and run unit tests
run: ./gradlew detekt assembleStandardRelease testReleaseUnitTest run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
- name: Upload APK
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk
- name: Upload mapping
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/standardRelease

View file

@ -17,26 +17,38 @@ jobs:
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@216d1ad2b3710bf005dc39237337b9673fd8fcd5 # v3.3.2 uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- 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"
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with: with:
java-version: 17 java-version: 17
distribution: adopt distribution: adopt
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Build app and run unit tests - name: Build app and run unit tests
run: ./gradlew detekt assembleStandardRelease testReleaseUnitTest run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
- name: Upload APK
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk
- name: Upload mapping
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/standardRelease
# Sign APK and create release for tags # Sign APK and create release for tags
@ -83,7 +95,7 @@ jobs:
- name: Create Release - name: Create Release
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon' if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4 uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
with: with:
tag_name: ${{ env.VERSION_TAG }} tag_name: ${{ env.VERSION_TAG }}
name: Mihon ${{ env.VERSION_TAG }} name: Mihon ${{ env.VERSION_TAG }}

View file

@ -1,41 +0,0 @@
name: Issue moderator
on:
issues:
types: [opened, edited, reopened]
issue_comment:
types: [created]
jobs:
moderate:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: keiyoushi/issue-moderator-action@a017be83547db6e107431ce7575f53c1dfa3296a
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
duplicate-label: Duplicate
auto-close-rules: |
[
{
"type": "both",
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
"ignoreCase": true,
"message": "Mihon does not support anime, and has no plans to support anime. In addition Mihon is not affiliated with Aniyomi https://github.com/jmir1/aniyomi"
},
{
"type": "both",
"regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?<!n[o']?t )blocked by|error) (?:to )?(?:get past|by ?pass|penetrate)?.*cloud ?fl?are.*",
"ignoreCase": true,
"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."
},
{
"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

23
.gitignore vendored
View file

@ -1,17 +1,16 @@
# Build files
.gradle .gradle
/local.properties .kotlin
/.idea/workspace.xml build
.DS_Store
# IDE files
*.iml
.idea/* .idea/*
!.idea/icon.png !.idea/icon.png
*iml /captures
*.iml
# Built files # Configuration files
*/build local.properties
/build
*.apk
app/**/output.json
# Unnecessary file # macOS specific files
*.swp .DS_Store

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 62 KiB

270
CHANGELOG.md Normal file
View file

@ -0,0 +1,270 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is a modified version of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `Added` - for new features.
- `Changed ` - for changes in existing functionality.
- `Improved` - for enhancement or optimization in existing functionality.
- `Removed` - for now removed features.
- `Fixed` - for any bug fixes.
- `Other` - for technical stuff.
## [Unreleased]
### Fixed
- Fixed "currentTab was used multiple times"
## [v0.17.0] - 2024-10-26
### Added
- Option to disable reader zoom out ([@Splintorien](https://github.com/Splintorien)) ([#302](https://github.com/mihonapp/mihon/pull/302))
- Source name and tracker urls to app generated `ComicInfo.xml` file ([@Shamicen](https://github.com/Shamicen)) ([#459](https://github.com/mihonapp/mihon/pull/459))
- Option to migrate in Duplicate entry dialog ([@sirlag](https://github.com/sirlag)) ([#492](https://github.com/mihonapp/mihon/pull/492))
- Upcoming screen to visualize expected update dates ([@sirlag](https://github.com/sirlag)) ([#420](https://github.com/mihonapp/mihon/pull/420))
- Only show upcoming updates in the future ([@sirlag](https://github.com/sirlag)) ([#606](https://github.com/mihonapp/mihon/pull/606))
- Add Quantity Badge to Upcoming Screen ([@Animeboynz](https://github.com/Animeboynz), [@AntsyLich](https://github.com/AntsyLich)) ([#1250](https://github.com/mihonapp/mihon/pull/1250))
- Crash screen error message to the top of the crash log generated from that screen ([@FooIbar](https://github.com/FooIbar)) ([#742](https://github.com/mihonapp/mihon/pull/742))
- Support for 7Zip and RAR5 archives ([@FooIbar](https://github.com/FooIbar)) ([#949](https://github.com/mihonapp/mihon/pull/949))
- Extra configuration options to e-ink page flashes ([@sirlag](https://github.com/sirlag)) ([#625](https://github.com/mihonapp/mihon/pull/625))
- 8-bit+ AVIF image support ([@WerctFourth](https://github.com/WerctFourth)) ([#971](https://github.com/mihonapp/mihon/pull/971))
- Smart update dialog message when no predicted released date exists ([@Animeboynz](https://github.com/Animeboynz)) ([#977](https://github.com/mihonapp/mihon/pull/977))
- Option to copy reader panel to clipboard ([@Animeboynz](https://github.com/Animeboynz)) ([#1003](https://github.com/mihonapp/mihon/pull/1003))
- Copy Tracker URL option to tracker sheet ([@mm12](https://github.com/mm12)) ([#1101](https://github.com/mihonapp/mihon/pull/1101))
- A button to exclude all scanlators in exclude scanlators dialog ([@AntsyLich](https://github.com/AntsyLich)) ([`84b2164`](https://github.com/mihonapp/mihon/commit/84b2164787a795f3fd757c325cbfb6ef660ac3a3))
- Open in browser option to reader menu ([@mm12](https://github.com/mm12)) ([#1110](https://github.com/mihonapp/mihon/pull/1110))
- Reorder reader menu overflow items ([@AntsyLich](https://github.com/AntsyLich)) ([`788235f`](https://github.com/mihonapp/mihon/commit/788235feeca241228eac0561339dd07b5ea0b77d))
- Option to skip downloading duplicate read chapters ([@shabnix](https://github.com/shabnix)) ([#1125](https://github.com/mihonapp/mihon/pull/1125))
- Add confirmation dialog when adding repo via URI ([@Animeboynz](https://github.com/Animeboynz)) ([#1158](https://github.com/mihonapp/mihon/pull/1158))
- Add "show entry" action to download notifications ([@mm12](https://github.com/mm12), [@AntsyLich](https://github.com/AntsyLich)) ([#1159](https://github.com/mihonapp/mihon/pull/1159))
- Option to update trackers when chapter marked as read ([@Animeboynz](https://github.com/Animeboynz), [@AntsyLich](https://github.com/AntsyLich)) ([#1177](https://github.com/mihonapp/mihon/pull/1177), [#1365](https://github.com/mihonapp/mihon/pull/1365), [#1374](https://github.com/mihonapp/mihon/pull/1374))
- Toast to restart app when User-Agent is changed ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#1204](https://github.com/mihonapp/mihon/pull/1204))
- Added more profile compilation status (p) ([`c8bb78d`](https://github.com/mihonapp/mihon/commit/c8bb78d91afc2824baaca999f0095559c49d1306))
- Add option to opt out of Analytics and Crashlytics ([@Animeboynz](https://github.com/Animeboynz)) ([#1237](https://github.com/mihonapp/mihon/pull/1237))
- Rework Firebase setup ([@AntsyLich](https://github.com/AntsyLich)) ([`15e3f28`](https://github.com/mihonapp/mihon/commit/15e3f28aa36bec3c31f212c572ab57ce960cc862))
- Added random library sort ([@jackhamilton](https://github.com/jackhamilton)) ([#1317](https://github.com/mihonapp/mihon/pull/1317))
- Make sure random library sort is at the bottom ([@AntsyLich](https://github.com/AntsyLich)) ([`2e2c8d3`](https://github.com/mihonapp/mihon/commit/2e2c8d36c1e23bf274c7c19f1242e14b0c7afbc1))
- Confirmation dialog when removing privately installed extensions ([@Animeboynz](https://github.com/Animeboynz), [@AntsyLich](https://github.com/AntsyLich)) ([#1320](https://github.com/mihonapp/mihon/pull/1320))
- Option to backup non-library read entries ([@Animeboynz](https://github.com/Animeboynz), [@jobobby04](https://github.com/jobobby04), [@AntsyLich](https://github.com/AntsyLich)) ([#1324](https://github.com/mihonapp/mihon/pull/1324))
### Changed
- Read archive files from memory instead of temporarily extracting to internal storage ([@FooIbar](https://github.com/FooIbar)) ([#326](https://github.com/mihonapp/mihon/pull/326))
- Fix dual page split ([@FooIbar](https://github.com/FooIbar)) ([#485](https://github.com/mihonapp/mihon/pull/485))
- Bump default user agent ([@AntsyLich](https://github.com/AntsyLich)) ([`8160b47`](https://github.com/mihonapp/mihon/commit/8160b47ff5fbbd9b32caeb462b5be881fabd3449))
- Wait for sources to be initialized before performing source related tasks ([@jobobby04](https://github.com/jobobby04)) ([`a08e03f`](https://github.com/mihonapp/mihon/commit/a08e03f5cbf3f4e6be1de35f97ef8ebb26a1210e))
- Duplicate entry dialog UI ([@sirlag](https://github.com/sirlag)) ([#492](https://github.com/mihonapp/mihon/pull/492))
- Extension trust system
- Store extension repo details from `repo.json` in database ([@sirlag](https://github.com/sirlag)) ([#506](https://github.com/mihonapp/mihon/pull/506))
- Fix extension repo migration not triggering ([@AntsyLich](https://github.com/AntsyLich)) ([`9672ea8`](https://github.com/mihonapp/mihon/commit/9672ea8b1b06f464800e310c96e060ead182f7ca))
- Refactor the ExtensionRepoService to use DTOs ([@MajorTanya](https://github.com/MajorTanya)) ([#573](https://github.com/mihonapp/mihon/pull/573))
- Fix extension repo name is used to construct URL instead of baseUrl ([@MajorTanya](https://github.com/MajorTanya)) ([#572](https://github.com/mihonapp/mihon/pull/572))
- Fix crash with `TypeReference` issue when creating extension repo ([@AntsyLich](https://github.com/AntsyLich)) ([#574](https://github.com/mihonapp/mihon/pull/574), [`e020ae5`](https://github.com/mihonapp/mihon/commit/e020ae5ed558e80742ef0ad8bfa0f69af0959d5a))
- Fix mishap in [`e020ae5`](https://github.com/mihonapp/mihon/commit/e020ae5ed558e80742ef0ad8bfa0f69af0959d5a) ([@AntsyLich](https://github.com/AntsyLich)) ([`6965e59`](https://github.com/mihonapp/mihon/commit/6965e59a643c67a2bf81b3c69ec70268e5da5797))
- Backup and Restore ([@Animeboynz](https://github.com/Animeboynz)) ([#1057](https://github.com/mihonapp/mihon/pull/1057))
- Trust extension by repo ([@AntsyLich](https://github.com/AntsyLich)) ([#570](https://github.com/mihonapp/mihon/pull/570))-
- From M2 ripple to M3 ([@FooIbar](https://github.com/FooIbar)) ([#675](https://github.com/mihonapp/mihon/pull/675))
- Increased continue reading button size ([@AntsyLich](https://github.com/AntsyLich), [@Animeboynz](https://github.com/Animeboynz)) ([`e17f70f`](https://github.com/mihonapp/mihon/commit/e17f70f7226ea031fc1f962c9dfea3e404ba53ad))
- Global search "Has result" choice is now sticky ([@AntsyLich](https://github.com/AntsyLich)) ([`5a61ca5`](https://github.com/mihonapp/mihon/commit/5a61ca5535fe0d9e8e7bcb9e665ba2f9cb0cf649))
- Make category backup/restore not dependant on library backup ([@AntsyLich](https://github.com/AntsyLich)) ([`56fb4f6`](https://github.com/mihonapp/mihon/commit/56fb4f62a152e87a71892aa68c78cac51a2c8596))
- Rename backup restore error log file ([@AntsyLich](https://github.com/AntsyLich)) ([`2858ef8`](https://github.com/mihonapp/mihon/commit/2858ef835fec8d7278b1d0cad1b5664104d1e4b0))
- Keyboard type in add extension repo dialog ([@xbjfk](https://github.com/xbjfk)) ([#764](https://github.com/mihonapp/mihon/pull/764))
- Adjust collapse/open animation on manga description ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`1c16fc7`](https://github.com/mihonapp/mihon/commit/1c16fc79c2ac4c4be30308fed84ffb371dab5902))
- Kitsu domain to `kitsu.app` ([@MajorTanya](https://github.com/MajorTanya)) ([#1106](https://github.com/mihonapp/mihon/pull/1106))
- Respect privacy settings in extension update notification ([@Animeboynz](https://github.com/Animeboynz)) ([#1156](https://github.com/mihonapp/mihon/pull/1156))
- Hide keyboard when a Tracker SearchResultItem is clicked ([@Animeboynz](https://github.com/Animeboynz)) ([#1168](https://github.com/mihonapp/mihon/pull/1168))
- Enable 'Split Tall Images' by default ([@Smol-Ame](https://github.com/Smol-Ame)) ([#1185](https://github.com/mihonapp/mihon/pull/1185))
- Ignore "intent://" urls on webview ([@bapeey](https://github.com/bapeey)) ([#1193](https://github.com/mihonapp/mihon/pull/1193))
- Make reader chapter navigator slightly wider on small screens (p) ([#1202](https://github.com/mihonapp/mihon/pull/1202))
- Re-enable fetching chapters list for entries with licenced status ([@Animeboynz](https://github.com/Animeboynz)) ([#1230](https://github.com/mihonapp/mihon/pull/1230))
- Change casing for Extention Repos String ([@Animeboynz](https://github.com/Animeboynz)) ([#1248](https://github.com/mihonapp/mihon/pull/1248))
- Retain remote last chapter read if it's higher than the local one for EnhancedTracker ([@brewkunz](https://github.com/brewkunz)) ([#1301](https://github.com/mihonapp/mihon/pull/1301))
- Adjust expandable fab animation (p) ([`eb6092b`](https://github.com/mihonapp/mihon/commit/eb6092bd0cfa09694985a8bafdd8bbf2815190a1))
- "Invalidate downloads index" to "Reindex downloads" ([@AntsyLich](https://github.com/AntsyLich)) ([`d2afbfe`](https://github.com/mihonapp/mihon/commit/d2afbfe4ede283076aae40633c79c3f90b4390e7))
### Improved
- Reader performance
- Avoid unnecessary copying when processing reader image ([@FooIbar](https://github.com/FooIbar)) ([#691](https://github.com/mihonapp/mihon/pull/691))
- Significantly improve performance when loading extremely long images in long strip mode ([@FooIbar](https://github.com/FooIbar)) ([#692](https://github.com/mihonapp/mihon/pull/692))
- Use `Bitmap.Config.HARDWARE` if possible to improve image loading speed ([@wwww-wwww](https://github.com/wwww-wwww)) ([#687](https://github.com/mihonapp/mihon/pull/687))
- Improve preloading in long strip mode ([@FooIbar](https://github.com/FooIbar)) ([#1076](https://github.com/mihonapp/mihon/pull/1076))
- Performance when looking up specific files ([@raxod502](https://github.com/raxod502)) ([#728](https://github.com/mihonapp/mihon/pull/728))
- Chapter number parsing ([@Naputt1](https://github.com/Naputt1)) ([`6a80305`](https://github.com/mihonapp/mihon/commit/6a80305d6c572da6c08c0c69f5c25ff26ecf7383))
- Error message on restoring if backup decoding fails ([@vetleledaal](https://github.com/vetleledaal)) ([#1056](https://github.com/mihonapp/mihon/pull/1056))
### Removed
- Legacy download folder names no longer supported ([@AntsyLich](https://github.com/AntsyLich)) ([`e55e5f6`](https://github.com/mihonapp/mihon/commit/e55e5f6f64f872475d370d6ce0c186e2601776e4))
- Remove legacy broken source and history backup ([@AntsyLich](https://github.com/AntsyLich)) ([`518abf0`](https://github.com/mihonapp/mihon/commit/518abf032ccb9bb45d197927be2a5faca4167d29))
- Remove more unnecessary permissions from Firebase dependency ([@AntsyLich](https://github.com/AntsyLich)) ([`02af9b1`](https://github.com/mihonapp/mihon/commit/02af9b1acf9f590d29560bc3fc90d206e8e6e1af))
- Fix mishap in `02af9b1` ([@AntsyLich](https://github.com/AntsyLich)) ([`f22767d`](https://github.com/mihonapp/mihon/commit/f22767d863a0fa001f93f24092cd5ade87350502))
### Fixed
- Extracting `ComicInfo.xml` from local source archives ([@FooIbar](https://github.com/FooIbar)) ([#325](https://github.com/mihonapp/mihon/pull/325))
- Chapter download indicator ([@ivaniskandar](https://github.com/ivaniskandar)) ([`d8b9a9f`](https://github.com/mihonapp/mihon/commit/d8b9a9f593911569ff2bceb49b4f020978d0d2e1))
- Issues with shizuku in a multi user setup ([@Redjard](https://github.com/Redjard)) ([#494](https://github.com/mihonapp/mihon/pull/494))
- Fix reader page image not being decoded until it's visible ([@FooIbar](https://github.com/FooIbar)) ([#563](https://github.com/mihonapp/mihon/pull/563))
- Reader chapter progress slider visuals ([@FooIbar](https://github.com/FooIbar)) ([#674](https://github.com/mihonapp/mihon/pull/674))
- Extension being marked as not installed instead of untrusted after updating with private installer ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
- Extension update counter not updating due to extension being marked as untrusted ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
- `Key "extension-XXX-YYY" was already used` crash ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
- Navigation layout tap zones shifting after zooming out in webtoon readers ([@FooIbar](https://github.com/FooIbar)) ([#767](https://github.com/mihonapp/mihon/pull/767))
- Some extension not loading due to missing classes ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#783](https://github.com/mihonapp/mihon/pull/783))
- Theme colors in accordance to upstream changes ([@CrepeTF](https://github.com/CrepeTF), [@AntsyLich](https://github.com/AntsyLich)) ([#766](https://github.com/mihonapp/mihon/pull/766), [#963](https://github.com/mihonapp/mihon/pull/963), [#976](https://github.com/mihonapp/mihon/pull/976), [9a34ace](https://github.com/mihonapp/mihon/commit/9a34ace09c66274e6c2b3f9446058a0fa99d4bd0))
- Crash when requesting folder access on non-conforming devices ([@mainrs](https://github.com/mainrs)) ([#726](https://github.com/mihonapp/mihon/pull/726))
- Fix unexpected skips in strong skipping mode ([@FooIbar](https://github.com/FooIbar)) ([#940](https://github.com/mihonapp/mihon/pull/940))
- Bugged color for Date/Scanlator in chapter list for read chapters ([@ivaniskandar](https://github.com/ivaniskandar)) ([`15d9992`](https://github.com/mihonapp/mihon/commit/15d999229fcce865001d5fa77d0163e6e80e38db))
- Categories having same `order` after restoring backup ([@Cologler](https://github.com/Cologler)) ([`119bcbf`](https://github.com/mihonapp/mihon/commit/119bcbf8ed2415664922ea77fadf0da1165d1732))
- Filter by "Tracking" temporarily stuck after signing out of tracker ([@AntsyLich](https://github.com/AntsyLich)) ([#987](https://github.com/mihonapp/mihon/pull/987))
- Fix login prompts despite being logged in to trackers in Manga screen ([@AntsyLich](https://github.com/AntsyLich)) ([`cbcd8bd`](https://github.com/mihonapp/mihon/commit/cbcd8bd6682023f728568f2b44da26124618aed7))
- JXL image downloading and loading ([@FooIbar](https://github.com/FooIbar)) ([#993](https://github.com/mihonapp/mihon/pull/993))
- Crash when using `%` in category name ([@Animeboynz](https://github.com/Animeboynz), [@FooIbar](https://github.com/FooIbar)) ([#1030](https://github.com/mihonapp/mihon/pull/1030))
- Fix item disappearing when fast scrolling ([@cuong-tran](https://github.com/cuong-tran)) ([#1035](https://github.com/mihonapp/mihon/pull/1035))
- Library is backed up while being disabled ([@AntsyLich](https://github.com/AntsyLich)) ([`56fb4f6`](https://github.com/mihonapp/mihon/commit/56fb4f62a152e87a71892aa68c78cac51a2c8596))
- Crash on list with only sticky header ([@cuong-tran](https://github.com/cuong-tran)) ([#1083](https://github.com/mihonapp/mihon/pull/1083))
- Crash when trying to clear cookies of some source ([@FooIbar](https://github.com/FooIbar)) ([#1084](https://github.com/mihonapp/mihon/pull/1084))
- MAL search results not showing start dates ([@MajorTanya](https://github.com/MajorTanya)) ([#1098](https://github.com/mihonapp/mihon/pull/1098))
- Android SDK 35 API collision ([@AntsyLich](https://github.com/AntsyLich)) ([`fdb9617`](https://github.com/mihonapp/mihon/commit/fdb96179c6373eb0a8e7d6daea671a315d5ce5f0))
- Manga next update calculation when considering custom fetch interval ([@cuong-tran](https://github.com/cuong-tran)) ([#1206](https://github.com/mihonapp/mihon/pull/1206))
- WheelPicker Manual Input ([@Animeboynz](https://github.com/Animeboynz)) ([#1209](https://github.com/mihonapp/mihon/pull/1209))
- EnhancedTracker not auto binding when adding manga to library ([@brewkunz](https://github.com/brewkunz)) ([#1298](https://github.com/mihonapp/mihon/pull/1298))
- Step count in settings slider ([@abdurisaq](https://github.com/abdurisaq)) ([#1356](https://github.com/mihonapp/mihon/pull/1356))
- Freezing in some screens due to blocking call ([@cuong-tran](https://github.com/cuong-tran)) ([#1364](https://github.com/mihonapp/mihon/pull/1364))
- Crash when removing non-existent tracked entry from tracker ([@cuong-tran](https://github.com/cuong-tran)) ([#1380](https://github.com/mihonapp/mihon/pull/1380))
### Other
- Code cleanup
- Minor refactor of theming when expressions ([@MajorTanya](https://github.com/MajorTanya)) ([#396](https://github.com/mihonapp/mihon/pull/396))
- Inside `WorkerInfoScreen` ([@AntsyLich](https://github.com/AntsyLich)) ([`5aec8f8`](https://github.com/mihonapp/mihon/commit/5aec8f8018236a38106483da08f9cbc28261ac9b))
- Inside `ChapterDownloadIndicator`, `MangaChapterListItem` ([@AntsyLich](https://github.com/AntsyLich)) ([`b7e091d`](https://github.com/mihonapp/mihon/commit/b7e091d5d039e00cababc7daf555280df6cf9c03))
- MangaCoverFetcher ([@ivaniskandar](https://github.com/ivaniskandar)) ([`1365695`](https://github.com/mihonapp/mihon/commit/13656959ae0606736f6ca9eb62699dc23e467c2f))
- Cleanup `LibraryScreenModel` `LibraryMap.applySort` and some more ([@AntsyLich](https://github.com/AntsyLich)) ([`2beb89d`](https://github.com/mihonapp/mihon/commit/2beb89d53163a6d288f8acdebe0f5d26fea8ab3e))
- Address `overridePendingTransition` deprecation ([@MajorTanya](https://github.com/MajorTanya)) ([#410](https://github.com/mihonapp/mihon/pull/410))
- Prioritize extension classes and files over app ([@beer-psi](https://github.com/beer-psi)) ([#433](https://github.com/mihonapp/mihon/pull/433))
- Use compose pager implementation ([@ivaniskandar](https://github.com/ivaniskandar)) ([`84984ef`](https://github.com/mihonapp/mihon/commit/84984ef7e1d7242924120cd2f171cb9dd75bc916))
- Switch to coil3 from coil2 ([@ivaniskandar](https://github.com/ivaniskandar)) ([`f72b6e4`](https://github.com/mihonapp/mihon/commit/f72b6e4d7c1f2f93d705402e4d80c94160bef54d))
- Fix GIF not playing ([@jobobby04](https://github.com/jobobby04)) ([`59bedb3`](https://github.com/mihonapp/mihon/commit/59bedb33ff59ad5db1df2e93567a2266fb63eacc))
- Accommodate db for sync support ([@kaiserbh](https://github.com/kaiserbh)) ([#450](https://github.com/mihonapp/mihon/pull/450))
- Fix webtoon last visible item position calculation ([@FooIbar](https://github.com/FooIbar)) ([#562](https://github.com/mihonapp/mihon/pull/562))
- Migrate from `com.google.accompanist:accompanist-webview` to `io.github.kevinnzou:compose-webview` ([@sirlag](https://github.com/sirlag)) ([#569](https://github.com/mihonapp/mihon/pull/569))
- Rewrite migrations ([@ghostbear](https://github.com/ghostbear)) ([#577](https://github.com/mihonapp/mihon/pull/577))
- Further improve migration ([@ghostbear](https://github.com/ghostbear)) ([#588](https://github.com/mihonapp/mihon/pull/588))
- Fix migrations not running ([@ghostbear](https://github.com/ghostbear)) ([#604](https://github.com/mihonapp/mihon/pull/604))
- Fix MigratorTest after updating to Kotlin 2 ([@cuong-tran](https://github.com/cuong-tran)) ([#896](https://github.com/mihonapp/mihon/pull/896))
- Add MigratorTest to build script ([@cuong-tran](https://github.com/cuong-tran)) ([#896](https://github.com/mihonapp/mihon/pull/896))
- Fix UI freeze after migration ([@AntsyLich](https://github.com/AntsyLich)) ([`3f1d28c`](https://github.com/mihonapp/mihon/commit/3f1d28c3833e6b868152149ed02b3fb8c54eccef))
- Fix some migrations never running ([@MajorTanya](https://github.com/MajorTanya), [@AntsyLich](https://github.com/AntsyLich)) ([#1030](https://github.com/mihonapp/mihon/pull/1030))
- Add ProGuard rule to keep `mihon` namespace classes ([@MajorTanya](https://github.com/MajorTanya)) ([#605](https://github.com/mihonapp/mihon/pull/605))
- Use gradle plugins to share build configuration instead of subprojects ([@AntsyLich](https://github.com/AntsyLich)) ([`e448e40`](https://github.com/mihonapp/mihon/commit/e448e40406e8d9916120a278e42829a6f1b25a7a))
- Remove dependency on compose material 2 components ([@AntsyLich](https://github.com/AntsyLich)) ([`fb94230`](https://github.com/mihonapp/mihon/commit/fb9423028eb017c110cb805f2d0601e5b02e50f9))
- Upload PR build artifacts to GitHub ([@FooIbar](https://github.com/FooIbar)) ([#941](https://github.com/mihonapp/mihon/pull/941))
- Refactor archive support with libarchive ([@FooIbar](https://github.com/FooIbar)) ([#949](https://github.com/mihonapp/mihon/pull/949))
- Add safeguard to prevent ArchiveInputStream from being closed twice ([@null2264](https://github.com/null2264)) ([#967](https://github.com/mihonapp/mihon/pull/967))
- Move archive related code to :core:archive ([@AntsyLich](https://github.com/AntsyLich)) ([`bd7b354`](https://github.com/mihonapp/mihon/commit/bd7b35419861df6d426d6ec0a188391910d0f615))
- Replace detekt with ktlint via spotless ([@AntsyLich](https://github.com/AntsyLich)) ([#1130](https://github.com/mihonapp/mihon/pull/1130), [#1136](https://github.com/mihonapp/mihon/pull/1136), [#1138](https://github.com/mihonapp/mihon/pull/1138))
- Refrain from running spotless on weblate files ([@AntsyLich](https://github.com/AntsyLich)) ([`32d2c2a`](https://github.com/mihonapp/mihon/commit/32d2c2ac1bc224cbda2f09a4023d7d120ea0e954))
- Use feature flags in compose compiler plugin ([@AntsyLich](https://github.com/AntsyLich)) ([`8f9a325`](https://github.com/mihonapp/mihon/commit/8f9a325895bb7b94c2ec92dd969094fc30b3b5e2))- PagerPageHolder: lazy init loading indicator ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`a45eb5e`](https://github.com/mihonapp/mihon/commit/a45eb5e5288159dbbbbb5f92140ce0dd32a8f3ab))
- Collect MangaScreen state with lifecycle ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`03eb756`](https://github.com/mihonapp/mihon/commit/03eb756ecba0692d88d3a76254afc4c157fa225b))
- Add stable marker to Manga data class ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`03eb756`](https://github.com/mihonapp/mihon/commit/03eb756ecba0692d88d3a76254afc4c157fa225b))
- Use DTOs to parse tracking API responses ([@MajorTanya](https://github.com/MajorTanya)) ([#1103](https://github.com/mihonapp/mihon/pull/1103))
- Fix Kitsu ratingTwenty being typed as String ([@MajorTanya](https://github.com/MajorTanya)) ([#1191](https://github.com/mihonapp/mihon/pull/1191))
- Fix Kitsu `synopsis` nullability ([@MajorTanya](https://github.com/MajorTanya)) ([#1233](https://github.com/mihonapp/mihon/pull/1233))
- Fix AniList `ALSearchItem.status` nullibility ([@Secozzi](https://github.com/Secozzi)) ([#1297](https://github.com/mihonapp/mihon/pull/1297))
- Migrate some classpaths to gradle plugins ([@AntsyLich](https://github.com/AntsyLich)) ([`fc1c804`](https://github.com/mihonapp/mihon/commit/fc1c804bfda1d76c0399bbb6214e75b3def951cc))
- Add crashlytics to standard builds ([@AntsyLich](https://github.com/AntsyLich)) ([`3c611b9`](https://github.com/mihonapp/mihon/commit/3c611b95fb79e5ac972019b76c7b24f46a3087fd))
- Switch to stable compose ([@AntsyLich](https://github.com/AntsyLich)) ([`2baffa6`](https://github.com/mihonapp/mihon/commit/2baffa62cade1abd978d5fd03151b47fc87fd31e))
- Switch from inorichi injekt to kohesive Injekt ([@AntsyLich](https://github.com/AntsyLich)) ([#1205](https://github.com/mihonapp/mihon/pull/1205))
- Use custom injekt register with inorichi patch ([@AntsyLich](https://github.com/AntsyLich)) ([`83fd474`](https://github.com/mihonapp/mihon/commit/83fd4746eda1b99f35292b0c2211e606a421b3eb))
- Use TextFieldState in BasicTextField where applicable (p) ([#1201](https://github.com/mihonapp/mihon/pull/1201))
- Bump NDK version ([@AntsyLich](https://github.com/AntsyLich)) ([#1203](https://github.com/mihonapp/mihon/pull/1203))
- Move firebase permission removal to standard flavor ([@AntsyLich](https://github.com/AntsyLich)) ([`be671b4`](https://github.com/mihonapp/mihon/commit/be671b42cefd70180644e01bb065a18cb7701bf9))
- Adjust distinct checker in WidgetManager and run on default dispatcher (p) ([`9b8ab6a`](https://github.com/mihonapp/mihon/commit/9b8ab6acc25a5f99c9c5eebf9cc250975931c57c))
- Update resources exclusion rules (p) ([`481cfed`](https://github.com/mihonapp/mihon/commit/481cfedf08576cecfbb35616837bd8f627d8f959))
- Bump compile sdk to 35 (p) ([`37419cd`](https://github.com/mihonapp/mihon/commit/37419cdc26c2b5c4f8583fc2ba439b08fab42856))
- ChapterNavigator: dispatch page change only when needed (p) ([`f84d9a0`](https://github.com/mihonapp/mihon/commit/f84d9a08b4af768b1e9920c43cc445c86f5427fc))
- Remove usage of deprecated accompanist SystemUiController ([@AntsyLich](https://github.com/AntsyLich)) ([`2ba3f06`](https://github.com/mihonapp/mihon/commit/2ba3f0612c08c7021fed2f6d96cd538da2f34a13))
- Run PR check when base strings are changed ([@AntsyLich](https://github.com/AntsyLich)) ([`4051f18`](https://github.com/mihonapp/mihon/commit/4051f180a2e36e8a2cde6c55f0bea7952fdc4704))
- Fix PR build check ([@AntsyLich](https://github.com/AntsyLich)) ([`9503082`](https://github.com/mihonapp/mihon/commit/9503082d44b5bd868ee1bfc42741dc978d1d9047))
- Cleanup .gitignore files ([@AntsyLich](https://github.com/AntsyLich)) ([`afa5002`](https://github.com/mihonapp/mihon/commit/afa50029882655af8d5eea40aed7644fce4564d8))
- Pass uncaught exception to default handler in GlobalExceptionHandler (so it's reported to crashlytics) ([@AntsyLich](https://github.com/AntsyLich)) ([`f3a2f56`](https://github.com/mihonapp/mihon/commit/f3a2f566c8a09ab862758ae69b43da2a2cd8f1db))
## [v0.16.5] - 2024-04-09
### Added
- Relative date for up to a week in the future ([@sirlag](https://github.com/sirlag)) ([#415](https://github.com/mihonapp/mihon/pull/415))
- Advance setting to install custom color profiles ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
### Changed
- Permanently enable 32-bit color mode ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
### Fixed
- Wrong dates in Updates and History tab due to time zone issues ([@sirlag](https://github.com/sirlag)) ([#402](https://github.com/mihonapp/mihon/pull/402))
- Fix extra date header introduced by parent PR ([@sirlag](https://github.com/sirlag)) ([#415](https://github.com/mihonapp/mihon/pull/415))
- Fix build time in about screen displayed in UTC ([@AntsyLich](https://github.com/AntsyLich)) ([`aed53d3`](https://github.com/mihonapp/mihon/commit/aed53d3bdc85ce0e899fbb90b9f9cad0f1b86480))
- App infinitely retries tracker update instead of failing after 3 tries ([@MajorTanya](https://github.com/MajorTanya)) ([#411](https://github.com/mihonapp/mihon/pull/411))
- Crash on Pixel devices (was introduced due to compose update) ([`ab06720`](https://github.com/mihonapp/mihon/commit/ab067209661eceefc04c65f6bdbfcaa8a1264651))
- Crash when opening some heif/heic images ([@az4521](https://github.com/az4521)) ([#466](https://github.com/mihonapp/mihon/pull/466))
- Crash when putting app in background while track date selection dialog is open ([@ivaniskandar](https://github.com/ivaniskandar)) ([`c348fac`](https://github.com/mihonapp/mihon/commit/c348fac78fac479fb123bd617c01c78b9ca851d5))
- Dates for saved images not following the specification (fixes date issue mainly on Samsung devices) ([@MajorTanya](https://github.com/MajorTanya)) ([#552](https://github.com/mihonapp/mihon/pull/552))
- Colors getting distorted when opening CMYK jpeg images ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
## [v0.16.4] - 2024-02-27
### Changed
- Don't include custom user agent for MAL (circumvents MAL block) ([@AntsyLich](https://github.com/AntsyLich)) ([`085ad8d`](https://github.com/mihonapp/mihon/commit/085ad8d44637c375a8ed24aba3a6f75f5b0cc9ee))
## [v0.16.3] - 2024-01-30
### Added
- Copy extension debug info when clicking logo or name in the extension details screen ([@MajorTanya](https://github.com/MajorTanya)) ([#271](https://github.com/mihonapp/mihon/pull/271))
### Changed
- Hide display cutoff setting in reader settings sheet if fullscreen is disabled ([@Riztard](https://github.com/Riztard)) ([#241](https://github.com/mihonapp/mihon/pull/241))
- Library update error filename to `mihon_update_errors.txt` from `tachiyomi_update_errors.txt` ([@mjishnu](https://github.com/mjishnu)) ([#253](https://github.com/mihonapp/mihon/pull/253))
### Fixed
- Bottom sheet UI issues on non-tablet devices ([@theolm](https://github.com/theolm)) ([#182](https://github.com/mihonapp/mihon/pull/182))
- Crash when switching screen while a list is scrolling ([@theolm](https://github.com/theolm)) ([#272](https://github.com/mihonapp/mihon/pull/272))
- Newly installed extensions not being recognized by Mihon ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#275](https://github.com/mihonapp/mihon/pull/275))
- Failing to refresh MAL token being inferred as token expiration ([@AntsyLich](https://github.com/AntsyLich)) ([`0f4de03`](https://github.com/mihonapp/mihon/commit/0f4de03d7a77b52490dc9a95e96a308b93b26e4f))
### Other
- Add `detekt` (kotlin code analyzer) to the project ([@theolm](https://github.com/theolm)) ([#216](https://github.com/mihonapp/mihon/pull/216))
## [v0.16.2] - 2024-01-28
### Changed
- Backup now contains scanlator filter of a series ([@jobobby04](https://github.com/jobobby04)) ([#166](https://github.com/mihonapp/mihon/pull/166))
- App icon scaling ([@AntsyLich](https://github.com/AntsyLich)) ([`26815c7`](https://github.com/mihonapp/mihon/commit/26815c7356111394665467c1e81255ac9ee33c1a))
- Tracker OAuth client to Mihon's (fixes login issue for Shikimori tracker) ([@AntsyLich](https://github.com/AntsyLich)) ([`e3f33e2`](https://github.com/mihonapp/mihon/commit/e3f33e24f5e928ac8a85d1f500fd42d4715fc6b5))
- Tracker user agents ([@AntsyLich](https://github.com/AntsyLich), [@kitsumed](https://github.com/kitsumed)) ([`e3f33e2`](https://github.com/mihonapp/mihon/commit/e3f33e24f5e928ac8a85d1f500fd42d4715fc6b5))
- Crash log filename to `mihon_crash_logs.txt` from `tachiyomi_crash_logs.txt` ([@MajorTanya](https://github.com/MajorTanya)) ([#234](https://github.com/mihonapp/mihon/pull/234))
- Don't try to refresh MAL token after refresh token expires ([@AntsyLich](https://github.com/AntsyLich)) ([`32188f9`](https://github.com/mihonapp/mihon/commit/32188f9f65009a18250674ef1bd6e57d351c1fba))
### Fixed
- "Flash screen on page change" making the screen full black ([@AntsyLich](https://github.com/AntsyLich)) ([`38d6ab8`](https://github.com/mihonapp/mihon/commit/38d6ab80ce868707829dbc81de4170afe3c2f2a5))
- Faulty MangaUpdates score in database ([@AntsyLich](https://github.com/AntsyLich) ([`a024218`](https://github.com/mihonapp/mihon/commit/a024218410953a389b8af4880fa7ae6cc30124a2)
- Updating extension not reflecting correctly ([@AntsyLich](https://github.com/AntsyLich)) ([`cb06898`](https://github.com/mihonapp/mihon/commit/cb068984303f811692531bf6f14902ae118d8ac7))
- Inconsistent button height in "Data and storage" for some languages ([@theolm](https://github.com/theolm)) ([#202](https://github.com/mihonapp/mihon/pull/202))
- Chapter not being marked as read locally when refreshing Enhanced Trackers ([@Secozzi](https://github.com/Secozzi)) ([#219](https://github.com/mihonapp/mihon/pull/219))
### Other
- Make `last_modified_at` field in database be `0` on insert ([@kaiserbh](https://github.com/kaiserbh)) ([#113](https://github.com/mihonapp/mihon/pull/113))
- Remove usage of `.not()` where possible in code ([@AntsyLich](https://github.com/AntsyLich)) ([`3940740`](https://github.com/mihonapp/mihon/commit/39407407f282dbb7fa972b12053c26b3e3bd66d8))
- Use type-safe project accessors ([@theolm](https://github.com/theolm)) ([#194](https://github.com/mihonapp/mihon/pull/194))
- Legacy tracker model properties now has the same type as the domain ones ([@AntsyLich](https://github.com/AntsyLich)) ([#245](https://github.com/mihonapp/mihon/pull/245))
## [v0.16.1] - 2024-01-18
### Changed
- Branding to Mihon (for references we missed) ([@AntsyLich](https://github.com/AntsyLich)) ([`6539406`](https://github.com/mihonapp/mihon/commit/653940613d661eb371aab3b3c3a8181e4e308c43))
- Preview builds are now called Beta builds ([@AntsyLich](https://github.com/AntsyLich)) ([`3c3a1cd`](https://github.com/mihonapp/mihon/commit/3c3a1cd448ab1f653ddd12b2afe0cba38968d1b9))
### Fixed
- App icon not following the [specification](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive) ([@AntsyLich](https://github.com/AntsyLich)) ([`1849715`](https://github.com/mihonapp/mihon/commit/18497154183356bb0d469b27827f9f7d6b7a3130))
- MangaUpdates default score being set to -1.0 ([@AntsyLich](https://github.com/AntsyLich)) ([`99fd273`](https://github.com/mihonapp/mihon/commit/99fd2731f5d9d374700e89fa67d4d5bf611bbafa))
## [v0.16.0] - 2024-01-16
### Changed
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.17.0...main
[v0.17.0]: https://github.com/mihonapp/mihon/compare/v0.16.5...v0.17.0
[v0.16.5]: https://github.com/mihonapp/mihon/compare/v0.16.4...v0.16.5
[v0.16.4]: https://github.com/mihonapp/mihon/compare/v0.16.3...v0.16.4
[v0.16.3]: https://github.com/mihonapp/mihon/compare/v0.16.2...v0.16.3
[v0.16.2]: https://github.com/mihonapp/mihon/compare/v0.16.1...v0.16.2
[v0.16.1]: https://github.com/mihonapp/mihon/compare/v0.16.0...v0.16.1
[v0.16.0]: https://github.com/mihonapp/mihon/compare/a9c7cbf...v0.16.0

View file

@ -24,10 +24,6 @@ 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/mihon) for online help and to ask questions while developing.

View file

@ -29,7 +29,7 @@ Discover and read manga, webtoons, comics, and more easier than ever on your
* Local reading of content. * Local reading of 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.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support. * Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support.
* Categories to organize your library. * 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.
@ -49,8 +49,8 @@ Before reporting a new issue, take a look at the [FAQ](https://mihon.app/docs/fa
### Repositories ### Repositories
[![mihonapp/website - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=website&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true)](https://github.com/mihonapp/website/) [![mihonapp/website - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=website&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true&description_lines_count=2)](https://github.com/mihonapp/website/)
[![mihonapp/bitmap.kt - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=bitmap.kt&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true)](https://github.com/mihonapp/bitmap.kt/) [![mihonapp/bitmap.kt - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=bitmap.kt&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true&description_lines_count=2)](https://github.com/mihonapp/bitmap.kt/)
### Credits ### Credits

3
app/.gitignore vendored
View file

@ -1,3 +0,0 @@
/build
*iml
*.iml

View file

@ -6,18 +6,21 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
id("mihon.android.application") id("mihon.android.application")
id("mihon.android.application.compose") id("mihon.android.application.compose")
id("com.mikepenz.aboutlibraries.plugin")
id("com.github.zellius.shortcut-helper") id("com.github.zellius.shortcut-helper")
kotlin("plugin.serialization") kotlin("plugin.serialization")
alias(libs.plugins.aboutLibraries)
} }
if (gradle.startParameter.taskRequests.toString().contains("Standard")) { if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
apply<com.google.gms.googleservices.GoogleServicesPlugin>() pluginManager.apply {
apply(libs.plugins.google.services.get().pluginId)
apply(libs.plugins.firebase.crashlytics.get().pluginId)
}
} }
shortcutHelper.setFilePath("./shortcuts.xml") shortcutHelper.setFilePath("./shortcuts.xml")
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
android { android {
namespace = "eu.kanade.tachiyomi" namespace = "eu.kanade.tachiyomi"
@ -25,8 +28,8 @@ android {
defaultConfig { defaultConfig {
applicationId = "app.mihon" applicationId = "app.mihon"
versionCode = 7 versionCode = 8
versionName = "0.16.5" versionName = "0.17.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@ -35,7 +38,7 @@ android {
buildConfigField("boolean", "PREVIEW", "false") buildConfigField("boolean", "PREVIEW", "false")
ndk { ndk {
abiFilters += SUPPORTED_ABIS abiFilters += supportedAbis
} }
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@ -45,7 +48,7 @@ android {
abi { abi {
isEnable = true isEnable = true
reset() reset()
include(*SUPPORTED_ABIS.toTypedArray()) include(*supportedAbis.toTypedArray())
isUniversalApk = true isUniversalApk = true
} }
} }
@ -105,13 +108,16 @@ 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/*.kotlin_module", "META-INF/*.version",
), ),
) )
} }
@ -138,6 +144,7 @@ android {
dependencies { dependencies {
implementation(projects.i18n) implementation(projects.i18n)
implementation(projects.core.archive)
implementation(projects.core.common) implementation(projects.core.common)
implementation(projects.coreMetadata) implementation(projects.coreMetadata)
implementation(projects.sourceApi) implementation(projects.sourceApi)
@ -151,14 +158,14 @@ dependencies {
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.systemuicontroller)
implementation(androidx.interpolator)
implementation(androidx.paging.runtime) implementation(androidx.paging.runtime)
implementation(androidx.paging.compose) implementation(androidx.paging.compose)
@ -204,13 +211,12 @@ dependencies {
// Disk // Disk
implementation(libs.disklrucache) implementation(libs.disklrucache)
implementation(libs.unifile) implementation(libs.unifile)
implementation(libs.bundles.archive)
// Preferences // Preferences
implementation(libs.preferencektx) implementation(libs.preferencektx)
// Dependency injection // Dependency injection
implementation(libs.injekt.core) implementation(libs.injekt)
// Image loading // Image loading
implementation(platform(libs.coil.bom)) implementation(platform(libs.coil.bom))
@ -236,12 +242,13 @@ dependencies {
implementation(libs.compose.webview) implementation(libs.compose.webview)
implementation(libs.compose.grid) implementation(libs.compose.grid)
// Logging // Logging
implementation(libs.logcat) implementation(libs.logcat)
// Crash reports/analytics // Crash reports/analytics
"standardImplementation"(platform(libs.firebase.bom))
"standardImplementation"(libs.firebase.analytics) "standardImplementation"(libs.firebase.analytics)
"standardImplementation"(libs.firebase.crashlytics)
// Shizuku // Shizuku
implementation(libs.bundles.shizuku) implementation(libs.bundles.shizuku)
@ -275,7 +282,7 @@ androidComponents {
tasks { tasks {
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers) // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> { withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += listOf( compilerOptions.freeCompilerArgs.addAll(
"-Xcontext-receivers", "-Xcontext-receivers",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi", "-opt-in=androidx.compose.material.ExperimentalMaterialApi",

View file

@ -44,6 +44,10 @@
-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
@ -73,9 +77,6 @@
# XmlUtil # XmlUtil
-keep public enum nl.adaptivity.xmlutil.EventType { *; } -keep public enum nl.adaptivity.xmlutil.EventType { *; }
# Apache Commons Compress
-keep class * extends org.apache.commons.compress.archivers.zip.ZipExtraField { <init>(); }
# Firebase # Firebase
-keep class com.google.firebase.installations.** { *; } -keep class com.google.firebase.installations.** { *; }
-keep interface com.google.firebase.installations.** { *; } -keep interface com.google.firebase.installations.** { *; }

View file

@ -0,0 +1,11 @@
package mihon.core.firebase
import android.content.Context
object FirebaseConfig {
fun init(context: Context) = Unit
fun setAnalyticsEnabled(enabled: Boolean) = Unit
fun setCrashlyticsEnabled(enabled: Boolean) = Unit
}

View file

@ -33,11 +33,6 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<!-- Remove permission from Firebase dependency -->
<uses-permission
android:name="com.google.android.gms.permission.AD_ID"
tools:node="remove" />
<application <application
android:name=".App" android:name=".App"
android:allowBackup="false" android:allowBackup="false"
@ -236,11 +231,6 @@
android:name="android.webkit.WebView.MetricsOptOut" android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" /> android:value="true" />
<!-- Disable advertising ID collection for Firebase -->
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />
</application> </application>
</manifest> </manifest>

View file

@ -5,7 +5,7 @@ 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: (T?, T?) -> R?, generator: (before: T?, after: T?) -> R?,
): List<R> { ): List<R> {
if (isEmpty()) return emptyList() if (isEmpty()) return emptyList()
val newList = mutableListOf<R>() val newList = mutableListOf<R>()
@ -19,6 +19,24 @@ 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)

View file

@ -24,6 +24,7 @@ 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 mihon.data.repository.ExtensionRepoRepositoryImpl import mihon.data.repository.ExtensionRepoRepositoryImpl
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
import mihon.domain.extensionrepo.interactor.GetExtensionRepo import mihon.domain.extensionrepo.interactor.GetExtensionRepo
@ -152,6 +153,7 @@ class DomainModule : InjektModule {
addFactory { ShouldUpdateDbChapter() } addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(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()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) } addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetHistory(get()) } addFactory { GetHistory(get()) }

View file

@ -110,7 +110,10 @@ class SyncChaptersWithSource(
if (shouldUpdateDbChapter.await(dbChapter, chapter)) { if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) && val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
downloadManager.isChapterDownloaded( downloadManager.isChapterDownloaded(
dbChapter.name, dbChapter.scanlator, manga.title, manga.source, dbChapter.name,
dbChapter.scanlator,
manga.title,
manga.source,
) )
if (shouldRenameChapter) { if (shouldRenameChapter) {

View file

@ -50,4 +50,9 @@ class SourcePreferences(
Preference.appStateKey("trusted_extensions"), Preference.appStateKey("trusted_extensions"),
emptySet(), emptySet(),
) )
fun globalSearchFilterState() = preferenceStore.getBoolean(
Preference.appStateKey("has_filters_toggle_state"),
false,
)
} }

View file

@ -5,6 +5,7 @@ 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
@ -14,17 +15,16 @@ 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 {
getTracks.await(manga.id) trackerManager.loggedInTrackers()
.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()!!) insertTrack.await(track.toDomainTrack(idRequired = false)!!)
syncChapterProgressWithTrack.await( syncChapterProgressWithTrack.await(
manga.id, manga.id,
track.toDomainTrack()!!, track.toDomainTrack(idRequired = false)!!,
service, service,
) )
} }

View file

@ -10,6 +10,7 @@ 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,
@ -36,7 +37,8 @@ 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 updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble()) val lastRead = max(remoteTrack.lastChapterRead, localLastRead.toDouble())
val updatedTrack = remoteTrack.copy(lastChapterRead = lastRead)
try { try {
tracker.update(updatedTrack.toDbTrack()) tracker.update(updatedTrack.toDbTrack())

View file

@ -0,0 +1,10 @@
package eu.kanade.domain.track.model
import dev.icerock.moko.resources.StringResource
import tachiyomi.i18n.MR
enum class AutoTrackState(val titleRes: StringResource) {
ALWAYS(MR.strings.lock_always),
ASK(MR.strings.default_category_summary),
NEVER(MR.strings.lock_never),
}

View file

@ -1,9 +1,11 @@
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,
@ -35,4 +37,9 @@ 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,
)
} }

View file

@ -19,7 +19,11 @@ class UiPreferences(
fun appTheme() = preferenceStore.getEnum( fun appTheme() = preferenceStore.getEnum(
"pref_app_theme", "pref_app_theme",
if (DeviceUtil.isDynamicColorAvailable) { AppTheme.MONET } else { AppTheme.DEFAULT }, if (DeviceUtil.isDynamicColorAvailable) {
AppTheme.MONET
} else {
AppTheme.DEFAULT
},
) )
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false) fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)

View file

@ -245,7 +245,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) {
@ -256,7 +256,7 @@ private fun DetailsHeader(
Obsolete: ${extension.isObsolete} Obsolete: ${extension.isObsolete}
Shared: ${extension.isShared} Shared: ${extension.isShared}
Repository: ${extension.repoUrl} Repository: ${extension.repoUrl}
""".trimIndent() """.trimIndent(),
) )
} }
} }

View file

@ -48,6 +48,7 @@ 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
@ -90,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))
@ -187,14 +188,14 @@ private fun ExtensionContent(
} }
ExtensionHeader( ExtensionHeader(
textRes = header.textRes, textRes = header.textRes,
modifier = Modifier.animateItem(), modifier = Modifier.animateItemFastScroll(),
action = action, action = action,
) )
} }
is ExtensionUiModel.Header.Text -> { is ExtensionUiModel.Header.Text -> {
ExtensionHeader( ExtensionHeader(
text = header.text, text = header.text,
modifier = Modifier.animateItem(), modifier = Modifier.animateItemFastScroll(),
) )
} }
} }
@ -212,13 +213,15 @@ private fun ExtensionContent(
}, },
) { item -> ) { item ->
ExtensionItem( ExtensionItem(
modifier = Modifier.animateItem(), modifier = Modifier.animateItemFastScroll(),
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 -> { trustState = it } is Extension.Untrusted -> {
trustState = it
}
} }
}, },
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
@ -240,7 +243,9 @@ private fun ExtensionContent(
onOpenExtension(it) onOpenExtension(it)
} }
} }
is Extension.Untrusted -> { trustState = it } is Extension.Untrusted -> {
trustState = it
}
} }
}, },
) )

View file

@ -10,6 +10,7 @@ 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
@ -68,7 +69,7 @@ private fun SourcesFilterContent(
contentType = "source-filter-header", contentType = "source-filter-header",
) { ) {
SourcesFilterHeader( SourcesFilterHeader(
modifier = Modifier.animateItem(), modifier = Modifier.animateItemFastScroll(),
language = language, language = language,
enabled = enabled, enabled = enabled,
onClickItem = onClickLanguage, onClickItem = onClickLanguage,
@ -81,7 +82,7 @@ private fun SourcesFilterContent(
contentType = { "source-filter-item" }, contentType = { "source-filter-item" },
) { source -> ) { source ->
SourcesFilterItem( SourcesFilterItem(
modifier = Modifier.animateItem(), modifier = Modifier.animateItemFastScroll(),
source = source, source = source,
enabled = "${source.id}" !in state.disabledSources, enabled = "${source.id}" !in state.disabledSources,
onClickItem = onClickSource, onClickItem = onClickSource,

View file

@ -28,7 +28,7 @@ import tachiyomi.domain.source.model.Pin
import tachiyomi.domain.source.model.Source import tachiyomi.domain.source.model.Source
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
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
@ -148,7 +148,7 @@ private fun SourcePinButton(
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary
} else { } else {
MaterialTheme.colorScheme.onBackground.copy( MaterialTheme.colorScheme.onBackground.copy(
alpha = SecondaryItemAlpha, alpha = SECONDARY_ALPHA,
) )
} }
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

View file

@ -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)!!

View file

@ -7,8 +7,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith 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
@ -23,7 +21,6 @@ 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,
) { ) {
@ -31,7 +28,6 @@ fun NavigatorAdaptiveSheet(
screen = screen, screen = screen,
content = { sheetNavigator -> content = { sheetNavigator ->
AdaptiveSheet( AdaptiveSheet(
tonalElevation = tonalElevation,
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator), enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {
@ -73,7 +69,6 @@ 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,
) { ) {
@ -86,7 +81,6 @@ fun AdaptiveSheet(
AdaptiveSheetImpl( AdaptiveSheetImpl(
modifier = modifier, modifier = modifier,
isTabletUi = isTabletUi, isTabletUi = isTabletUi,
tonalElevation = tonalElevation,
enableSwipeDismiss = enableSwipeDismiss, enableSwipeDismiss = enableSwipeDismiss,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {

View file

@ -8,7 +8,6 @@ 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
@ -21,6 +20,7 @@ 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
@ -179,7 +179,7 @@ fun AppBarTitle(
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.basicMarquee( modifier = Modifier.basicMarquee(
delayMillis = 2_000, repeatDelayMillis = 2_000,
), ),
) )
} }
@ -312,7 +312,7 @@ fun SearchToolbar(
visualTransformation = visualTransformation, visualTransformation = visualTransformation,
interactionSource = interactionSource, interactionSource = interactionSource,
decorationBox = { innerTextField -> decorationBox = { innerTextField ->
TextFieldDefaults.TextFieldDecorationBox( TextFieldDefaults.DecorationBox(
value = searchQuery, value = searchQuery,
innerTextField = innerTextField, innerTextField = innerTextField,
enabled = true, enabled = true,
@ -331,6 +331,7 @@ fun SearchToolbar(
), ),
) )
}, },
container = {},
) )
}, },
) )

View file

@ -58,6 +58,7 @@ 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 ->
@ -78,7 +79,7 @@ fun TabbedDialog(
modifier = Modifier.animateContentSize(), modifier = Modifier.animateContentSize(),
state = pagerState, state = pagerState,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
pageContent = { page -> content(page) } pageContent = { page -> content(page) },
) )
} }
} }

View file

@ -7,6 +7,7 @@ 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.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
@ -14,7 +15,6 @@ 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
@ -33,20 +33,13 @@ import tachiyomi.presentation.core.i18n.stringResource
fun TabbedScreen( fun TabbedScreen(
titleRes: StringResource, titleRes: StringResource,
tabs: ImmutableList<TabContent>, tabs: ImmutableList<TabContent>,
startIndex: Int? = null, state: PagerState = rememberPagerState { tabs.size },
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]

View file

@ -18,6 +18,7 @@ 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.persistentListOf import kotlinx.collections.immutable.persistentListOf
import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.model.HistoryWithRelations
@ -113,14 +114,14 @@ private fun HistoryScreenContent(
when (item) { when (item) {
is HistoryUiModel.Header -> { is HistoryUiModel.Header -> {
ListGroupHeader( ListGroupHeader(
modifier = Modifier.animateItem(), modifier = Modifier.animateItemFastScroll(),
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.animateItem(), modifier = Modifier.animateItemFastScroll(),
history = value, history = value,
onClickCover = { onClickCover(value) }, onClickCover = { onClickCover(value) },
onClickResume = { onClickResume(value) }, onClickResume = { onClickResume(value) },

View file

@ -6,9 +6,12 @@ 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.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.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -26,6 +29,7 @@ import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.library.model.sort import tachiyomi.domain.library.model.sort
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
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.SettingsChipRow import tachiyomi.presentation.core.components.SettingsChipRow
@ -125,7 +129,7 @@ private fun ColumnScope.FilterPage(
) )
} }
val trackers = remember { screenModel.trackers } val trackers by screenModel.trackersFlow.collectAsState()
when (trackers.size) { when (trackers.size) {
0 -> { 0 -> {
// No trackers // No trackers
@ -158,26 +162,42 @@ private fun ColumnScope.SortPage(
category: Category?, category: Category?,
screenModel: LibrarySettingsScreenModel, screenModel: LibrarySettingsScreenModel,
) { ) {
val trackers by screenModel.trackersFlow.collectAsState()
val sortingMode = category.sort.type val sortingMode = category.sort.type
val sortDescending = !category.sort.isAscending val sortDescending = !category.sort.isAscending
val trackerSortOption = val options = remember(trackers.isEmpty()) {
if (screenModel.trackers.isEmpty()) { val trackerMeanPair = if (trackers.isNotEmpty()) {
emptyList() MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean
} else { } else {
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean) null
} }
listOfNotNull(
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
MR.strings.action_sort_total to LibrarySort.Type.TotalChapters,
MR.strings.action_sort_last_read to LibrarySort.Type.LastRead,
MR.strings.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
MR.strings.action_sort_unread_count to LibrarySort.Type.UnreadCount,
MR.strings.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
MR.strings.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
MR.strings.action_sort_date_added to LibrarySort.Type.DateAdded,
trackerMeanPair,
MR.strings.action_sort_random to LibrarySort.Type.Random,
)
}
listOf( options.map { (titleRes, mode) ->
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical, if (mode == LibrarySort.Type.Random) {
MR.strings.action_sort_total to LibrarySort.Type.TotalChapters, BaseSortItem(
MR.strings.action_sort_last_read to LibrarySort.Type.LastRead, label = stringResource(titleRes),
MR.strings.action_sort_last_manga_update to LibrarySort.Type.LastUpdate, icon = Icons.Default.Refresh
MR.strings.action_sort_unread_count to LibrarySort.Type.UnreadCount, .takeIf { sortingMode == LibrarySort.Type.Random },
MR.strings.action_sort_latest_chapter to LibrarySort.Type.LatestChapter, onClick = {
MR.strings.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate, screenModel.setSort(category, mode, LibrarySort.Direction.Ascending)
MR.strings.action_sort_date_added to LibrarySort.Type.DateAdded, },
).plus(trackerSortOption).map { (titleRes, mode) -> )
return@map
}
SortItem( SortItem(
label = stringResource(titleRes), label = stringResource(titleRes),
sortDescending = sortDescending.takeIf { sortingMode == mode }, sortDescending = sortDescending.takeIf { sortingMode == mode },

View file

@ -35,6 +35,7 @@ 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
@ -42,19 +43,26 @@ 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 ContinueReadingButtonSize = 28.dp private val ContinueReadingButtonSizeSmall = 28.dp
private val ContinueReadingButtonSizeLarge = 32.dp
private val ContinueReadingButtonIconSizeSmall = 16.dp
private val ContinueReadingButtonIconSizeLarge = 20.dp
private val ContinueReadingButtonGridPadding = 6.dp private val ContinueReadingButtonGridPadding = 6.dp
private val ContinueReadingButtonListSpacing = 8.dp private val ContinueReadingButtonListSpacing = 8.dp
private const val GridSelectedCoverAlpha = 0.76f private const val GRID_SELECTED_COVER_ALPHA = 0.76f
/** /**
* Layout of grid list item with title overlaying the cover. * Layout of grid list item with title overlaying the cover.
@ -62,7 +70,7 @@ private const val GridSelectedCoverAlpha = 0.76f
*/ */
@Composable @Composable
fun MangaCompactGridItem( fun MangaCompactGridItem(
coverData: tachiyomi.domain.manga.model.MangaCover, coverData: MangaCoverModel,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
isSelected: Boolean = false, isSelected: Boolean = false,
@ -82,7 +90,7 @@ fun MangaCompactGridItem(
MangaCover.Book( MangaCover.Book(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha), .alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
data = coverData, data = coverData,
) )
}, },
@ -96,10 +104,12 @@ 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,
) )
} }
}, },
@ -148,11 +158,13 @@ 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,
) )
} }
} }
@ -163,7 +175,7 @@ private fun BoxScope.CoverTextOverlay(
*/ */
@Composable @Composable
fun MangaComfortableGridItem( fun MangaComfortableGridItem(
coverData: tachiyomi.domain.manga.model.MangaCover, coverData: MangaCoverModel,
title: String, title: String,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
@ -185,7 +197,7 @@ fun MangaComfortableGridItem(
MangaCover.Book( MangaCover.Book(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha), .alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
data = coverData, data = coverData,
) )
}, },
@ -194,10 +206,12 @@ 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,
) )
} }
}, },
@ -309,14 +323,14 @@ private fun GridItemSelectable(
private fun Modifier.selectedOutline( private fun Modifier.selectedOutline(
isSelected: Boolean, isSelected: Boolean,
color: Color, color: Color,
) = this then drawBehind { if (isSelected) drawRect(color = color) } ) = drawBehind { if (isSelected) drawRect(color = color) }
/** /**
* Layout of list item. * Layout of list item.
*/ */
@Composable @Composable
fun MangaListItem( fun MangaListItem(
coverData: tachiyomi.domain.manga.model.MangaCover, coverData: MangaCoverModel,
title: String, title: String,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
@ -354,8 +368,10 @@ 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,
) )
} }
} }
@ -363,23 +379,25 @@ 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 = onClickContinueReading, onClick = onClick,
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(16.dp), modifier = Modifier.size(iconSize),
) )
} }
} }

View file

@ -93,7 +93,7 @@ fun LibraryContent(
isRefreshing = false isRefreshing = false
} }
}, },
enabled = { notSelectionMode }, enabled = notSelectionMode,
) { ) {
LibraryPager( LibraryPager(
state = pagerState, state = pagerState,

View file

@ -355,7 +355,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
@ -380,13 +380,9 @@ private fun MangaScreenSmallImpl(
MangaInfoBox( MangaInfoBox(
isTabletUi = false, isTabletUi = false,
appBarPadding = topPadding, appBarPadding = topPadding,
title = state.manga.title, manga = state.manga,
author = state.manga.author,
artist = state.manga.artist,
sourceName = remember { state.source.getNameForMangaInfo() }, sourceName = remember { state.source.getNameForMangaInfo() },
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,
) )
@ -601,7 +597,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() },
@ -622,13 +618,9 @@ fun MangaScreenLargeImpl(
MangaInfoBox( MangaInfoBox(
isTabletUi = true, isTabletUi = true,
appBarPadding = contentPadding.calculateTopPadding(), appBarPadding = contentPadding.calculateTopPadding(),
title = state.manga.title, manga = state.manga,
author = state.manga.author,
artist = state.manga.artist,
sourceName = remember { state.source.getNameForMangaInfo() }, sourceName = remember { state.source.getNameForMangaInfo() },
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,
) )

View file

@ -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.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
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 = SecondaryItemAlpha), color = MaterialTheme.colorScheme.error.copy(alpha = SECONDARY_ALPHA),
) )
} }

View file

@ -81,7 +81,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),
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
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) }
@ -237,7 +237,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),
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false) } val confirm = remember { mutableStateListOf(false, false, false, false, false) }

View file

@ -30,7 +30,6 @@ 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.draw.alpha
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@ -41,8 +40,8 @@ import eu.kanade.tachiyomi.data.download.model.Download
import me.saket.swipe.SwipeableActionsBox import me.saket.swipe.SwipeableActionsBox
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.ReadItemAlpha import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
@ -66,9 +65,6 @@ fun MangaChapterListItem(
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val textAlpha = if (read) ReadItemAlpha else 1f
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
val start = getSwipeAction( val start = getSwipeAction(
action = chapterSwipeStartAction, action = chapterSwipeStartAction,
read = read, read = read,
@ -133,15 +129,20 @@ fun MangaChapterListItem(
Text( Text(
text = title, text = title,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = textAlpha),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height }, onTextLayout = { textHeight = it.size.height },
color = LocalContentColor.current.copy(alpha = if (read) DISABLED_ALPHA else 1f),
) )
} }
Row(modifier = Modifier.alpha(textSubtitleAlpha)) { Row {
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) { val subtitleStyle = MaterialTheme.typography.bodySmall
.merge(
color = LocalContentColor.current
.copy(alpha = if (read) DISABLED_ALPHA else SECONDARY_ALPHA),
)
ProvideTextStyle(value = subtitleStyle) {
if (date != null) { if (date != null) {
Text( Text(
text = date, text = date,
@ -155,7 +156,7 @@ fun MangaChapterListItem(
text = readProgress, text = readProgress,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = LocalContentColor.current.copy(alpha = ReadItemAlpha), color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
) )
if (scanlator != null) DotSeparatorText() if (scanlator != null) DotSeparatorText()
} }

View file

@ -37,6 +37,7 @@ import androidx.compose.ui.viewinterop.AndroidView
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 androidx.core.view.updatePadding import androidx.core.view.updatePadding
import coil3.asDrawable
import coil3.imageLoader import coil3.imageLoader
import coil3.request.CachePolicy import coil3.request.CachePolicy
import coil3.request.ImageRequest import coil3.request.ImageRequest
@ -55,7 +56,7 @@ import tachiyomi.presentation.core.util.clickableNoIndication
@Composable @Composable
fun MangaCoverDialog( fun MangaCoverDialog(
coverDataProvider: () -> Manga, manga: Manga,
isCustomCover: Boolean, isCustomCover: Boolean,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
onShareClick: () -> Unit, onShareClick: () -> Unit,
@ -165,7 +166,7 @@ fun MangaCoverDialog(
}, },
update = { view -> update = { view ->
val request = ImageRequest.Builder(view.context) val request = ImageRequest.Builder(view.context)
.data(coverDataProvider()) .data(manga)
.size(Size.ORIGINAL) .size(Size.ORIGINAL)
.memoryCachePolicy(CachePolicy.DISABLED) .memoryCachePolicy(CachePolicy.DISABLED)
.target { image -> .target { image ->

View file

@ -102,9 +102,12 @@ fun SetIntervalDialog(
), ),
), ),
) )
} else {
Spacer(Modifier.height(MaterialTheme.padding.small)) Text(
stringResource(MR.strings.manga_interval_expected_update_null),
)
} }
Spacer(Modifier.height(MaterialTheme.padding.small))
if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) { if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) {
Text(stringResource(MR.strings.manga_interval_custom_amount)) Text(stringResource(MR.strings.manga_interval_custom_amount))

View file

@ -2,6 +2,7 @@ package eu.kanade.presentation.manga.components
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.animation.graphics.vector.AnimatedImageVector
@ -74,12 +75,15 @@ 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 coil3.compose.AsyncImage import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.TextButton import tachiyomi.presentation.core.components.material.TextButton
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.pluralStringResource
@ -96,13 +100,9 @@ private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTIL
fun MangaInfoBox( fun MangaInfoBox(
isTabletUi: Boolean, isTabletUi: Boolean,
appBarPadding: Dp, appBarPadding: Dp,
title: String, manga: Manga,
author: String?,
artist: String?,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
coverDataProvider: () -> Manga,
status: Long,
onCoverClick: () -> Unit, onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit, doSearch: (query: String, global: Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -114,7 +114,10 @@ fun MangaInfoBox(
MaterialTheme.colorScheme.background, MaterialTheme.colorScheme.background,
) )
AsyncImage( AsyncImage(
model = coverDataProvider(), model = ImageRequest.Builder(LocalContext.current)
.data(manga)
.crossfade(true)
.build(),
contentDescription = null, contentDescription = null,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
@ -134,28 +137,20 @@ fun MangaInfoBox(
if (!isTabletUi) { if (!isTabletUi) {
MangaAndSourceTitlesSmall( MangaAndSourceTitlesSmall(
appBarPadding = appBarPadding, appBarPadding = appBarPadding,
coverDataProvider = coverDataProvider, manga = manga,
onCoverClick = onCoverClick,
title = title,
doSearch = doSearch,
author = author,
artist = artist,
status = status,
sourceName = sourceName, sourceName = sourceName,
isStubSource = isStubSource, isStubSource = isStubSource,
onCoverClick = onCoverClick,
doSearch = doSearch,
) )
} else { } else {
MangaAndSourceTitlesLarge( MangaAndSourceTitlesLarge(
appBarPadding = appBarPadding, appBarPadding = appBarPadding,
coverDataProvider = coverDataProvider, manga = manga,
onCoverClick = onCoverClick,
title = title,
doSearch = doSearch,
author = author,
artist = artist,
status = status,
sourceName = sourceName, sourceName = sourceName,
isStubSource = isStubSource, isStubSource = isStubSource,
onCoverClick = onCoverClick,
doSearch = doSearch,
) )
} }
} }
@ -176,7 +171,7 @@ fun MangaActionRow(
onEditCategory: (() -> Unit)?, onEditCategory: (() -> Unit)?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f) val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = DISABLED_ALPHA)
// TODO: show something better when using custom interval // TODO: show something better when using custom interval
val nextUpdateDays = remember(nextUpdate) { val nextUpdateDays = remember(nextUpdate) {
@ -271,7 +266,8 @@ fun ExpandableMangaDescription(
modifier = Modifier modifier = Modifier
.padding(top = 8.dp) .padding(top = 8.dp)
.padding(vertical = 12.dp) .padding(vertical = 12.dp)
.animateContentSize(), .animateContentSize(animationSpec = spring())
.fillMaxWidth(),
) { ) {
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
var tagSelected by remember { mutableStateOf("") } var tagSelected by remember { mutableStateOf("") }
@ -335,15 +331,11 @@ fun ExpandableMangaDescription(
@Composable @Composable
private fun MangaAndSourceTitlesLarge( private fun MangaAndSourceTitlesLarge(
appBarPadding: Dp, appBarPadding: Dp,
coverDataProvider: () -> Manga, manga: Manga,
onCoverClick: () -> Unit,
title: String,
doSearch: (query: String, global: Boolean) -> Unit,
author: String?,
artist: String?,
status: Long,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit,
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@ -353,19 +345,22 @@ private fun MangaAndSourceTitlesLarge(
) { ) {
MangaCover.Book( MangaCover.Book(
modifier = Modifier.fillMaxWidth(0.65f), modifier = Modifier.fillMaxWidth(0.65f),
data = coverDataProvider(), data = ImageRequest.Builder(LocalContext.current)
.data(manga)
.crossfade(true)
.build(),
contentDescription = stringResource(MR.strings.manga_cover), contentDescription = stringResource(MR.strings.manga_cover),
onClick = onCoverClick, onClick = onCoverClick,
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
MangaContentInfo( MangaContentInfo(
title = title, title = manga.title,
doSearch = doSearch, author = manga.author,
author = author, artist = manga.artist,
artist = artist, status = manga.status,
status = status,
sourceName = sourceName, sourceName = sourceName,
isStubSource = isStubSource, isStubSource = isStubSource,
doSearch = doSearch,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )
} }
@ -374,15 +369,11 @@ private fun MangaAndSourceTitlesLarge(
@Composable @Composable
private fun MangaAndSourceTitlesSmall( private fun MangaAndSourceTitlesSmall(
appBarPadding: Dp, appBarPadding: Dp,
coverDataProvider: () -> Manga, manga: Manga,
onCoverClick: () -> Unit,
title: String,
doSearch: (query: String, global: Boolean) -> Unit,
author: String?,
artist: String?,
status: Long,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -395,7 +386,10 @@ private fun MangaAndSourceTitlesSmall(
modifier = Modifier modifier = Modifier
.sizeIn(maxWidth = 100.dp) .sizeIn(maxWidth = 100.dp)
.align(Alignment.Top), .align(Alignment.Top),
data = coverDataProvider(), data = ImageRequest.Builder(LocalContext.current)
.data(manga)
.crossfade(true)
.build(),
contentDescription = stringResource(MR.strings.manga_cover), contentDescription = stringResource(MR.strings.manga_cover),
onClick = onCoverClick, onClick = onCoverClick,
) )
@ -403,13 +397,13 @@ private fun MangaAndSourceTitlesSmall(
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp),
) { ) {
MangaContentInfo( MangaContentInfo(
title = title, title = manga.title,
doSearch = doSearch, author = manga.author,
author = author, artist = manga.artist,
artist = artist, status = manga.status,
status = status,
sourceName = sourceName, sourceName = sourceName,
isStubSource = isStubSource, isStubSource = isStubSource,
doSearch = doSearch,
) )
} }
} }
@ -418,12 +412,12 @@ private fun MangaAndSourceTitlesSmall(
@Composable @Composable
private fun ColumnScope.MangaContentInfo( private fun ColumnScope.MangaContentInfo(
title: String, title: String,
doSearch: (query: String, global: Boolean) -> Unit,
author: String?, author: String?,
artist: String?, artist: String?,
status: Long, status: Long,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
doSearch: (query: String, global: Boolean) -> Unit,
textAlign: TextAlign? = LocalTextStyle.current.textAlign, textAlign: TextAlign? = LocalTextStyle.current.textAlign,
) { ) {
val context = LocalContext.current val context = LocalContext.current

View file

@ -108,8 +108,14 @@ fun ScanlatorFilterDialog(
} }
} else { } else {
FlowRow { FlowRow {
TextButton(onClick = mutableExcludedScanlators::clear) { if (mutableExcludedScanlators.isEmpty()) {
Text(text = stringResource(MR.strings.action_reset)) TextButton(onClick = { mutableExcludedScanlators.addAll(availableScanlators) }) {
Text(text = stringResource(MR.strings.action_select_all))
}
} else {
TextButton(onClick = mutableExcludedScanlators::clear) {
Text(text = stringResource(MR.strings.action_reset))
}
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) { TextButton(onClick = onDismissRequest) {

View file

@ -14,11 +14,13 @@ import androidx.compose.foundation.layout.Column
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.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
@ -34,13 +36,18 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.injectLazy
internal class PermissionStep : OnboardingStep { internal class PermissionStep : OnboardingStep {
private val privacyPreferences: PrivacyPreferences by injectLazy()
private var notificationGranted by mutableStateOf(false) private var notificationGranted by mutableStateOf(false)
private var batteryGranted by mutableStateOf(false) private var batteryGranted by mutableStateOf(false)
@ -73,7 +80,7 @@ internal class PermissionStep : OnboardingStep {
} }
Column { Column {
PermissionItem( PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_install_apps), title = stringResource(MR.strings.onboarding_permission_install_apps),
subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description), subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description),
granted = installGranted, granted = installGranted,
@ -89,7 +96,7 @@ internal class PermissionStep : OnboardingStep {
// no-op. resulting checks is being done on resume // no-op. resulting checks is being done on resume
}, },
) )
PermissionItem( PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_notifications), title = stringResource(MR.strings.onboarding_permission_notifications),
subtitle = stringResource(MR.strings.onboarding_permission_notifications_description), subtitle = stringResource(MR.strings.onboarding_permission_notifications_description),
granted = notificationGranted, granted = notificationGranted,
@ -97,7 +104,7 @@ internal class PermissionStep : OnboardingStep {
) )
} }
PermissionItem( PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts), title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts),
subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description), subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description),
granted = batteryGranted, granted = batteryGranted,
@ -109,6 +116,29 @@ internal class PermissionStep : OnboardingStep {
context.startActivity(intent) context.startActivity(intent)
}, },
) )
HorizontalDivider(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
val crashlyticsPref = privacyPreferences.crashlytics()
val crashlytics by crashlyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
granted = crashlytics,
onToggleChange = crashlyticsPref::set,
)
val analyticsPref = privacyPreferences.analytics()
val analytics by analyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
granted = analytics,
onToggleChange = analyticsPref::set,
)
} }
} }
@ -127,7 +157,7 @@ internal class PermissionStep : OnboardingStep {
} }
@Composable @Composable
private fun PermissionItem( private fun PermissionCheckbox(
title: String, title: String,
subtitle: String, subtitle: String,
granted: Boolean, granted: Boolean,
@ -157,4 +187,26 @@ internal class PermissionStep : OnboardingStep {
colors = ListItemDefaults.colors(containerColor = Color.Transparent), colors = ListItemDefaults.colors(containerColor = Color.Transparent),
) )
} }
@Composable
private fun PermissionSwitch(
title: String,
subtitle: String,
granted: Boolean,
modifier: Modifier = Modifier,
onToggleChange: (Boolean) -> Unit,
) {
ListItem(
modifier = modifier,
headlineContent = { Text(text = title) },
supportingContent = { Text(text = subtitle) },
trailingContent = {
Switch(
checked = granted,
onCheckedChange = onToggleChange,
)
},
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}
} }

View file

@ -7,12 +7,12 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.InfoWidget import eu.kanade.presentation.more.settings.widget.InfoWidget
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
@ -23,8 +23,6 @@ import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false } val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp } val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp }
@ -156,16 +154,14 @@ internal fun PreferenceItem(
) )
} }
is Preference.PreferenceItem.TrackerPreference -> { is Preference.PreferenceItem.TrackerPreference -> {
val uName by Injekt.get<TrackPreferences>() val isLoggedIn by item.tracker.let { tracker ->
.trackUsername(item.tracker) tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
.collectAsState()
item.tracker.run {
TrackingPreferenceWidget(
tracker = this,
checked = uName.isNotEmpty(),
onClick = { if (isLoggedIn) item.logout() else item.login() },
)
} }
TrackingPreferenceWidget(
tracker = item.tracker,
checked = isLoggedIn,
onClick = { if (isLoggedIn) item.logout() else item.login() },
)
} }
is Preference.PreferenceItem.InfoPreference -> { is Preference.PreferenceItem.InfoPreference -> {
InfoWidget(text = item.title) InfoWidget(text = item.title)

View file

@ -264,6 +264,7 @@ object SettingsAdvancedScreen : SearchableSettings {
try { try {
// OkHttp checks for valid values internally // OkHttp checks for valid values internally
Headers.Builder().add("User-Agent", it) Headers.Builder().add("User-Agent", it)
context.toast(MR.strings.requires_app_restart)
} catch (_: IllegalArgumentException) { } catch (_: IllegalArgumentException) {
context.toast(MR.strings.error_user_agent_string_invalid) context.toast(MR.strings.error_user_agent_string_invalid)
return@EditTextPreference false return@EditTextPreference false
@ -340,7 +341,7 @@ object SettingsAdvancedScreen : SearchableSettings {
chooseColorProfile.launch(arrayOf("*/*")) chooseColorProfile.launch(arrayOf("*/*"))
}, },
), ),
) ),
) )
} }

View file

@ -111,7 +111,17 @@ object SettingsDataScreen : SearchableSettings {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags) // For some reason InkBook devices do not implement the SAF properly. Persistable URI grants do not
// work. However, simply retrieving the URI and using it works fine for these devices. Access is not
// revoked after the app is closed or the device is restarted.
// This also holds for some Samsung devices. Thus, we simply execute inside of a try-catch block and
// ignore the exception if it is thrown.
try {
context.contentResolver.takePersistableUriPermission(uri, flags)
} catch (e: SecurityException) {
logcat(LogPriority.ERROR, e)
context.toast(MR.strings.file_picker_uri_permission_unsupported)
}
UniFile.fromUri(context, uri)?.let { UniFile.fromUri(context, uri)?.let {
storageDirPref.set(it.uri.toString()) storageDirPref.set(it.uri.toString())

View file

@ -15,7 +15,6 @@ import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.download.service.DownloadPreferences
@ -35,7 +34,7 @@ object SettingsDownloadScreen : SearchableSettings {
@Composable @Composable
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetCategories>() } val getCategories = remember { Injekt.get<GetCategories>() }
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() }) val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() } val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
return listOf( return listOf(
@ -120,6 +119,7 @@ object SettingsDownloadScreen : SearchableSettings {
allCategories: List<Category>, allCategories: List<Category>,
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters() val downloadNewChaptersPref = downloadPreferences.downloadNewChapters()
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly()
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories() val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories()
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude() val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude()
@ -152,6 +152,11 @@ object SettingsDownloadScreen : SearchableSettings {
pref = downloadNewChaptersPref, pref = downloadNewChaptersPref,
title = stringResource(MR.strings.pref_download_new), title = stringResource(MR.strings.pref_download_new),
), ),
Preference.PreferenceItem.SwitchPreference(
pref = downloadNewUnreadChaptersOnlyPref,
title = stringResource(MR.strings.pref_download_new_unread_chapters_only),
enabled = downloadNewChapters,
),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.categories), title = stringResource(MR.strings.categories),
subtitle = getCategoriesLabel( subtitle = getCategoriesLabel(

View file

@ -24,7 +24,6 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.ResetCategoryFlags import tachiyomi.domain.category.interactor.ResetCategoryFlags
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
@ -53,7 +52,7 @@ object SettingsLibraryScreen : SearchableSettings {
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetCategories>() } val getCategories = remember { Injekt.get<GetCategories>() }
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() } val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() }) val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
return listOf( return listOf(
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences), getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
@ -71,9 +70,6 @@ object SettingsLibraryScreen : SearchableSettings {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
val defaultCategory by libraryPreferences.defaultCategory().collectAsState()
val selectedCategory = allCategories.find { it.id == defaultCategory.toLong() }
// For default category // For default category
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) + val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
allCategories.fastMap { it.id.toInt() } allCategories.fastMap { it.id.toInt() }
@ -95,7 +91,6 @@ object SettingsLibraryScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.defaultCategory(), pref = libraryPreferences.defaultCategory(),
title = stringResource(MR.strings.default_category), title = stringResource(MR.strings.default_category),
subtitle = selectedCategory?.visualName ?: stringResource(MR.strings.default_category_summary),
entries = ids.zip(labels).toMap().toImmutableMap(), entries = ids.zip(labels).toMap().toImmutableMap(),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(

View file

@ -14,6 +14,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -61,12 +62,8 @@ object SettingsReaderScreen : SearchableSettings {
pref = readerPref.pageTransitions(), pref = readerPref.pageTransitions(),
title = stringResource(MR.strings.pref_page_transitions), title = stringResource(MR.strings.pref_page_transitions),
), ),
Preference.PreferenceItem.SwitchPreference(
pref = readerPref.flashOnPageChange(),
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
getDisplayGroup(readerPreferences = readerPref), getDisplayGroup(readerPreferences = readerPref),
getEInkGroup(readerPreferences = readerPref),
getReadingGroup(readerPreferences = readerPref), getReadingGroup(readerPreferences = readerPref),
getPagedGroup(readerPreferences = readerPref), getPagedGroup(readerPreferences = readerPref),
getWebtoonGroup(readerPreferences = readerPref), getWebtoonGroup(readerPreferences = readerPref),
@ -122,6 +119,65 @@ object SettingsReaderScreen : SearchableSettings {
) )
} }
@Composable
private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val flashPageState by readerPreferences.flashOnPageChange().collectAsState()
val flashMillisPref = readerPreferences.flashDurationMillis()
val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = readerPreferences.flashPageInterval()
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = readerPreferences.flashColor()
return Preference.PreferenceGroup(
title = "E-Ink",
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = readerPreferences.flashOnPageChange(),
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
Preference.PreferenceItem.SliderPreference(
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
min = 1,
max = 15,
title = stringResource(MR.strings.pref_flash_duration),
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onValueChanged = {
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
true
},
enabled = flashPageState,
),
Preference.PreferenceItem.SliderPreference(
value = flashInterval,
min = 1,
max = 10,
title = stringResource(MR.strings.pref_flash_page_interval),
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onValueChanged = {
flashIntervalPref.set(it)
true
},
enabled = flashPageState,
),
Preference.PreferenceItem.ListPreference(
pref = flashColorPref,
title = stringResource(MR.strings.pref_flash_with),
entries = persistentMapOf(
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
ReaderPreferences.FlashColor.WHITE_BLACK
to stringResource(MR.strings.pref_flash_style_white_black),
),
enabled = flashPageState,
),
),
)
}
@Composable @Composable
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
return Preference.PreferenceGroup( return Preference.PreferenceGroup(

View file

@ -13,8 +13,10 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@ -28,11 +30,8 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
@ -43,7 +42,6 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -88,7 +86,7 @@ class SettingsSearchScreen : Screen() {
focusRequester.requestFocus() focusRequester.requestFocus()
} }
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) } val textFieldState = rememberTextFieldState()
Scaffold( Scaffold(
topBar = { topBar = {
Column { Column {
@ -103,20 +101,19 @@ class SettingsSearchScreen : Screen() {
}, },
title = { title = {
BasicTextField( BasicTextField(
value = textFieldValue, state = textFieldState,
onValueChange = { textFieldValue = it },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.focusRequester(focusRequester) .focusRequester(focusRequester)
.runOnEnterKeyPressed(action = focusManager::clearFocus), .runOnEnterKeyPressed(action = focusManager::clearFocus),
textStyle = MaterialTheme.typography.bodyLarge textStyle = MaterialTheme.typography.bodyLarge
.copy(color = MaterialTheme.colorScheme.onSurface), .copy(color = MaterialTheme.colorScheme.onSurface),
singleLine = true, lineLimits = TextFieldLineLimits.SingleLine,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = { focusManager.clearFocus() }), onKeyboardAction = { focusManager.clearFocus() },
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
decorationBox = { decorator = {
if (textFieldValue.text.isEmpty()) { if (textFieldState.text.isEmpty()) {
Text( Text(
text = stringResource(MR.strings.action_search_settings), text = stringResource(MR.strings.action_search_settings),
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
@ -128,8 +125,8 @@ class SettingsSearchScreen : Screen() {
) )
}, },
actions = { actions = {
if (textFieldValue.text.isNotEmpty()) { if (textFieldState.text.isNotEmpty()) {
IconButton(onClick = { textFieldValue = TextFieldValue() }) { IconButton(onClick = { textFieldState.clearText() }) {
Icon( Icon(
imageVector = Icons.Outlined.Close, imageVector = Icons.Outlined.Close,
contentDescription = null, contentDescription = null,
@ -144,7 +141,7 @@ class SettingsSearchScreen : Screen() {
}, },
) { contentPadding -> ) { contentPadding ->
SearchResult( SearchResult(
searchKey = textFieldValue.text, searchKey = textFieldState.text.toString(),
listState = listState, listState = listState,
contentPadding = contentPadding, contentPadding = contentPadding,
) { result -> ) { result ->

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
@ -28,55 +29,91 @@ object SettingsSecurityScreen : SearchableSettings {
@Composable @Composable
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val context = LocalContext.current
val securityPreferences = remember { Injekt.get<SecurityPreferences>() } val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
val authSupported = remember { context.isAuthenticationSupported() } val privacyPreferences = remember { Injekt.get<PrivacyPreferences>() }
return listOf(
getSecurityGroup(securityPreferences),
getFirebaseGroup(privacyPreferences),
)
}
@Composable
private fun getSecurityGroup(
securityPreferences: SecurityPreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
val authSupported = remember { context.isAuthenticationSupported() }
val useAuthPref = securityPreferences.useAuthenticator() val useAuthPref = securityPreferences.useAuthenticator()
val useAuth by useAuthPref.collectAsState() val useAuth by useAuthPref.collectAsState()
return listOf( return Preference.PreferenceGroup(
Preference.PreferenceItem.SwitchPreference( title = stringResource(MR.strings.pref_security),
pref = useAuthPref, preferenceItems = persistentListOf(
title = stringResource(MR.strings.lock_with_biometrics), Preference.PreferenceItem.SwitchPreference(
enabled = authSupported, pref = useAuthPref,
onValueChanged = { title = stringResource(MR.strings.lock_with_biometrics),
(context as FragmentActivity).authenticate( enabled = authSupported,
title = context.stringResource(MR.strings.lock_with_biometrics), onValueChanged = {
) (context as FragmentActivity).authenticate(
}, title = context.stringResource(MR.strings.lock_with_biometrics),
), )
Preference.PreferenceItem.ListPreference( },
pref = securityPreferences.lockAppAfter(), ),
title = stringResource(MR.strings.lock_when_idle), Preference.PreferenceItem.ListPreference(
enabled = authSupported && useAuth, pref = securityPreferences.lockAppAfter(),
entries = LockAfterValues title = stringResource(MR.strings.lock_when_idle),
.associateWith { enabled = authSupported && useAuth,
when (it) { entries = LockAfterValues
-1 -> stringResource(MR.strings.lock_never) .associateWith {
0 -> stringResource(MR.strings.lock_always) when (it) {
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it) -1 -> stringResource(MR.strings.lock_never)
0 -> stringResource(MR.strings.lock_always)
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
}
} }
} .toImmutableMap(),
.toImmutableMap(), onValueChanged = {
onValueChanged = { (context as FragmentActivity).authenticate(
(context as FragmentActivity).authenticate( title = context.stringResource(MR.strings.lock_when_idle),
title = context.stringResource(MR.strings.lock_when_idle), )
) },
}, ),
Preference.PreferenceItem.SwitchPreference(
pref = securityPreferences.hideNotificationContent(),
title = stringResource(MR.strings.hide_notification_content),
),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.secureScreen(),
title = stringResource(MR.strings.secure_screen),
entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
), ),
Preference.PreferenceItem.SwitchPreference( )
pref = securityPreferences.hideNotificationContent(), }
title = stringResource(MR.strings.hide_notification_content),
@Composable
private fun getFirebaseGroup(
privacyPreferences: PrivacyPreferences,
): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_firebase),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = privacyPreferences.crashlytics(),
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
),
Preference.PreferenceItem.SwitchPreference(
pref = privacyPreferences.analytics(),
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.firebase_summary)),
), ),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.secureScreen(),
title = stringResource(MR.strings.secure_screen),
entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
) )
} }
} }

View file

@ -40,6 +40,7 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.data.track.EnhancedTracker import eu.kanade.tachiyomi.data.track.EnhancedTracker
@ -53,6 +54,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentMap
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
@ -85,6 +87,7 @@ object SettingsTrackingScreen : SearchableSettings {
val trackPreferences = remember { Injekt.get<TrackPreferences>() } val trackPreferences = remember { Injekt.get<TrackPreferences>() }
val trackerManager = remember { Injekt.get<TrackerManager>() } val trackerManager = remember { Injekt.get<TrackerManager>() }
val sourceManager = remember { Injekt.get<SourceManager>() } val sourceManager = remember { Injekt.get<SourceManager>() }
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead()
var dialog by remember { mutableStateOf<Any?>(null) } var dialog by remember { mutableStateOf<Any?>(null) }
dialog?.run { dialog?.run {
@ -125,6 +128,13 @@ object SettingsTrackingScreen : SearchableSettings {
pref = trackPreferences.autoUpdateTrack(), pref = trackPreferences.autoUpdateTrack(),
title = stringResource(MR.strings.pref_auto_update_manga_sync), title = stringResource(MR.strings.pref_auto_update_manga_sync),
), ),
Preference.PreferenceItem.ListPreference(
pref = trackPreferences.autoUpdateTrackOnMarkRead(),
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
entries = AutoTrackState.entries
.associateWith { stringResource(it.titleRes) }
.toPersistentMap(),
),
Preference.PreferenceGroup( Preference.PreferenceGroup(
title = stringResource(MR.strings.services), title = stringResource(MR.strings.services),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(

View file

@ -37,7 +37,7 @@ class OpenSourceLicensesScreen : Screen() {
name = it.name, name = it.name,
website = it.website, website = it.website,
license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(), license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
) ),
) )
}, },
) )

View file

@ -8,6 +8,7 @@ import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConfirmDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConflictDialog import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConflictDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog
@ -32,7 +33,7 @@ class ExtensionReposScreen(
val state by screenModel.state.collectAsState() val state by screenModel.state.collectAsState()
LaunchedEffect(url) { LaunchedEffect(url) {
url?.let { screenModel.createRepo(it) } url?.let { screenModel.showDialog(RepoDialog.Confirm(it)) }
} }
if (state is RepoScreenState.Loading) { if (state is RepoScreenState.Loading) {
@ -67,7 +68,6 @@ class ExtensionReposScreen(
repo = dialog.repo, repo = dialog.repo,
) )
} }
is RepoDialog.Conflict -> { is RepoDialog.Conflict -> {
ExtensionRepoConflictDialog( ExtensionRepoConflictDialog(
onDismissRequest = screenModel::dismissDialog, onDismissRequest = screenModel::dismissDialog,
@ -76,6 +76,13 @@ class ExtensionReposScreen(
newRepo = dialog.newRepo, newRepo = dialog.newRepo,
) )
} }
is RepoDialog.Confirm -> {
ExtensionRepoConfirmDialog(
onDismissRequest = screenModel::dismissDialog,
onCreate = { screenModel.createRepo(dialog.url) },
repo = dialog.url,
)
}
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {

View file

@ -125,6 +125,7 @@ sealed class RepoDialog {
data object Create : RepoDialog() data object Create : RepoDialog()
data class Delete(val repo: String) : RepoDialog() data class Delete(val repo: String) : RepoDialog()
data class Conflict(val oldRepo: ExtensionRepo, val newRepo: ExtensionRepo) : RepoDialog() data class Conflict(val oldRepo: ExtensionRepo, val newRepo: ExtensionRepo) : RepoDialog()
data class Confirm(val url: String) : RepoDialog()
} }
sealed class RepoScreenState { sealed class RepoScreenState {

View file

@ -152,3 +152,35 @@ fun ExtensionRepoConflictDialog(
}, },
) )
} }
@Composable
fun ExtensionRepoConfirmDialog(
onDismissRequest: () -> Unit,
onCreate: () -> Unit,
repo: String,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(MR.strings.action_add_repo))
},
text = {
Text(text = stringResource(MR.strings.add_repo_confirmation, repo))
},
confirmButton = {
TextButton(
onClick = {
onCreate()
onDismissRequest()
},
) {
Text(text = stringResource(MR.strings.action_add))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
)
}

View file

@ -6,7 +6,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -68,7 +67,7 @@ class CreateBackupScreen : Screen() {
LazyColumnWithAction( LazyColumnWithAction(
contentPadding = contentPadding, contentPadding = contentPadding,
actionLabel = stringResource(MR.strings.action_create), actionLabel = stringResource(MR.strings.action_create),
actionEnabled = state.options.anyEnabled(), actionEnabled = state.options.canCreate(),
onClickAction = { onClickAction = {
if (!BackupCreateJob.isManualJobRunning(context)) { if (!BackupCreateJob.isManualJobRunning(context)) {
try { try {
@ -103,7 +102,7 @@ class CreateBackupScreen : Screen() {
} }
@Composable @Composable
private fun ColumnScope.Options( private fun Options(
options: ImmutableList<BackupOptions.Entry>, options: ImmutableList<BackupOptions.Entry>,
state: CreateBackupScreenModel.State, state: CreateBackupScreenModel.State,
model: CreateBackupScreenModel, model: CreateBackupScreenModel,

View file

@ -63,7 +63,7 @@ class RestoreBackupScreen(
LazyColumnWithAction( LazyColumnWithAction(
contentPadding = contentPadding, contentPadding = contentPadding,
actionLabel = stringResource(MR.strings.action_restore), actionLabel = stringResource(MR.strings.action_restore),
actionEnabled = state.canRestore && state.options.anyEnabled(), actionEnabled = state.canRestore && state.options.canRestore(),
onClickAction = { onClickAction = {
model.startRestore() model.startRestore()
navigator.pop() navigator.pop()

View file

@ -28,7 +28,7 @@ import tachiyomi.presentation.core.i18n.stringResource
class BackupSchemaScreen : Screen() { class BackupSchemaScreen : Screen() {
companion object { companion object {
const val title = "Backup file schema" const val TITLE = "Backup file schema"
} }
@Composable @Composable
@ -41,7 +41,7 @@ class BackupSchemaScreen : Screen() {
Scaffold( Scaffold(
topBar = { topBar = {
AppBar( AppBar(
title = title, title = TITLE,
navigateUp = navigator::pop, navigateUp = navigator::pop,
actions = { actions = {
AppBarActions( AppBarActions(
@ -50,7 +50,7 @@ class BackupSchemaScreen : Screen() {
title = stringResource(MR.strings.action_copy_to_clipboard), title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Default.ContentCopy, icon = Icons.Default.ContentCopy,
onClick = { onClick = {
context.copyToClipboard(title, schema) context.copyToClipboard(TITLE, schema)
}, },
), ),
), ),

View file

@ -31,11 +31,11 @@ class DebugInfoScreen : Screen() {
itemsProvider = { itemsProvider = {
listOf( listOf(
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = WorkerInfoScreen.title, title = WorkerInfoScreen.TITLE,
onClick = { navigator.push(WorkerInfoScreen()) }, onClick = { navigator.push(WorkerInfoScreen()) },
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = BackupSchemaScreen.title, title = BackupSchemaScreen.TITLE,
onClick = { navigator.push(BackupSchemaScreen()) }, onClick = { navigator.push(BackupSchemaScreen()) },
), ),
getAppInfoGroup(), getAppInfoGroup(),
@ -78,7 +78,7 @@ class DebugInfoScreen : Screen() {
val status by produceState(initialValue = "-") { val status by produceState(initialValue = "-") {
val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode
value = when (result) { value = when (result) {
ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed" ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE_INSTALLED -> "No profile installed"
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled" ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING -> ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
"Compiled non-matching" "Compiled non-matching"
@ -88,6 +88,7 @@ class DebugInfoScreen : Screen() {
-> "Error $result" -> "Error $result"
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION -> "Not supported" ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION -> "Not supported"
ProfileVerifier.CompilationStatus.RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION -> "Pending compilation" ProfileVerifier.CompilationStatus.RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION -> "Pending compilation"
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED -> "No profile embedded"
else -> "Unknown code $result" else -> "Unknown code $result"
} }
} }

View file

@ -49,7 +49,7 @@ import java.time.ZoneId
class WorkerInfoScreen : Screen() { class WorkerInfoScreen : Screen() {
companion object { companion object {
const val title = "Worker info" const val TITLE = "Worker info"
} }
@Composable @Composable
@ -65,7 +65,7 @@ class WorkerInfoScreen : Screen() {
Scaffold( Scaffold(
topBar = { topBar = {
AppBar( AppBar(
title = title, title = TITLE,
navigateUp = navigator::pop, navigateUp = navigator::pop,
actions = { actions = {
AppBarActions( AppBarActions(
@ -74,7 +74,7 @@ class WorkerInfoScreen : Screen() {
title = stringResource(MR.strings.action_copy_to_clipboard), title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Default.ContentCopy, icon = Icons.Default.ContentCopy,
onClick = { onClick = {
context.copyToClipboard(title, enqueued + finished + running) context.copyToClipboard(TITLE, enqueued + finished + running)
}, },
), ),
), ),
@ -159,7 +159,7 @@ class WorkerInfoScreen : Screen() {
Injekt.get<UiPreferences>().dateFormat().get(), Injekt.get<UiPreferences>().dateFormat().get(),
), ),
) )
appendLine("Next scheduled run: $timestamp",) appendLine("Next scheduled run: $timestamp")
appendLine("Attempt #${workInfo.runAttemptCount + 1}") appendLine("Attempt #${workInfo.runAttemptCount + 1}")
} }
appendLine() appendLine()

View file

@ -223,13 +223,12 @@ fun AppThemePreviewItem(
contentAlignment = Alignment.BottomCenter, contentAlignment = Alignment.BottomCenter,
) { ) {
Surface( Surface(
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainer,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.height(32.dp) .height(32.dp)
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {

View file

@ -32,7 +32,9 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
private enum class State { private enum class State {
CHECKED, INVERSED, UNCHECKED CHECKED,
INVERSED,
UNCHECKED,
} }
@Composable @Composable

View file

@ -15,7 +15,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
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
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
@Composable @Composable
@ -73,7 +73,7 @@ private fun RowScope.BaseStatsItem(
style = subtitleStyle style = subtitleStyle
.copy( .copy(
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
.copy(alpha = SecondaryItemAlpha), .copy(alpha = SECONDARY_ALPHA),
), ),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )

View file

@ -226,7 +226,7 @@ private fun ChapterText(
Text( Text(
text = buildAnnotatedString { text = buildAnnotatedString {
if (downloaded) { if (downloaded) {
appendInlineContent(DownloadedIconContentId) appendInlineContent(DOWNLOADED_ICON_ID)
append(' ') append(' ')
} }
append(name) append(name)
@ -236,7 +236,7 @@ private fun ChapterText(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
inlineContent = persistentMapOf( inlineContent = persistentMapOf(
DownloadedIconContentId to InlineTextContent( DOWNLOADED_ICON_ID to InlineTextContent(
Placeholder( Placeholder(
width = 22.sp, width = 22.sp,
height = 22.sp, height = 22.sp,
@ -273,7 +273,7 @@ private val CardColor: CardColors
) )
private val VerticalSpacerSize = 24.dp private val VerticalSpacerSize = 24.dp
private const val DownloadedIconContentId = "downloaded" private const val DOWNLOADED_ICON_ID = "downloaded"
private fun previewChapter(name: String, scanlator: String, chapterNumber: Double) = Chapter.create().copy( private fun previewChapter(name: String, scanlator: String, chapterNumber: Double) = Chapter.create().copy(
id = 0L, id = 0L,

View file

@ -7,19 +7,42 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
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.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.milliseconds
@Stable @Stable
class DisplayRefreshHost { class DisplayRefreshHost {
internal var currentDisplayRefresh by mutableStateOf(false) internal var currentDisplayRefresh by mutableStateOf(false)
private val readerPreferences = Injekt.get<ReaderPreferences>()
internal val flashMillis = readerPreferences.flashDurationMillis()
internal val flashMode = readerPreferences.flashColor()
internal val flashIntervalPref = readerPreferences.flashPageInterval()
// Internal State for Flash
private var flashInterval = flashIntervalPref.get()
private var timesCalled = 0
fun flash() { fun flash() {
currentDisplayRefresh = true if (timesCalled % flashInterval == 0) {
currentDisplayRefresh = true
}
timesCalled += 1
}
fun setInterval(interval: Int) {
flashInterval = interval
timesCalled = 0
} }
} }
@ -29,18 +52,39 @@ fun DisplayRefreshHost(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val currentDisplayRefresh = hostState.currentDisplayRefresh val currentDisplayRefresh = hostState.currentDisplayRefresh
val refreshDuration by hostState.flashMillis.collectAsState()
val flashMode by hostState.flashMode.collectAsState()
val flashInterval by hostState.flashIntervalPref.collectAsState()
var currentColor by remember { mutableStateOf<Color?>(null) }
LaunchedEffect(currentDisplayRefresh) { LaunchedEffect(currentDisplayRefresh) {
if (currentDisplayRefresh) { if (!currentDisplayRefresh) {
delay(1.5.seconds) currentColor = null
hostState.currentDisplayRefresh = false return@LaunchedEffect
} }
val refreshDurationHalf = refreshDuration.milliseconds / 2
currentColor = if (flashMode == ReaderPreferences.FlashColor.BLACK) {
Color.Black
} else {
Color.White
}
delay(refreshDurationHalf)
if (flashMode == ReaderPreferences.FlashColor.WHITE_BLACK) {
currentColor = Color.Black
}
delay(refreshDurationHalf)
hostState.currentDisplayRefresh = false
}
LaunchedEffect(flashInterval) {
hostState.setInterval(flashInterval)
} }
Canvas( Canvas(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
) { ) {
if (currentDisplayRefresh) { currentColor?.let { drawRect(it) }
drawRect(Color.Black)
}
} }
} }

View file

@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
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.ContentCopy
import androidx.compose.material.icons.outlined.Photo import androidx.compose.material.icons.outlined.Photo
import androidx.compose.material.icons.outlined.Save import androidx.compose.material.icons.outlined.Save
import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.outlined.Share
@ -28,14 +29,12 @@ import tachiyomi.presentation.core.i18n.stringResource
fun ReaderPageActionsDialog( fun ReaderPageActionsDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onSetAsCover: () -> Unit, onSetAsCover: () -> Unit,
onShare: () -> Unit, onShare: (Boolean) -> Unit,
onSave: () -> Unit, onSave: () -> Unit,
) { ) {
var showSetCoverDialog by remember { mutableStateOf(false) } var showSetCoverDialog by remember { mutableStateOf(false) }
AdaptiveSheet( AdaptiveSheet(onDismissRequest = onDismissRequest) {
onDismissRequest = onDismissRequest,
) {
Row( Row(
modifier = Modifier.padding(vertical = 16.dp), modifier = Modifier.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
@ -46,12 +45,21 @@ fun ReaderPageActionsDialog(
icon = Icons.Outlined.Photo, icon = Icons.Outlined.Photo,
onClick = { showSetCoverDialog = true }, onClick = { showSetCoverDialog = true },
) )
ActionButton(
modifier = Modifier.weight(1f),
title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Outlined.ContentCopy,
onClick = {
onShare(true)
onDismissRequest()
},
)
ActionButton( ActionButton(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
title = stringResource(MR.strings.action_share), title = stringResource(MR.strings.action_share),
icon = Icons.Outlined.Share, icon = Icons.Outlined.Share,
onClick = { onClick = {
onShare() onShare(false)
onDismissRequest() onDismissRequest()
}, },
) )

View file

@ -46,6 +46,7 @@ fun ReaderAppBars(
bookmarked: Boolean, bookmarked: Boolean,
onToggleBookmarked: () -> Unit, onToggleBookmarked: () -> Unit,
onOpenInWebView: (() -> Unit)?, onOpenInWebView: (() -> Unit)?,
onOpenInBrowser: (() -> Unit)?,
onShare: (() -> Unit)?, onShare: (() -> Unit)?,
viewer: Viewer?, viewer: Viewer?,
@ -55,7 +56,7 @@ fun ReaderAppBars(
enabledPrevious: Boolean, enabledPrevious: Boolean,
currentPage: Int, currentPage: Int,
totalPages: Int, totalPages: Int,
onSliderValueChange: (Int) -> Unit, onPageIndexChange: (Int) -> Unit,
readingMode: ReadingMode, readingMode: ReadingMode,
onClickReadingMode: () -> Unit, onClickReadingMode: () -> Unit,
@ -127,6 +128,14 @@ fun ReaderAppBars(
), ),
) )
} }
onOpenInBrowser?.let {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_open_in_browser),
onClick = it,
),
)
}
onShare?.let { onShare?.let {
add( add(
AppBar.OverflowAction( AppBar.OverflowAction(
@ -167,9 +176,8 @@ fun ReaderAppBars(
enabledPrevious = enabledPrevious, enabledPrevious = enabledPrevious,
currentPage = currentPage, currentPage = currentPage,
totalPages = totalPages, totalPages = totalPages,
onSliderValueChange = onSliderValueChange, onPageIndexChange = onPageIndexChange,
) )
BottomReaderBar( BottomReaderBar(
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
readingMode = readingMode, readingMode = readingMode,

View file

@ -4,6 +4,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row 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.foundation.layout.fillMaxWidth
@ -16,26 +17,30 @@ import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
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.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.presentation.util.isTabletUi import eu.kanade.presentation.util.isTabletUi
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Slider
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import kotlin.math.roundToInt
@Composable @Composable
fun ChapterNavigator( fun ChapterNavigator(
@ -46,10 +51,10 @@ fun ChapterNavigator(
enabledPrevious: Boolean, enabledPrevious: Boolean,
currentPage: Int, currentPage: Int,
totalPages: Int, totalPages: Int,
onSliderValueChange: (Int) -> Unit, onPageIndexChange: (Int) -> Unit,
) { ) {
val isTabletUi = isTabletUi() val isTabletUi = isTabletUi()
val horizontalPadding = if (isTabletUi) 24.dp else 16.dp val horizontalPadding = if (isTabletUi) 24.dp else 8.dp
val layoutDirection = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr val layoutDirection = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
@ -93,7 +98,11 @@ fun ChapterNavigator(
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text(text = currentPage.toString()) Box(contentAlignment = Alignment.CenterEnd) {
Text(text = currentPage.toString())
// Taking up full length so the slider doesn't shift when 'currentPage' length changes
Text(text = totalPages.toString(), color = Color.Transparent)
}
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val sliderDragged by interactionSource.collectIsDraggedAsState() val sliderDragged by interactionSource.collectIsDraggedAsState()
@ -106,11 +115,11 @@ fun ChapterNavigator(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
value = currentPage.toFloat(), value = currentPage,
valueRange = 1f..totalPages.toFloat(), valueRange = 1..totalPages,
steps = totalPages - 2, onValueChange = f@{
onValueChange = { if (it == currentPage) return@f
onSliderValueChange(it.roundToInt() - 1) onPageIndexChange(it - 1)
}, },
interactionSource = interactionSource, interactionSource = interactionSource,
) )
@ -137,3 +146,21 @@ fun ChapterNavigator(
} }
} }
} }
@Preview
@Composable
private fun ChapterNavigatorPreview() {
var currentPage by remember { mutableIntStateOf(1) }
TachiyomiPreviewTheme {
ChapterNavigator(
isRtl = false,
onNextChapter = {},
enabledNext = true,
onPreviousChapter = {},
enabledPrevious = true,
currentPage = currentPage,
totalPages = 10,
onPageIndexChange = { currentPage = (it + 1) },
)
}
}

View file

@ -5,10 +5,13 @@ import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.SettingsChipRow import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
@ -19,9 +22,27 @@ private val themes = listOf(
MR.strings.automatic_background to 3, MR.strings.automatic_background to 3,
) )
private val flashColors = listOf(
MR.strings.pref_flash_style_black to ReaderPreferences.FlashColor.BLACK,
MR.strings.pref_flash_style_white to ReaderPreferences.FlashColor.WHITE,
MR.strings.pref_flash_style_white_black to ReaderPreferences.FlashColor.WHITE_BLACK,
)
@Composable @Composable
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
val readerTheme by screenModel.preferences.readerTheme().collectAsState() val readerTheme by screenModel.preferences.readerTheme().collectAsState()
val flashPageState by screenModel.preferences.flashOnPageChange().collectAsState()
val flashMillisPref = screenModel.preferences.flashDurationMillis()
val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = screenModel.preferences.flashPageInterval()
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = screenModel.preferences.flashColor()
val flashColor by flashColorPref.collectAsState()
SettingsChipRow(MR.strings.pref_reader_theme) { SettingsChipRow(MR.strings.pref_reader_theme) {
themes.map { (labelRes, value) -> themes.map { (labelRes, value) ->
FilterChip( FilterChip(
@ -73,4 +94,33 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
label = stringResource(MR.strings.pref_flash_page), label = stringResource(MR.strings.pref_flash_page),
pref = screenModel.preferences.flashOnPageChange(), pref = screenModel.preferences.flashOnPageChange(),
) )
if (flashPageState) {
SliderItem(
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
label = stringResource(MR.strings.pref_flash_duration),
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
min = 1,
max = 15,
)
SliderItem(
value = flashInterval,
label = stringResource(MR.strings.pref_flash_page_interval),
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onChange = {
flashIntervalPref.set(it)
},
min = 1,
max = 10,
)
SettingsChipRow(MR.strings.pref_flash_with) {
flashColors.map { (labelRes, value) ->
FilterChip(
selected = flashColor == value,
onClick = { flashColorPref.set(value) },
label = { Text(stringResource(labelRes)) },
)
}
}
}
} }

View file

@ -8,6 +8,12 @@ internal abstract class BaseColorScheme {
abstract val darkScheme: ColorScheme abstract val darkScheme: ColorScheme
abstract val lightScheme: ColorScheme abstract val lightScheme: ColorScheme
// Cannot be pure black as there's content scrolling behind it
// https://m3.material.io/components/navigation-bar/guidelines#90615a71-607e-485e-9e09-778bfc080563
private val surfaceContainer = Color(0xFF0C0C0C)
private val surfaceContainerHigh = Color(0xFF131313)
private val surfaceContainerHighest = Color(0xFF1B1B1B)
fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme { fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme {
if (!isDark) return lightScheme if (!isDark) return lightScheme
@ -18,6 +24,12 @@ internal abstract class BaseColorScheme {
onBackground = Color.White, onBackground = Color.White,
surface = Color.Black, surface = Color.Black,
onSurface = Color.White, onSurface = Color.White,
surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget)
surfaceContainerLowest = surfaceContainer,
surfaceContainerLow = surfaceContainer,
surfaceContainer = surfaceContainer, // Navigation bar background
surfaceContainerHigh = surfaceContainerHigh,
surfaceContainerHighest = surfaceContainerHighest,
) )
} }
} }

View file

@ -19,53 +19,77 @@ internal object GreenAppleColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFF7ADB8F), primary = Color(0xFF7ADB8F),
onPrimary = Color(0xFF003915), onPrimary = Color(0xFF003917),
primaryContainer = Color(0xFF005322), primaryContainer = Color(0xFF017737),
onPrimaryContainer = Color(0xFF96F8A9), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF006D2F), secondary = Color(0xFF7ADB8F), // Unread badge
secondary = Color(0xFF7ADB8F), onSecondary = Color(0xFF003917), // Unread badge text
onSecondary = Color(0xFF003915), secondaryContainer = Color(0xFF017737), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF005322), onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selected icon
onSecondaryContainer = Color(0xFF96F8A9), tertiary = Color(0xFFFFB3AC), // Downloaded badge
tertiary = Color(0xFFFFB3AA), onTertiary = Color(0xFF680008), // Downloaded badge text
onTertiary = Color(0xFF680006), tertiaryContainer = Color(0xFFC7282A),
tertiaryContainer = Color(0xFF93000D), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFFFFDAD5), error = Color(0xFFFFB4AB),
background = Color(0xFF1A1C19), onError = Color(0xFF690005),
onBackground = Color(0xFFE1E3DD), errorContainer = Color(0xFF93000A),
surface = Color(0xFF1A1C19), onErrorContainer = Color(0xFFFFDAD6),
onSurface = Color(0xFFE1E3DD), background = Color(0xFF0F1510),
surfaceVariant = Color(0xFF414941), onBackground = Color(0xFFDFE4DB),
onSurfaceVariant = Color(0xFFC1C8BE), surface = Color(0xFF0F1510),
surfaceTint = Color(0xFF7ADB8F), onSurface = Color(0xFFDFE4DB),
inverseSurface = Color(0xFFE1E3DD), surfaceVariant = Color(0xFF3F493F), // Navigation bar background (ThemePrefWidget)
inverseOnSurface = Color(0xFF1A1C19), onSurfaceVariant = Color(0xFFBECABC),
outline = Color(0xFF8B9389), outline = Color(0xFF889487),
outlineVariant = Color(0xFF3F493F),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFFDFE4DB),
inverseOnSurface = Color(0xFF2C322C),
inversePrimary = Color(0xFF006D32),
surfaceDim = Color(0xFF0F1510),
surfaceBright = Color(0xFF353B35),
surfaceContainerLowest = Color(0xFF0A0F0B),
surfaceContainerLow = Color(0xFF181D18),
surfaceContainer = Color(0xFF1C211C), // Navigation bar background
surfaceContainerHigh = Color(0xFF262B26),
surfaceContainerHighest = Color(0xFF313630),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFF006D2F), primary = Color(0xFF005927),
onPrimary = Color(0xFFFFFFFF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFF96F8A9), primaryContainer = Color(0xFF188140),
onPrimaryContainer = Color(0xFF002109), onPrimaryContainer = Color(0xFFFFFFFF),
secondary = Color(0xFF005927), // Unread badge
onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFF97f7a9), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF000000), // Navigation bar selected icon
tertiary = Color(0xFF9D0012), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFFD33131),
onTertiaryContainer = Color(0xFFFFFFFF),
error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFF6FBF2),
onBackground = Color(0xFF181D18),
surface = Color(0xFFF6FBF2),
onSurface = Color(0xFF181D18),
surfaceVariant = Color(0xFFDAE6D7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF3F493F),
outline = Color(0xFF6F7A6E),
outlineVariant = Color(0xFFBECABC),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFF2C322C),
inverseOnSurface = Color(0xFFEDF2E9),
inversePrimary = Color(0xFF7ADB8F), inversePrimary = Color(0xFF7ADB8F),
secondary = Color(0xFF006D2F), surfaceDim = Color(0xFFD6DCD3),
onSecondary = Color(0xFFFFFFFF), surfaceBright = Color(0xFFF6FBF2),
secondaryContainer = Color(0xFF96F8A9), surfaceContainerLowest = Color(0xFFFFFFFF),
onSecondaryContainer = Color(0xFF002109), surfaceContainerLow = Color(0xFFF0F5EC),
tertiary = Color(0xFFB91D22), surfaceContainer = Color(0xFFEAEFE6), // Navigation bar background
onTertiary = Color(0xFFFFFFFF), surfaceContainerHigh = Color(0xFFE4EAE1),
tertiaryContainer = Color(0xFFFFDAD5), surfaceContainerHighest = Color(0xFFDFE4DB),
onTertiaryContainer = Color(0xFF410003),
background = Color(0xFFFBFDF7),
onBackground = Color(0xFF1A1C19),
surface = Color(0xFFFBFDF7),
onSurface = Color(0xFF1A1C19),
surfaceVariant = Color(0xFFDDE5DA),
onSurfaceVariant = Color(0xFF414941),
surfaceTint = Color(0xFF006D2F),
inverseSurface = Color(0xFF2F312E),
inverseOnSurface = Color(0xFFF0F2EC),
outline = Color(0xFF717970),
) )
} }

View file

@ -18,53 +18,77 @@ internal object LavenderColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFFA177FF), primary = Color(0xFFA177FF),
onPrimary = Color(0xFF111129), onPrimary = Color(0xFF3D0090),
primaryContainer = Color(0xFFA177FF), primaryContainer = Color(0xFFA177FF),
onPrimaryContainer = Color(0xFF111129), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF006D2F), secondary = Color(0xFFA177FF), // Unread badge
secondary = Color(0xFFA177FF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
onSecondary = Color(0xFF111129), secondaryContainer = Color(0xFF423271), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFFA177FF), onSecondaryContainer = Color(0xFFA177FF), // Navigation bar selected icon
onSecondaryContainer = Color(0xFF111129), tertiary = Color(0xFFCDBDFF), // Downloaded badge
tertiary = Color(0xFF5E25E1), onTertiary = Color(0xFF360096), // Downloaded badge text
onTertiary = Color(0xFFE8E8E8), tertiaryContainer = Color(0xFF5512D8),
tertiaryContainer = Color(0xFF111129), onTertiaryContainer = Color(0xFFEFE6FF),
onTertiaryContainer = Color(0xFFDEE8FF), error = Color(0xFFFFB4AB),
onError = Color(0xFF690005),
errorContainer = Color(0xFF93000A),
onErrorContainer = Color(0xFFFFDAD6),
background = Color(0xFF111129), background = Color(0xFF111129),
onBackground = Color(0xFFDEE8FF), onBackground = Color(0xFFE7E0EC),
surface = Color(0xFF111129), surface = Color(0xFF111129),
onSurface = Color(0xFFDEE8FF), onSurface = Color(0xFFE7E0EC),
surfaceVariant = Color(0x2CB6B6B6), surfaceVariant = Color(0xFF3D2F6B), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFE8E8E8), onSurfaceVariant = Color(0xFFCBC3D6),
surfaceTint = Color(0xFFA177FF), outline = Color(0xFF958E9F),
inverseSurface = Color(0xFF221247), outlineVariant = Color(0xFF4A4453),
inverseOnSurface = Color(0xFFDEE8FF), scrim = Color(0xFF000000),
outline = Color(0xA8905FFF), inverseSurface = Color(0xFFE7E0EC),
inverseOnSurface = Color(0xFF322F38),
inversePrimary = Color(0xFF6D41C8),
surfaceDim = Color(0xFF111129),
surfaceBright = Color(0xFF3B3841),
surfaceContainerLowest = Color(0xFF15132d),
surfaceContainerLow = Color(0xFF171531),
surfaceContainer = Color(0xFF1D193B), // Navigation bar background
surfaceContainerHigh = Color(0xFF241f41),
surfaceContainerHighest = Color(0xFF282446),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFF7B46AF), primary = Color(0xFF6D41C8),
onPrimary = Color(0xFFEDE2FF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFF7B46AF), primaryContainer = Color(0xFF7B46AF),
onPrimaryContainer = Color(0xFFEDE2FF), onPrimaryContainer = Color(0xFF130038),
inversePrimary = Color(0xFFD6BAFF), secondary = Color(0xFF7B46AF), // Unread badge
secondary = Color(0xFF7B46AF), onSecondary = Color(0xFFEDE2FF), // Unread badge text
onSecondary = Color(0xFFEDE2FF), secondaryContainer = Color(0xFFC9B0E6), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF7B46AF), onSecondaryContainer = Color(0xFF7B46AF), // Navigation bar selector icon
onSecondaryContainer = Color(0xFFEDE2FF), tertiary = Color(0xFFEDE2FF), // Downloaded badge
tertiary = Color(0xFFEDE2FF), onTertiary = Color(0xFF7B46AF), // Downloaded badge text
onTertiary = Color(0xFF7B46AF), tertiaryContainer = Color(0xFF6D3BF0),
tertiaryContainer = Color(0xFFEDE2FF), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFF7B46AF), error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFEDE2FF), background = Color(0xFFEDE2FF),
onBackground = Color(0xFF1B1B22), onBackground = Color(0xFF1D1A22),
surface = Color(0xFFEDE2FF), surface = Color(0xFFEDE2FF),
onSurface = Color(0xFF1B1B22), onSurface = Color(0xFF1D1A22),
surfaceVariant = Color(0xFFB9B0CC), surfaceVariant = Color(0xFFE4D5F8), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xD849454E), onSurfaceVariant = Color(0xFF4A4453),
surfaceTint = Color(0xFF7B46AF), outline = Color(0xFF7B7485),
inverseSurface = Color(0xFF313033), outlineVariant = Color(0xFFCBC3D6),
inverseOnSurface = Color(0xFFF3EFF4), scrim = Color(0xFF000000),
outline = Color(0xFF7B46AF), inverseSurface = Color(0xFF322F38),
inverseOnSurface = Color(0xFFF5EEFA),
inversePrimary = Color(0xFFA177FF),
surfaceDim = Color(0xFFDED7E3),
surfaceBright = Color(0xFFEDE2FF),
surfaceContainerLowest = Color(0xFFDACCEC),
surfaceContainerLow = Color(0xFFDED0F1),
surfaceContainer = Color(0xFFE4D5F8), // Navigation bar background
surfaceContainerHigh = Color(0xFFEADCFD),
surfaceContainerHighest = Color(0xFFEEE2FF),
) )
} }

View file

@ -23,24 +23,29 @@ internal object MidnightDuskColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFBD1C5C), primaryContainer = Color(0xFFBD1C5C),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFF02475), inversePrimary = Color(0xFFF02475),
secondary = Color(0xFFF02475), secondary = Color(0xFFF02475), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFF16151D), // Unread badge text
secondaryContainer = Color(0xFFF02475), secondaryContainer = Color(0xFF66183C), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFFFFFFF), onSecondaryContainer = Color(0xFFF02475), // Navigation bar selector icon
tertiary = Color(0xFF55971C), tertiary = Color(0xFF55971C), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFF16151D), // Downloaded badge text
tertiaryContainer = Color(0xFF386412), tertiaryContainer = Color(0xFF386412),
onTertiaryContainer = Color(0xFFE5E1E5), onTertiaryContainer = Color(0xFFE5E1E5),
background = Color(0xFF16151D), background = Color(0xFF16151D),
onBackground = Color(0xFFE5E1E5), onBackground = Color(0xFFE5E1E5),
surface = Color(0xFF16151D), surface = Color(0xFF16151D),
onSurface = Color(0xFFE5E1E5), onSurface = Color(0xFFE5E1E5),
surfaceVariant = Color(0xFF524346), surfaceVariant = Color(0xFF281624), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD6C1C4), onSurfaceVariant = Color(0xFFD6C1C4),
surfaceTint = Color(0xFFF02475), surfaceTint = Color(0xFFF02475),
inverseSurface = Color(0xFF333043), inverseSurface = Color(0xFF333043),
inverseOnSurface = Color(0xFFFFFFFF), inverseOnSurface = Color(0xFFFFFFFF),
outline = Color(0xFF9F8C8F), outline = Color(0xFF9F8C8F),
surfaceContainerLowest = Color(0xFF221320),
surfaceContainerLow = Color(0xFF251522),
surfaceContainer = Color(0xFF281624), // Navigation bar background
surfaceContainerHigh = Color(0xFF2D1C2A),
surfaceContainerHighest = Color(0xFF2F1F2C),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@ -49,23 +54,28 @@ internal object MidnightDuskColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFD9E1), primaryContainer = Color(0xFFFFD9E1),
onPrimaryContainer = Color(0xFF3F0017), onPrimaryContainer = Color(0xFF3F0017),
inversePrimary = Color(0xFFFFB1C4), inversePrimary = Color(0xFFFFB1C4),
secondary = Color(0xFFBB0054), secondary = Color(0xFFBB0054), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFFFD9E1), secondaryContainer = Color(0xFFEFBAD4), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF3F0017), onSecondaryContainer = Color(0xFFD1377C), // Navigation bar selector icon
tertiary = Color(0xFF006638), tertiary = Color(0xFF006638), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF00894b), tertiaryContainer = Color(0xFF00894b),
onTertiaryContainer = Color(0xFF2D1600), onTertiaryContainer = Color(0xFF2D1600),
background = Color(0xFFFFFBFF), background = Color(0xFFFFFBFF),
onBackground = Color(0xFF1C1B1F), onBackground = Color(0xFF1C1B1F),
surface = Color(0xFFFFFBFF), surface = Color(0xFFFFFBFF),
onSurface = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F),
surfaceVariant = Color(0xFFF3DDE0), surfaceVariant = Color(0xFFF9E6F1), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF524346), onSurfaceVariant = Color(0xFF524346),
surfaceTint = Color(0xFFBB0054), surfaceTint = Color(0xFFBB0054),
inverseSurface = Color(0xFF313033), inverseSurface = Color(0xFF313033),
inverseOnSurface = Color(0xFFF4F0F4), inverseOnSurface = Color(0xFFF4F0F4),
outline = Color(0xFF847376), outline = Color(0xFF847376),
surfaceContainerLowest = Color(0xFFDAC0CD),
surfaceContainerLow = Color(0xFFE8D1DD),
surfaceContainer = Color(0xFFF9E6F1), // Navigation bar background
surfaceContainerHigh = Color(0xFFFCF3F8),
surfaceContainerHighest = Color(0xFFFEF9FC),
) )
} }

View file

@ -17,19 +17,19 @@ internal object NordColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF88C0D0), primaryContainer = Color(0xFF88C0D0),
onPrimaryContainer = Color(0xFF2E3440), onPrimaryContainer = Color(0xFF2E3440),
inversePrimary = Color(0xFF397E91), inversePrimary = Color(0xFF397E91),
secondary = Color(0xFF81A1C1), secondary = Color(0xFF81A1C1), // Unread badge
onSecondary = Color(0xFF2E3440), onSecondary = Color(0xFF2E3440), // Unread badge text
secondaryContainer = Color(0xFF81A1C1), secondaryContainer = Color(0xFF506275), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF2E3440), onSecondaryContainer = Color(0xFF88C0D0), // Navigation bar selector icon
tertiary = Color(0xFF5E81AC), tertiary = Color(0xFF5E81AC), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFF5E81AC), tertiaryContainer = Color(0xFF5E81AC),
onTertiaryContainer = Color(0xFF000000), onTertiaryContainer = Color(0xFF000000),
background = Color(0xFF2E3440), background = Color(0xFF2E3440),
onBackground = Color(0xFFECEFF4), onBackground = Color(0xFFECEFF4),
surface = Color(0xFF3B4252), surface = Color(0xFF2E3440),
onSurface = Color(0xFFECEFF4), onSurface = Color(0xFFECEFF4),
surfaceVariant = Color(0xFF2E3440), surfaceVariant = Color(0xFF414C5C), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFECEFF4), onSurfaceVariant = Color(0xFFECEFF4),
surfaceTint = Color(0xFF88C0D0), surfaceTint = Color(0xFF88C0D0),
inverseSurface = Color(0xFFD8DEE9), inverseSurface = Color(0xFFD8DEE9),
@ -39,6 +39,11 @@ internal object NordColorScheme : BaseColorScheme() {
onError = Color(0xFF2E3440), onError = Color(0xFF2E3440),
errorContainer = Color(0xFFBF616A), errorContainer = Color(0xFFBF616A),
onErrorContainer = Color(0xFF000000), onErrorContainer = Color(0xFF000000),
surfaceContainerLowest = Color(0xFF373F4D),
surfaceContainerLow = Color(0xFF3E4756),
surfaceContainer = Color(0xFF414C5C),
surfaceContainerHigh = Color(0xFF4E5766),
surfaceContainerHighest = Color(0xFF505968), // Navigation bar background
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@ -47,19 +52,19 @@ internal object NordColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF5E81AC), primaryContainer = Color(0xFF5E81AC),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFF8CA8CD), inversePrimary = Color(0xFF8CA8CD),
secondary = Color(0xFF81A1C1), secondary = Color(0xFF81A1C1), // Unread badge
onSecondary = Color(0xFF2E3440), onSecondary = Color(0xFF2E3440), // Unread badge text
secondaryContainer = Color(0xFF81A1C1), secondaryContainer = Color(0xFF91B4D7), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF2E3440), onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon
tertiary = Color(0xFF88C0D0), tertiary = Color(0xFF88C0D0), // Downloaded badge
onTertiary = Color(0xFF2E3440), onTertiary = Color(0xFF2E3440), // Downloaded badge text
tertiaryContainer = Color(0xFF88C0D0), tertiaryContainer = Color(0xFF88C0D0),
onTertiaryContainer = Color(0xFF2E3440), onTertiaryContainer = Color(0xFF2E3440),
background = Color(0xFFECEFF4), background = Color(0xFFECEFF4),
onBackground = Color(0xFF2E3440), onBackground = Color(0xFF2E3440),
surface = Color(0xFFE5E9F0), surface = Color(0xFFE5E9F0),
onSurface = Color(0xFF2E3440), onSurface = Color(0xFF2E3440),
surfaceVariant = Color(0xFFffffff), surfaceVariant = Color(0xFFDAE0EA), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF2E3440), onSurfaceVariant = Color(0xFF2E3440),
surfaceTint = Color(0xFF5E81AC), surfaceTint = Color(0xFF5E81AC),
inverseSurface = Color(0xFF3B4252), inverseSurface = Color(0xFF3B4252),
@ -68,5 +73,10 @@ internal object NordColorScheme : BaseColorScheme() {
onError = Color(0xFFECEFF4), onError = Color(0xFFECEFF4),
errorContainer = Color(0xFFBF616A), errorContainer = Color(0xFFBF616A),
onErrorContainer = Color(0xFF000000), onErrorContainer = Color(0xFF000000),
surfaceContainerLowest = Color(0xFFD1D7E0),
surfaceContainerLow = Color(0xFFD6DCE6),
surfaceContainer = Color(0xFFDAE0EA), // Navigation bar background
surfaceContainerHigh = Color(0xFFE9EDF3),
surfaceContainerHighest = Color(0xFFF2F4F8),
) )
} }

View file

@ -18,54 +18,78 @@ import androidx.compose.ui.graphics.Color
internal object StrawberryColorScheme : BaseColorScheme() { internal object StrawberryColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFFFFB2B9), primary = Color(0xFFFFB2B8),
onPrimary = Color(0xFF67001B), onPrimary = Color(0xFF67001D),
primaryContainer = Color(0xFF91002A), primaryContainer = Color(0xFFD53855),
onPrimaryContainer = Color(0xFFFFDADD), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFB61E40), secondary = Color(0xFFED4A65), // Unread badge
secondary = Color(0xFFFFB2B9), onSecondary = Color(0xFF201A1A), // Unread badge text
onSecondary = Color(0xFF67001B), secondaryContainer = Color(0xFF91002A), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF91002A), onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selector icon
onSecondaryContainer = Color(0xFFFFDADD), tertiary = Color(0xFFE8C08E), // Downloaded badge
tertiary = Color(0xFFE8C08E), onTertiary = Color(0xFF201A1A), // Downloaded badge text
onTertiary = Color(0xFF432C06), tertiaryContainer = Color(0xFF775930),
tertiaryContainer = Color(0xFF5D421B), onTertiaryContainer = Color(0xFFFFF7F1),
onTertiaryContainer = Color(0xFFFFDDB1), error = Color(0xFFFFB4AB),
onError = Color(0xFF690005),
errorContainer = Color(0xFF93000A),
onErrorContainer = Color(0xFFFFDAD6),
background = Color(0xFF201A1A), background = Color(0xFF201A1A),
onBackground = Color(0xFFECDFDF), onBackground = Color(0xFFF7DCDD),
surface = Color(0xFF201A1A), surface = Color(0xFF201A1A),
onSurface = Color(0xFFECDFDF), onSurface = Color(0xFFF7DCDD),
surfaceVariant = Color(0xFF534344), surfaceVariant = Color(0xFF322727), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD7C1C2), onSurfaceVariant = Color(0xFFE1BEC0),
surfaceTint = Color(0xFFFFB2B9), outline = Color(0xFFA9898B),
inverseSurface = Color(0xFFECDFDF), outlineVariant = Color(0xFF594042),
inverseOnSurface = Color(0xFF201A1A), scrim = Color(0xFF000000),
outline = Color(0xFFA08C8D), inverseSurface = Color(0xFFF7DCDD),
inverseOnSurface = Color(0xFF3D2C2D),
inversePrimary = Color(0xFFB61F40),
surfaceDim = Color(0xFF1D1011),
surfaceBright = Color(0xFF463536),
surfaceContainerLowest = Color(0xFF2C2222),
surfaceContainerLow = Color(0xFF302525),
surfaceContainer = Color(0xFF322727), // Navigation bar background
surfaceContainerHigh = Color(0xFF3C2F2F),
surfaceContainerHighest = Color(0xFF463737),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFFB61E40), primary = Color(0xFFA10833),
onPrimary = Color(0xFFFFFFFF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFFFDADD), primaryContainer = Color(0xFFD53855),
onPrimaryContainer = Color(0xFF40000D), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFFFB2B9), secondary = Color(0xFFA10833), // Unread badge
secondary = Color(0xFFB61E40), onSecondary = Color(0xFFFFFFFF), // Unread badge text
onSecondary = Color(0xFFFFFFFF), secondaryContainer = Color(0xFFD53855), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFFFFDADD), onSecondaryContainer = Color(0xFFF6EAED), // Navigation bar selector icon
onSecondaryContainer = Color(0xFF40000D), tertiary = Color(0xFF5F441D), // Downloaded badge
tertiary = Color(0xFF775930), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
onTertiary = Color(0xFFFFFFFF), tertiaryContainer = Color(0xFF87683D),
tertiaryContainer = Color(0xFFFFDDB1), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFF2A1800), error = Color(0xFFBA1A1A),
background = Color(0xFFFCFCFC), onError = Color(0xFFFFFFFF),
onBackground = Color(0xFF201A1A), errorContainer = Color(0xFFFFDAD6),
surface = Color(0xFFFCFCFC), onErrorContainer = Color(0xFF410002),
onSurface = Color(0xFF201A1A), background = Color(0xFFFAFAFA),
surfaceVariant = Color(0xFFF4DDDD), onBackground = Color(0xFF261819),
onSurfaceVariant = Color(0xFF534344), surface = Color(0xFFFAFAFA),
surfaceTint = Color(0xFFB61E40), onSurface = Color(0xFF261819),
inverseSurface = Color(0xFF362F2F), surfaceVariant = Color(0xFFF6EAED), // Navigation bar background (ThemePrefWidget)
inverseOnSurface = Color(0xFFFBEDED), onSurfaceVariant = Color(0xFF594042),
outline = Color(0xFF857374), outline = Color(0xFF8D7071),
outlineVariant = Color(0xFFE1BEC0),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFF3D2C2D),
inverseOnSurface = Color(0xFFFFECED),
inversePrimary = Color(0xFFFFB2B8),
surfaceDim = Color(0xFFEED4D5),
surfaceBright = Color(0xFFFFF8F7),
surfaceContainerLowest = Color(0xFFF7DCDD),
surfaceContainerLow = Color(0xFFFDE2E3),
surfaceContainer = Color(0xFFF6EAED), // Navigation bar background
surfaceContainerHigh = Color(0xFFFFF0F0),
surfaceContainerHighest = Color(0xFFFFFFFF),
) )
} }

View file

@ -22,19 +22,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF00429B), primaryContainer = Color(0xFF00429B),
onPrimaryContainer = Color(0xFFD9E2FF), onPrimaryContainer = Color(0xFFD9E2FF),
inversePrimary = Color(0xFF0058CA), inversePrimary = Color(0xFF0058CA),
secondary = Color(0xFFB0C6FF), secondary = Color(0xFFB0C6FF), // Unread badge
onSecondary = Color(0xFF002D6E), onSecondary = Color(0xFF002D6E), // Unread badge text
secondaryContainer = Color(0xFF00429B), secondaryContainer = Color(0xFF00429B), // Navigation bar selector pill & pro
onSecondaryContainer = Color(0xFFD9E2FF), onSecondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector icon
tertiary = Color(0xFF7ADC77), tertiary = Color(0xFF7ADC77), // Downloaded badge
onTertiary = Color(0xFF003909), onTertiary = Color(0xFF003909), // Downloaded badge text
tertiaryContainer = Color(0xFF005312), tertiaryContainer = Color(0xFF005312),
onTertiaryContainer = Color(0xFF95F990), onTertiaryContainer = Color(0xFF95F990),
background = Color(0xFF1B1B1F), background = Color(0xFF1B1B1F),
onBackground = Color(0xFFE3E2E6), onBackground = Color(0xFFE3E2E6),
surface = Color(0xFF1B1B1F), surface = Color(0xFF1B1B1F),
onSurface = Color(0xFFE3E2E6), onSurface = Color(0xFFE3E2E6),
surfaceVariant = Color(0xFF44464F), surfaceVariant = Color(0xFF211F26), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFC5C6D0), onSurfaceVariant = Color(0xFFC5C6D0),
surfaceTint = Color(0xFFB0C6FF), surfaceTint = Color(0xFFB0C6FF),
inverseSurface = Color(0xFFE3E2E6), inverseSurface = Color(0xFFE3E2E6),
@ -45,6 +45,11 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
onErrorContainer = Color(0xFFFFDAD6), onErrorContainer = Color(0xFFFFDAD6),
outline = Color(0xFF8F9099), outline = Color(0xFF8F9099),
outlineVariant = Color(0xFF44464F), outlineVariant = Color(0xFF44464F),
surfaceContainerLowest = Color(0xFF1A181D),
surfaceContainerLow = Color(0xFF1E1C22),
surfaceContainer = Color(0xFF211F26), // Navigation bar background
surfaceContainerHigh = Color(0xFF292730),
surfaceContainerHighest = Color(0xFF302E38),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@ -53,19 +58,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFD9E2FF), primaryContainer = Color(0xFFD9E2FF),
onPrimaryContainer = Color(0xFF001945), onPrimaryContainer = Color(0xFF001945),
inversePrimary = Color(0xFFB0C6FF), inversePrimary = Color(0xFFB0C6FF),
secondary = Color(0xFF0058CA), secondary = Color(0xFF0058CA), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFD9E2FF), secondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF001945), onSecondaryContainer = Color(0xFF001945), // Navigation bar selector icon
tertiary = Color(0xFF006E1B), tertiary = Color(0xFF006E1B), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF95F990), tertiaryContainer = Color(0xFF95F990),
onTertiaryContainer = Color(0xFF002203), onTertiaryContainer = Color(0xFF002203),
background = Color(0xFFFEFBFF), background = Color(0xFFFEFBFF),
onBackground = Color(0xFF1B1B1F), onBackground = Color(0xFF1B1B1F),
surface = Color(0xFFFEFBFF), surface = Color(0xFFFEFBFF),
onSurface = Color(0xFF1B1B1F), onSurface = Color(0xFF1B1B1F),
surfaceVariant = Color(0xFFE1E2EC), surfaceVariant = Color(0xFFF3EDF7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF44464F), onSurfaceVariant = Color(0xFF44464F),
surfaceTint = Color(0xFF0058CA), surfaceTint = Color(0xFF0058CA),
inverseSurface = Color(0xFF303034), inverseSurface = Color(0xFF303034),
@ -76,5 +81,10 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
onErrorContainer = Color(0xFF410002), onErrorContainer = Color(0xFF410002),
outline = Color(0xFF757780), outline = Color(0xFF757780),
outlineVariant = Color(0xFFC5C6D0), outlineVariant = Color(0xFFC5C6D0),
surfaceContainerLowest = Color(0xFFF5F1F8),
surfaceContainerLow = Color(0xFFF7F2FA),
surfaceContainer = Color(0xFFF3EDF7), // Navigation bar background
surfaceContainerHigh = Color(0xFFFCF7FF),
surfaceContainerHighest = Color(0xFFFCF7FF),
) )
} }

View file

@ -23,24 +23,29 @@ internal object TakoColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFF3B375), primaryContainer = Color(0xFFF3B375),
onPrimaryContainer = Color(0xFF38294E), onPrimaryContainer = Color(0xFF38294E),
inversePrimary = Color(0xFF84531E), inversePrimary = Color(0xFF84531E),
secondary = Color(0xFFF3B375), secondary = Color(0xFFF3B375), // Unread badge
onSecondary = Color(0xFF38294E), onSecondary = Color(0xFF38294E), // Unread badge text
secondaryContainer = Color(0xFFF3B375), secondaryContainer = Color(0xFF5C4D4B), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF38294E), onSecondaryContainer = Color(0xFFF3B375), // Navigation bar selector icon
tertiary = Color(0xFF66577E), tertiary = Color(0xFF66577E), // Downloaded badge
onTertiary = Color(0xFFF3B375), onTertiary = Color(0xFFF3B375), // Downloaded badge text
tertiaryContainer = Color(0xFF4E4065), tertiaryContainer = Color(0xFF4E4065),
onTertiaryContainer = Color(0xFFEDDCFF), onTertiaryContainer = Color(0xFFEDDCFF),
background = Color(0xFF21212E), background = Color(0xFF21212E),
onBackground = Color(0xFFE3E0F2), onBackground = Color(0xFFE3E0F2),
surface = Color(0xFF21212E), surface = Color(0xFF21212E),
onSurface = Color(0xFFE3E0F2), onSurface = Color(0xFFE3E0F2),
surfaceVariant = Color(0xFF49454E), surfaceVariant = Color(0xFF2A2A3C), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFCBC4CE), onSurfaceVariant = Color(0xFFCBC4CE),
surfaceTint = Color(0xFF66577E), surfaceTint = Color(0xFF66577E),
inverseSurface = Color(0xFFE5E1E6), inverseSurface = Color(0xFFE5E1E6),
inverseOnSurface = Color(0xFF1B1B1E), inverseOnSurface = Color(0xFF1B1B1E),
outline = Color(0xFF958F99), outline = Color(0xFF958F99),
surfaceContainerLowest = Color(0xFF20202E),
surfaceContainerLow = Color(0xFF262636),
surfaceContainer = Color(0xFF2A2A3C), // Navigation bar background
surfaceContainerHigh = Color(0xFF303044),
surfaceContainerHighest = Color(0xFF36364D),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@ -49,23 +54,28 @@ internal object TakoColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF66577E), primaryContainer = Color(0xFF66577E),
onPrimaryContainer = Color(0xFFF3B375), onPrimaryContainer = Color(0xFFF3B375),
inversePrimary = Color(0xFFD6BAFF), inversePrimary = Color(0xFFD6BAFF),
secondary = Color(0xFF66577E), secondary = Color(0xFF66577E), // Unread badge
onSecondary = Color(0xFFF3B375), onSecondary = Color(0xFFF3B375), // Unread badge text
secondaryContainer = Color(0xFF66577E), secondaryContainer = Color(0xFFC8BED0), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFF3B375), onSecondaryContainer = Color(0xFF66577E), // Navigation bar selector icon
tertiary = Color(0xFFF3B375), tertiary = Color(0xFFF3B375), // Downloaded badge
onTertiary = Color(0xFF574360), onTertiary = Color(0xFF574360), // Downloaded badge text
tertiaryContainer = Color(0xFFFDD6B0), tertiaryContainer = Color(0xFFFDD6B0),
onTertiaryContainer = Color(0xFF221437), onTertiaryContainer = Color(0xFF221437),
background = Color(0xFFF7F5FF), background = Color(0xFFF7F5FF),
onBackground = Color(0xFF1B1B22), onBackground = Color(0xFF1B1B22),
surface = Color(0xFFF7F5FF), surface = Color(0xFFF7F5FF),
onSurface = Color(0xFF1B1B22), onSurface = Color(0xFF1B1B22),
surfaceVariant = Color(0xFFE8E0EB), surfaceVariant = Color(0xFFE8E0EB), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF49454E), onSurfaceVariant = Color(0xFF49454E),
surfaceTint = Color(0xFF66577E), surfaceTint = Color(0xFF66577E),
inverseSurface = Color(0xFF313033), inverseSurface = Color(0xFF313033),
inverseOnSurface = Color(0xFFF3EFF4), inverseOnSurface = Color(0xFFF3EFF4),
outline = Color(0xFF7A757E), outline = Color(0xFF7A757E),
surfaceContainerLowest = Color(0xFFD7D0DA),
surfaceContainerLow = Color(0xFFDFD8E2),
surfaceContainer = Color(0xFFE8E0EB), // Navigation bar background
surfaceContainerHigh = Color(0xFFEEE6F1),
surfaceContainerHighest = Color(0xFFF7EEFA),
) )
} }

View file

@ -15,24 +15,29 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF40E0D0), primaryContainer = Color(0xFF40E0D0),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFF008080), inversePrimary = Color(0xFF008080),
secondary = Color(0xFF40E0D0), secondary = Color(0xFF40E0D0), // Unread badge
onSecondary = Color(0xFF000000), onSecondary = Color(0xFF000000), // Unread badge text
secondaryContainer = Color(0xFF18544E), secondaryContainer = Color(0xFF18544E), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF40E0D0), onSecondaryContainer = Color(0xFF40E0D0), // Navigation bar selector icon
tertiary = Color(0xFFBF1F2F), tertiary = Color(0xFFBF1F2F), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF200508), tertiaryContainer = Color(0xFF200508),
onTertiaryContainer = Color(0xFFBF1F2F), onTertiaryContainer = Color(0xFFBF1F2F),
background = Color(0xFF202125), background = Color(0xFF202125),
onBackground = Color(0xFFDFDEDA), onBackground = Color(0xFFDFDEDA),
surface = Color(0xFF202125), surface = Color(0xFF202125),
onSurface = Color(0xFFDFDEDA), onSurface = Color(0xFFDFDEDA),
surfaceVariant = Color(0xFF3F4947), surfaceVariant = Color(0xFF233133), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFDFDEDA), onSurfaceVariant = Color(0xFFDFDEDA),
surfaceTint = Color(0xFF40E0D0), surfaceTint = Color(0xFF40E0D0),
inverseSurface = Color(0xFFDFDEDA), inverseSurface = Color(0xFFDFDEDA),
inverseOnSurface = Color(0xFF202125), inverseOnSurface = Color(0xFF202125),
outline = Color(0xFF899391), outline = Color(0xFF899391),
surfaceContainerLowest = Color(0xFF202C2E),
surfaceContainerLow = Color(0xFF222F31),
surfaceContainer = Color(0xFF233133), // Navigation bar background
surfaceContainerHigh = Color(0xFF28383A),
surfaceContainerHighest = Color(0xFF2F4244),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@ -41,23 +46,28 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF008080), primaryContainer = Color(0xFF008080),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF40E0D0), inversePrimary = Color(0xFF40E0D0),
secondary = Color(0xFF008080), secondary = Color(0xFF008080), // Unread badge text
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFBFDFDF), secondaryContainer = Color(0xFFCFE5E4), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF008080), onSecondaryContainer = Color(0xFF008080), // Navigation bar selector icon
tertiary = Color(0xFFFF7F7F), tertiary = Color(0xFFFF7F7F), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFF2A1616), tertiaryContainer = Color(0xFF2A1616),
onTertiaryContainer = Color(0xFFFF7F7F), onTertiaryContainer = Color(0xFFFF7F7F),
background = Color(0xFFFAFAFA), background = Color(0xFFFAFAFA),
onBackground = Color(0xFF050505), onBackground = Color(0xFF050505),
surface = Color(0xFFFAFAFA), surface = Color(0xFFFAFAFA),
onSurface = Color(0xFF050505), onSurface = Color(0xFF050505),
surfaceVariant = Color(0xFFDAE5E2), surfaceVariant = Color(0xFFEBF3F1), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF050505), onSurfaceVariant = Color(0xFF050505),
surfaceTint = Color(0xFFBFDFDF), surfaceTint = Color(0xFFBFDFDF),
inverseSurface = Color(0xFF050505), inverseSurface = Color(0xFF050505),
inverseOnSurface = Color(0xFFFAFAFA), inverseOnSurface = Color(0xFFFAFAFA),
outline = Color(0xFF6F7977), outline = Color(0xFF6F7977),
surfaceContainerLowest = Color(0xFFE1E9E7),
surfaceContainerLow = Color(0xFFE6EEEC),
surfaceContainer = Color(0xFFEBF3F1), // Navigation bar background
surfaceContainerHigh = Color(0xFFF0F8F6),
surfaceContainerHighest = Color(0xFFF7FFFD),
) )
} }

View file

@ -22,24 +22,29 @@ internal object TidalWaveColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF004d61), primaryContainer = Color(0xFF004d61),
onPrimaryContainer = Color(0xFFb8eaff), onPrimaryContainer = Color(0xFFb8eaff),
inversePrimary = Color(0xFFa12b03), inversePrimary = Color(0xFFa12b03),
secondary = Color(0xFF5ed4fc), secondary = Color(0xFF5ed4fc), // Unread badge
onSecondary = Color(0xFF003544), onSecondary = Color(0xFF003544), // Unread badge text
secondaryContainer = Color(0xFF004d61), secondaryContainer = Color(0xFF004d61), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFb8eaff), onSecondaryContainer = Color(0xFFb8eaff), // Navigation bar selector icon
tertiary = Color(0xFF92f7bc), tertiary = Color(0xFF92f7bc), // Downloaded badge
onTertiary = Color(0xFF001c3b), onTertiary = Color(0xFF001c3b), // Downloaded badge text
tertiaryContainer = Color(0xFFc3fada), tertiaryContainer = Color(0xFFc3fada),
onTertiaryContainer = Color(0xFF78ffd6), onTertiaryContainer = Color(0xFF78ffd6),
background = Color(0xFF001c3b), background = Color(0xFF001c3b),
onBackground = Color(0xFFd5e3ff), onBackground = Color(0xFFd5e3ff),
surface = Color(0xFF001c3b), surface = Color(0xFF001c3b),
onSurface = Color(0xFFd5e3ff), onSurface = Color(0xFFd5e3ff),
surfaceVariant = Color(0xFF40484c), surfaceVariant = Color(0xFF082b4b), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFbfc8cc), onSurfaceVariant = Color(0xFFbfc8cc),
surfaceTint = Color(0xFF5ed4fc), surfaceTint = Color(0xFF5ed4fc),
inverseSurface = Color(0xFFffe3c4), inverseSurface = Color(0xFFffe3c4),
inverseOnSurface = Color(0xFF001c3b), inverseOnSurface = Color(0xFF001c3b),
outline = Color(0xFF8a9296), outline = Color(0xFF8a9296),
surfaceContainerLowest = Color(0xFF072642),
surfaceContainerLow = Color(0xFF072947),
surfaceContainer = Color(0xFF082b4b), // Navigation bar background
surfaceContainerHigh = Color(0xFF093257),
surfaceContainerHighest = Color(0xFF0A3861),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@ -48,23 +53,28 @@ internal object TidalWaveColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFB4D4DF), primaryContainer = Color(0xFFB4D4DF),
onPrimaryContainer = Color(0xFF001f28), onPrimaryContainer = Color(0xFF001f28),
inversePrimary = Color(0xFFff987f), inversePrimary = Color(0xFFff987f),
secondary = Color(0xFF006780), secondary = Color(0xFF006780), // Unread badge
onSecondary = Color(0xFFffffff), onSecondary = Color(0xFFffffff), // Unread badge text
secondaryContainer = Color(0xFFb8eaff), secondaryContainer = Color(0xFF9AE1FF), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF001f28), onSecondaryContainer = Color(0xFF001f28), // Navigation bar selector icon
tertiary = Color(0xFF92f7bc), tertiary = Color(0xFF92f7bc), // Downloaded badge
onTertiary = Color(0xFF001c3b), onTertiary = Color(0xFF001c3b), // Downloaded badge text
tertiaryContainer = Color(0xFFc3fada), tertiaryContainer = Color(0xFFc3fada),
onTertiaryContainer = Color(0xFF78ffd6), onTertiaryContainer = Color(0xFF78ffd6),
background = Color(0xFFfdfbff), background = Color(0xFFfdfbff),
onBackground = Color(0xFF001c3b), onBackground = Color(0xFF001c3b),
surface = Color(0xFFfdfbff), surface = Color(0xFFfdfbff),
onSurface = Color(0xFF001c3b), onSurface = Color(0xFF001c3b),
surfaceVariant = Color(0xFFdce4e8), surfaceVariant = Color(0xFFe8eff5), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF40484c), onSurfaceVariant = Color(0xFF40484c),
surfaceTint = Color(0xFF006780), surfaceTint = Color(0xFF006780),
inverseSurface = Color(0xFF020400), inverseSurface = Color(0xFF020400),
inverseOnSurface = Color(0xFFffe3c4), inverseOnSurface = Color(0xFFffe3c4),
outline = Color(0xFF70787c), outline = Color(0xFF70787c),
surfaceContainerLowest = Color(0xFFe2e8ec),
surfaceContainerLow = Color(0xFFe5ecf1),
surfaceContainer = Color(0xFFe8eff5), // Navigation bar background
surfaceContainerHigh = Color(0xFFedf4fA),
surfaceContainerHighest = Color(0xFFf5faff),
) )
} }

View file

@ -17,24 +17,29 @@ internal object YinYangColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFFFFF), primaryContainer = Color(0xFFFFFFFF),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFFCECECE), inversePrimary = Color(0xFFCECECE),
secondary = Color(0xFFFFFFFF), secondary = Color(0xFFFFFFFF), // Unread badge
onSecondary = Color(0xFF5A5A5A), onSecondary = Color(0xFF5A5A5A), // Unread badge text
secondaryContainer = Color(0xFF717171), secondaryContainer = Color(0xFF717171), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFE4E4E4), onSecondaryContainer = Color(0xFFE4E4E4), // Navigation bar selector icon
tertiary = Color(0xFF000000), tertiary = Color(0xFF000000), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF00419E), tertiaryContainer = Color(0xFF00419E),
onTertiaryContainer = Color(0xFFD8E2FF), onTertiaryContainer = Color(0xFFD8E2FF),
background = Color(0xFF1E1E1E), background = Color(0xFF1E1E1E),
onBackground = Color(0xFFE6E6E6), onBackground = Color(0xFFE6E6E6),
surface = Color(0xFF1E1E1E), surface = Color(0xFF1E1E1E),
onSurface = Color(0xFFE6E6E6), onSurface = Color(0xFFE6E6E6),
surfaceVariant = Color(0xFF4E4E4E), surfaceVariant = Color(0xFF313131), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD1D1D1), onSurfaceVariant = Color(0xFFD1D1D1),
surfaceTint = Color(0xFFFFFFFF), surfaceTint = Color(0xFFFFFFFF),
inverseSurface = Color(0xFFE6E6E6), inverseSurface = Color(0xFFE6E6E6),
inverseOnSurface = Color(0xFF1E1E1E), inverseOnSurface = Color(0xFF1E1E1E),
outline = Color(0xFF999999), outline = Color(0xFF999999),
surfaceContainerLowest = Color(0xFF2A2A2A),
surfaceContainerLow = Color(0xFF2D2D2D),
surfaceContainer = Color(0xFF313131), // Navigation bar background
surfaceContainerHigh = Color(0xFF383838),
surfaceContainerHighest = Color(0xFF3F3F3F),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@ -43,23 +48,28 @@ internal object YinYangColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF000000), primaryContainer = Color(0xFF000000),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFA6A6A6), inversePrimary = Color(0xFFA6A6A6),
secondary = Color(0xFF000000), secondary = Color(0xFF000000), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFDDDDDD), secondaryContainer = Color(0xFFDDDDDD), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF0C0C0C), onSecondaryContainer = Color(0xFF0C0C0C), // Navigation bar selector icon
tertiary = Color(0xFFFFFFFF), tertiary = Color(0xFFFFFFFF), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFFD8E2FF), tertiaryContainer = Color(0xFFD8E2FF),
onTertiaryContainer = Color(0xFF001947), onTertiaryContainer = Color(0xFF001947),
background = Color(0xFFFDFDFD), background = Color(0xFFFDFDFD),
onBackground = Color(0xFF222222), onBackground = Color(0xFF222222),
surface = Color(0xFFFDFDFD), surface = Color(0xFFFDFDFD),
onSurface = Color(0xFF222222), onSurface = Color(0xFF222222),
surfaceVariant = Color(0xFFEDEDED), surfaceVariant = Color(0xFFE8E8E8), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF515151), onSurfaceVariant = Color(0xFF515151),
surfaceTint = Color(0xFF000000), surfaceTint = Color(0xFF000000),
inverseSurface = Color(0xFF333333), inverseSurface = Color(0xFF333333),
inverseOnSurface = Color(0xFFF4F4F4), inverseOnSurface = Color(0xFFF4F4F4),
outline = Color(0xFF838383), outline = Color(0xFF838383),
surfaceContainerLowest = Color(0xFFCFCFCF),
surfaceContainerLow = Color(0xFFDADADA),
surfaceContainer = Color(0xFFE8E8E8), // Navigation bar background
surfaceContainerHigh = Color(0xFFECECEC),
surfaceContainerHighest = Color(0xFFEFEFEF),
) )
} }

View file

@ -23,24 +23,29 @@ internal object YotsubaColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF862200), primaryContainer = Color(0xFF862200),
onPrimaryContainer = Color(0xFFFFDBCF), onPrimaryContainer = Color(0xFFFFDBCF),
inversePrimary = Color(0xFFAE3200), inversePrimary = Color(0xFFAE3200),
secondary = Color(0xFFFFB59D), secondary = Color(0xFFFFB59D), // Unread badge
onSecondary = Color(0xFF5F1600), onSecondary = Color(0xFF5F1600), // Unread badge text
secondaryContainer = Color(0xFF862200), secondaryContainer = Color(0xFF862200), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFFFDBCF), onSecondaryContainer = Color(0xFFFFDBCF), // Navigation bar selector icon
tertiary = Color(0xFFD7C68D), tertiary = Color(0xFFD7C68D), // Downloaded badge
onTertiary = Color(0xFF3A2F05), onTertiary = Color(0xFF3A2F05), // Downloaded badge text
tertiaryContainer = Color(0xFF524619), tertiaryContainer = Color(0xFF524619),
onTertiaryContainer = Color(0xFFF5E2A7), onTertiaryContainer = Color(0xFFF5E2A7),
background = Color(0xFF211A18), background = Color(0xFF211A18),
onBackground = Color(0xFFEDE0DD), onBackground = Color(0xFFEDE0DD),
surface = Color(0xFF211A18), surface = Color(0xFF211A18),
onSurface = Color(0xFFEDE0DD), onSurface = Color(0xFFEDE0DD),
surfaceVariant = Color(0xFF53433F), surfaceVariant = Color(0xFF332723), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD8C2BC), onSurfaceVariant = Color(0xFFD8C2BC),
surfaceTint = Color(0xFFFFB59D), surfaceTint = Color(0xFFFFB59D),
inverseSurface = Color(0xFFEDE0DD), inverseSurface = Color(0xFFEDE0DD),
inverseOnSurface = Color(0xFF211A18), inverseOnSurface = Color(0xFF211A18),
outline = Color(0xFFA08C87), outline = Color(0xFFA08C87),
surfaceContainerLowest = Color(0xFF2E221F),
surfaceContainerLow = Color(0xFF312521),
surfaceContainer = Color(0xFF332723), // Navigation bar background
surfaceContainerHigh = Color(0xFF413531),
surfaceContainerHighest = Color(0xFF4C403D),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@ -49,23 +54,28 @@ internal object YotsubaColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFDBCF), primaryContainer = Color(0xFFFFDBCF),
onPrimaryContainer = Color(0xFF3B0A00), onPrimaryContainer = Color(0xFF3B0A00),
inversePrimary = Color(0xFFFFB59D), inversePrimary = Color(0xFFFFB59D),
secondary = Color(0xFFAE3200), secondary = Color(0xFFAE3200), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFFFDBCF), secondaryContainer = Color(0xFFEBCDC2), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF3B0A00), onSecondaryContainer = Color(0xFF3B0A00), // Navigation bar selector icon
tertiary = Color(0xFF6B5E2F), tertiary = Color(0xFF6B5E2F), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFFF5E2A7), tertiaryContainer = Color(0xFFF5E2A7),
onTertiaryContainer = Color(0xFF231B00), onTertiaryContainer = Color(0xFF231B00),
background = Color(0xFFFCFCFC), background = Color(0xFFFCFCFC),
onBackground = Color(0xFF211A18), onBackground = Color(0xFF211A18),
surface = Color(0xFFFCFCFC), surface = Color(0xFFFCFCFC),
onSurface = Color(0xFF211A18), onSurface = Color(0xFF211A18),
surfaceVariant = Color(0xFFF5DED8), surfaceVariant = Color(0xFFF6EBE7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF53433F), onSurfaceVariant = Color(0xFF53433F),
surfaceTint = Color(0xFFAE3200), surfaceTint = Color(0xFFAE3200),
inverseSurface = Color(0xFF362F2D), inverseSurface = Color(0xFF362F2D),
inverseOnSurface = Color(0xFFFBEEEB), inverseOnSurface = Color(0xFFFBEEEB),
outline = Color(0xFF85736E), outline = Color(0xFF85736E),
surfaceContainerLowest = Color(0xFFECE3E0),
surfaceContainerLow = Color(0xFFF1E7E4),
surfaceContainer = Color(0xFFF6EBE7), // Navigation bar background
surfaceContainerHigh = Color(0xFFFAF4F2),
surfaceContainerHighest = Color(0xFFFBF6F4),
) )
} }

View file

@ -38,7 +38,6 @@ 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.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -58,8 +57,6 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
private const val UnsetStatusTextAlpha = 0.5F
@Composable @Composable
fun TrackInfoDialogHome( fun TrackInfoDialogHome(
trackItems: List<TrackItem>, trackItems: List<TrackItem>,
@ -72,6 +69,7 @@ fun TrackInfoDialogHome(
onNewSearch: (TrackItem) -> Unit, onNewSearch: (TrackItem) -> Unit,
onOpenInBrowser: (TrackItem) -> Unit, onOpenInBrowser: (TrackItem) -> Unit,
onRemoved: (TrackItem) -> Unit, onRemoved: (TrackItem) -> Unit,
onCopyLink: (TrackItem) -> Unit,
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@ -116,6 +114,7 @@ fun TrackInfoDialogHome(
onNewSearch = { onNewSearch(item) }, onNewSearch = { onNewSearch(item) },
onOpenInBrowser = { onOpenInBrowser(item) }, onOpenInBrowser = { onOpenInBrowser(item) },
onRemoved = { onRemoved(item) }, onRemoved = { onRemoved(item) },
onCopyLink = { onCopyLink(item) },
) )
} else { } else {
TrackInfoItemEmpty( TrackInfoItemEmpty(
@ -144,6 +143,7 @@ private fun TrackInfoItem(
onNewSearch: () -> Unit, onNewSearch: () -> Unit,
onOpenInBrowser: () -> Unit, onOpenInBrowser: () -> Unit,
onRemoved: () -> Unit, onRemoved: () -> Unit,
onCopyLink: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
Column { Column {
@ -153,6 +153,7 @@ private fun TrackInfoItem(
TrackLogoIcon( TrackLogoIcon(
tracker = tracker, tracker = tracker,
onClick = onOpenInBrowser, onClick = onOpenInBrowser,
onLongClick = onCopyLink,
) )
Box( Box(
modifier = Modifier modifier = Modifier
@ -179,6 +180,7 @@ private fun TrackInfoItem(
TrackInfoItemMenu( TrackInfoItemMenu(
onOpenInBrowser = onOpenInBrowser, onOpenInBrowser = onOpenInBrowser,
onRemoved = onRemoved, onRemoved = onRemoved,
onCopyLink = onCopyLink,
) )
} }
@ -186,7 +188,7 @@ private fun TrackInfoItem(
modifier = Modifier modifier = Modifier
.padding(top = 12.dp) .padding(top = 12.dp)
.clip(MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surface) .background(MaterialTheme.colorScheme.surfaceContainerHighest)
.padding(8.dp) .padding(8.dp)
.clip(RoundedCornerShape(6.dp)), .clip(RoundedCornerShape(6.dp)),
) { ) {
@ -206,10 +208,9 @@ private fun TrackInfoItem(
if (onScoreClick != null) { if (onScoreClick != null) {
VerticalDivider() VerticalDivider()
TrackDetailsItem( TrackDetailsItem(
modifier = Modifier modifier = Modifier.weight(1f),
.weight(1f) text = score,
.alpha(if (score == null) UnsetStatusTextAlpha else 1f), placeholder = stringResource(MR.strings.score),
text = score ?: stringResource(MR.strings.score),
onClick = onScoreClick, onClick = onScoreClick,
) )
} }
@ -238,6 +239,8 @@ private fun TrackInfoItem(
} }
} }
private const val UNSET_TEXT_ALPHA = 0.5F
@Composable @Composable
private fun TrackDetailsItem( private fun TrackDetailsItem(
text: String?, text: String?,
@ -258,7 +261,7 @@ private fun TrackDetailsItem(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = if (text == null) UnsetStatusTextAlpha else 1f), color = MaterialTheme.colorScheme.onSurface.copy(alpha = if (text == null) UNSET_TEXT_ALPHA else 1f),
) )
} }
} }
@ -287,6 +290,7 @@ private fun TrackInfoItemEmpty(
private fun TrackInfoItemMenu( private fun TrackInfoItemMenu(
onOpenInBrowser: () -> Unit, onOpenInBrowser: () -> Unit,
onRemoved: () -> Unit, onRemoved: () -> Unit,
onCopyLink: () -> Unit,
) { ) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) { Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) {
@ -307,6 +311,13 @@ private fun TrackInfoItemMenu(
expanded = false expanded = false
}, },
) )
DropdownMenuItem(
text = { Text(stringResource(MR.strings.action_copy_link)) },
onClick = {
onCopyLink()
expanded = false
},
)
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(MR.strings.action_remove)) }, text = { Text(stringResource(MR.strings.action_remove)) },
onClick = { onClick = {

View file

@ -56,6 +56,7 @@ internal class TrackInfoDialogHomePreviewProvider :
onNewSearch = {}, onNewSearch = {},
onOpenInBrowser = {}, onOpenInBrowser = {},
onRemoved = {}, onRemoved = {},
onCopyLink = {},
) )
} }
@ -71,6 +72,7 @@ internal class TrackInfoDialogHomePreviewProvider :
onNewSearch = {}, onNewSearch = {},
onOpenInBrowser = {}, onOpenInBrowser = {},
onRemoved = {}, onRemoved = {},
onCopyLink = {},
) )
} }

View file

@ -25,8 +25,10 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.clearText
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.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
@ -59,7 +61,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.text.toLowerCase
@ -84,8 +85,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable @Composable
fun TrackerSearch( fun TrackerSearch(
query: TextFieldValue, state: TextFieldState,
onQueryChange: (TextFieldValue) -> Unit,
onDispatchQuery: () -> Unit, onDispatchQuery: () -> Unit,
queryResult: Result<List<TrackSearch>>?, queryResult: Result<List<TrackSearch>>?,
selected: TrackSearch?, selected: TrackSearch?,
@ -115,20 +115,19 @@ fun TrackerSearch(
}, },
title = { title = {
BasicTextField( BasicTextField(
value = query, state = state,
onValueChange = onQueryChange,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.focusRequester(focusRequester) .focusRequester(focusRequester)
.runOnEnterKeyPressed(action = dispatchQueryAndClearFocus), .runOnEnterKeyPressed(action = dispatchQueryAndClearFocus),
textStyle = MaterialTheme.typography.bodyLarge textStyle = MaterialTheme.typography.bodyLarge
.copy(color = MaterialTheme.colorScheme.onSurface), .copy(color = MaterialTheme.colorScheme.onSurface),
singleLine = true, lineLimits = TextFieldLineLimits.SingleLine,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = { dispatchQueryAndClearFocus() }), onKeyboardAction = { dispatchQueryAndClearFocus() },
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
decorationBox = { decorator = {
if (query.text.isEmpty()) { if (state.text.isEmpty()) {
Text( Text(
text = stringResource(MR.strings.action_search_hint), text = stringResource(MR.strings.action_search_hint),
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
@ -140,10 +139,10 @@ fun TrackerSearch(
) )
}, },
actions = { actions = {
if (query.text.isNotEmpty()) { if (state.text.isNotEmpty()) {
IconButton( IconButton(
onClick = { onClick = {
onQueryChange(TextFieldValue()) state.clearText()
focusRequester.requestFocus() focusRequester.requestFocus()
}, },
) { ) {
@ -224,6 +223,7 @@ private fun SearchResultItem(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val clipboardManager: ClipboardManager = LocalClipboardManager.current val clipboardManager: ClipboardManager = LocalClipboardManager.current
val focusManager = LocalFocusManager.current
val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current) val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current)
val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current) val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current)
val description = trackSearch.summary.trim() val description = trackSearch.summary.trim()
@ -243,7 +243,10 @@ private fun SearchResultItem(
) )
.combinedClickable( .combinedClickable(
onLongClick = { dropDownMenuExpanded = true }, onLongClick = { dropDownMenuExpanded = true },
onClick = onClick, onClick = {
focusManager.clearFocus()
onClick()
},
) )
.padding(12.dp), .padding(12.dp),
) { ) {

View file

@ -1,7 +1,7 @@
package eu.kanade.presentation.track package eu.kanade.presentation.track
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
@ -13,8 +13,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
private val fullPageWithSecondSelected = @Composable { private val fullPageWithSecondSelected = @Composable {
val items = someTrackSearches().take(30).toList() val items = someTrackSearches().take(30).toList()
TrackerSearch( TrackerSearch(
query = TextFieldValue(text = "search text"), state = TextFieldState(initialText = "search text"),
onQueryChange = {},
onDispatchQuery = {}, onDispatchQuery = {},
queryResult = Result.success(items), queryResult = Result.success(items),
selected = items[1], selected = items[1],
@ -25,8 +24,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
} }
private val fullPageWithoutSelected = @Composable { private val fullPageWithoutSelected = @Composable {
TrackerSearch( TrackerSearch(
query = TextFieldValue(text = ""), state = TextFieldState(),
onQueryChange = {},
onDispatchQuery = {}, onDispatchQuery = {},
queryResult = Result.success(someTrackSearches().take(30).toList()), queryResult = Result.success(someTrackSearches().take(30).toList()),
selected = null, selected = null,
@ -37,8 +35,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
} }
private val loading = @Composable { private val loading = @Composable {
TrackerSearch( TrackerSearch(
query = TextFieldValue(), state = TextFieldState(),
onQueryChange = {},
onDispatchQuery = {}, onDispatchQuery = {},
queryResult = null, queryResult = null,
selected = null, selected = null,

View file

@ -22,9 +22,10 @@ import tachiyomi.presentation.core.util.clickableNoIndication
fun TrackLogoIcon( fun TrackLogoIcon(
tracker: Tracker, tracker: Tracker,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
) { ) {
val modifier = if (onClick != null) { val modifier = if (onClick != null) {
Modifier.clickableNoIndication(onClick = onClick) Modifier.clickableNoIndication(onClick = onClick, onLongClick = onLongClick)
} else { } else {
Modifier Modifier
} }
@ -53,6 +54,7 @@ private fun TrackLogoIconPreviews(
TrackLogoIcon( TrackLogoIcon(
tracker = tracker, tracker = tracker,
onClick = null, onClick = null,
onLongClick = null,
) )
} }
} }

View file

@ -104,7 +104,7 @@ fun UpdateScreen(
isRefreshing = false isRefreshing = false
} }
}, },
enabled = { !state.selectionMode }, enabled = !state.selectionMode,
indicatorPadding = contentPadding, indicatorPadding = contentPadding,
) { ) {
FastScrollLazyColumn( FastScrollLazyColumn(

View file

@ -37,13 +37,14 @@ import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.manga.components.ChapterDownloadIndicator import eu.kanade.presentation.manga.components.ChapterDownloadIndicator
import eu.kanade.presentation.manga.components.DotSeparatorText import eu.kanade.presentation.manga.components.DotSeparatorText
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.util.animateItemFastScroll
import eu.kanade.presentation.util.relativeTimeSpanString import eu.kanade.presentation.util.relativeTimeSpanString
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.updates.UpdatesItem import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.model.UpdatesWithRelations
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ListGroupHeader import tachiyomi.presentation.core.components.ListGroupHeader
import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
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 tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
@ -54,7 +55,7 @@ internal fun LazyListScope.updatesLastUpdatedItem(
item(key = "updates-lastUpdated") { item(key = "updates-lastUpdated") {
Box( Box(
modifier = Modifier modifier = Modifier
.animateItem() .animateItem(fadeInSpec = null, fadeOutSpec = null)
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small), .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
) { ) {
Text( Text(
@ -91,14 +92,14 @@ internal fun LazyListScope.updatesUiItems(
when (item) { when (item) {
is UpdatesUiModel.Header -> { is UpdatesUiModel.Header -> {
ListGroupHeader( ListGroupHeader(
modifier = Modifier.animateItem(), modifier = Modifier.animateItemFastScroll(),
text = relativeDateText(item.date), text = relativeDateText(item.date),
) )
} }
is UpdatesUiModel.Item -> { is UpdatesUiModel.Item -> {
val updatesItem = item.item val updatesItem = item.item
UpdatesUiItem( UpdatesUiItem(
modifier = Modifier.animateItem(), modifier = Modifier.animateItemFastScroll(),
update = updatesItem.update, update = updatesItem.update,
selected = updatesItem.selected, selected = updatesItem.selected,
readProgress = updatesItem.update.lastPageRead readProgress = updatesItem.update.lastPageRead
@ -145,7 +146,7 @@ private fun UpdatesUiItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val textAlpha = if (update.read) ReadItemAlpha else 1f val textAlpha = if (update.read) DISABLED_ALPHA else 1f
Row( Row(
modifier = modifier modifier = modifier
@ -219,7 +220,7 @@ private fun UpdatesUiItem(
Text( Text(
text = readProgress, text = readProgress,
maxLines = 1, maxLines = 1,
color = LocalContentColor.current.copy(alpha = ReadItemAlpha), color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
} }

View file

@ -2,7 +2,6 @@ package eu.kanade.presentation.util
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.network.HttpException
import eu.kanade.tachiyomi.source.online.LicensedMangaChaptersException
import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.system.isOnline
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.data.source.NoResultsException import tachiyomi.data.source.NoResultsException
@ -25,7 +24,6 @@ val Throwable.formattedMessage: String
is NoResultsException -> return stringResource(MR.strings.no_results_found) is NoResultsException -> return stringResource(MR.strings.no_results_found)
is SourceNotInstalledException -> return stringResource(MR.strings.loader_not_implemented_error) is SourceNotInstalledException -> return stringResource(MR.strings.loader_not_implemented_error)
is LicensedMangaChaptersException -> return stringResource(MR.strings.licensed_manga_chapters_error)
} }
return when (val className = this::class.simpleName) { return when (val className = this::class.simpleName) {
"Exception", "IOException" -> message ?: className "Exception", "IOException" -> message ?: className

View file

@ -0,0 +1,8 @@
package eu.kanade.presentation.util
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.ui.Modifier
// https://issuetracker.google.com/352584409
context(LazyItemScope)
fun Modifier.animateItemFastScroll() = this.animateItem(fadeInSpec = null, fadeOutSpec = null)

View file

@ -1,6 +1,5 @@
package eu.kanade.presentation.util package eu.kanade.presentation.util
import android.annotation.SuppressLint
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform import androidx.compose.animation.ContentTransform
@ -28,7 +27,6 @@ import soup.compose.material.motion.animation.rememberSlideDistance
/** /**
* For invoking back press to the parent activity * For invoking back press to the parent activity
*/ */
@SuppressLint("ComposeCompositionLocalUsage")
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
interface Tab : cafe.adriel.voyager.navigator.tab.Tab { interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
@ -57,7 +55,10 @@ interface AssistContentScreen {
} }
@Composable @Composable
fun DefaultNavigatorScreenTransition(navigator: Navigator) { fun DefaultNavigatorScreenTransition(
navigator: Navigator,
modifier: Modifier = Modifier,
) {
val slideDistance = rememberSlideDistance() val slideDistance = rememberSlideDistance()
ScreenTransition( ScreenTransition(
navigator = navigator, navigator = navigator,
@ -67,6 +68,7 @@ fun DefaultNavigatorScreenTransition(navigator: Navigator) {
slideDistance = slideDistance, slideDistance = slideDistance,
) )
}, },
modifier = modifier,
) )
} }

View file

@ -104,6 +104,11 @@ fun WebViewScreenContent(
return false return false
} }
// Ignore intents urls
if (it.url.toString().startsWith("intent://")) {
return true
}
// Continue with request, but with custom headers // Continue with request, but with custom headers
view?.loadUrl(it.url.toString(), headers) view?.loadUrl(it.url.toString(), headers)
} }

View file

@ -21,10 +21,12 @@ import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.allowRgb565 import coil3.request.allowRgb565
import coil3.request.crossfade import coil3.request.crossfade
import coil3.util.DebugLogger import coil3.util.DebugLogger
import dev.mihon.injekt.patchInjekt
import eu.kanade.domain.DomainModule import eu.kanade.domain.DomainModule
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.crash.CrashActivity import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
@ -49,6 +51,7 @@ import kotlinx.coroutines.flow.onEach
import logcat.AndroidLogcatLogger import logcat.AndroidLogcatLogger
import logcat.LogPriority import logcat.LogPriority
import logcat.LogcatLogger import logcat.LogcatLogger
import mihon.core.firebase.FirebaseConfig
import mihon.core.migration.Migrator import mihon.core.migration.Migrator
import mihon.core.migration.migrations.migrations import mihon.core.migration.migrations.migrations
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
@ -66,6 +69,7 @@ import java.security.Security
class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory { class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory {
private val basePreferences: BasePreferences by injectLazy() private val basePreferences: BasePreferences by injectLazy()
private val privacyPreferences: PrivacyPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy() private val networkPreferences: NetworkPreferences by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver() private val disableIncognitoReceiver = DisableIncognitoReceiver()
@ -73,6 +77,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
@SuppressLint("LaunchActivityFromNotification") @SuppressLint("LaunchActivityFromNotification")
override fun onCreate() { override fun onCreate() {
super<Application>.onCreate() super<Application>.onCreate()
patchInjekt()
FirebaseConfig.init(applicationContext)
GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java) GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java)
@ -95,6 +101,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
ProcessLifecycleOwner.get().lifecycle.addObserver(this) ProcessLifecycleOwner.get().lifecycle.addObserver(this)
val scope = ProcessLifecycleOwner.get().lifecycleScope
// Show notification to disable Incognito Mode when it's enabled // Show notification to disable Incognito Mode when it's enabled
basePreferences.incognitoMode().changes() basePreferences.incognitoMode().changes()
.onEach { enabled -> .onEach { enabled ->
@ -122,14 +130,22 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
cancelNotification(Notifications.ID_INCOGNITO_MODE) cancelNotification(Notifications.ID_INCOGNITO_MODE)
} }
} }
.launchIn(ProcessLifecycleOwner.get().lifecycleScope) .launchIn(scope)
privacyPreferences.analytics()
.changes()
.onEach(FirebaseConfig::setAnalyticsEnabled)
.launchIn(scope)
privacyPreferences.crashlytics()
.changes()
.onEach(FirebaseConfig::setCrashlyticsEnabled)
.launchIn(scope)
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get()) setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
// Updates widget update // Updates widget update
with(WidgetManager(Injekt.get(), Injekt.get())) { WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) }
init(ProcessLifecycleOwner.get().lifecycleScope)
}
if (!LogcatLogger.isInstalled && networkPreferences.verboseLogging().get()) { if (!LogcatLogger.isInstalled && networkPreferences.verboseLogging().get()) {
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE)) LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
@ -157,22 +173,28 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
return ImageLoader.Builder(this).apply { return ImageLoader.Builder(this).apply {
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client } val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
components { components {
// NetworkFetcher.Factory
add(OkHttpNetworkFetcherFactory(callFactoryLazy::value)) add(OkHttpNetworkFetcherFactory(callFactoryLazy::value))
// Decoder.Factory
add(TachiyomiImageDecoder.Factory()) add(TachiyomiImageDecoder.Factory())
add(MangaCoverFetcher.MangaFactory(callFactoryLazy)) // Fetcher.Factory
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
add(MangaKeyer())
add(MangaCoverKeyer())
add(BufferedSourceFetcher.Factory()) add(BufferedSourceFetcher.Factory())
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
add(MangaCoverFetcher.MangaFactory(callFactoryLazy))
// Keyer
add(MangaCoverKeyer())
add(MangaKeyer())
} }
crossfade((300 * this@App.animatorDurationScale).toInt()) crossfade((300 * this@App.animatorDurationScale).toInt())
allowRgb565(DeviceUtil.isLowRamDevice(this@App)) allowRgb565(DeviceUtil.isLowRamDevice(this@App))
if (networkPreferences.verboseLogging().get()) logger(DebugLogger()) if (networkPreferences.verboseLogging().get()) logger(DebugLogger())
// Coil spawns a new thread for every image load by default // Coil spawns a new thread for every image load by default
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8)) fetcherCoroutineContext(Dispatchers.IO.limitedParallelism(8))
decoderDispatcher(Dispatchers.IO.limitedParallelism(2)) decoderCoroutineContext(Dispatchers.IO.limitedParallelism(3))
}.build() }
.build()
} }
override fun onStart(owner: LifecycleOwner) { override fun onStart(owner: LifecycleOwner) {

View file

@ -11,7 +11,6 @@ import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import kotlin.system.exitProcess
class GlobalExceptionHandler private constructor( class GlobalExceptionHandler private constructor(
private val applicationContext: Context, private val applicationContext: Context,
@ -31,13 +30,9 @@ class GlobalExceptionHandler private constructor(
} }
override fun uncaughtException(thread: Thread, exception: Throwable) { override fun uncaughtException(thread: Thread, exception: Throwable) {
try { logcat(priority = LogPriority.ERROR, throwable = exception)
logcat(priority = LogPriority.ERROR, throwable = exception) launchActivity(applicationContext, activityToBeLaunched, exception)
launchActivity(applicationContext, activityToBeLaunched, exception) defaultHandler.uncaughtException(thread, exception)
exitProcess(0)
} catch (_: Exception) {
defaultHandler.uncaughtException(thread, exception)
}
} }
private fun launchActivity( private fun launchActivity(

Some files were not shown because too many files have changed in this diff Show more