Compare commits

...

47 Commits

Author SHA1 Message Date
38d5fc9160 Release v0.17.0 2024-10-26 23:37:41 +06:00
9454fe4482 Update CHANGELOG.md (#1349)
Co-authored-by: Roshan Varughese <40583749+Animeboynz@users.noreply.github.com>
2024-10-26 21:49:44 +06:00
6de06419f8 Fix app crash when removing tracked entry from tracker (#1380) 2024-10-26 20:12:34 +06:00
fc2f339ea1 Allow completely disabling "Update tracker" snackbar on mark as read (#1374)
Also fixes #1369
2024-10-26 19:16:39 +06:00
264030d6ec Add libs.material to presentation-widget (#1373)
Fixes some build issues
2024-10-26 09:02:07 +06:00
140083ee39 Update dependency com.pinterest.ktlint:ktlint-cli to v1.4.0
Co-authored-by: Mend Renovate <bot@renovateapp.com>
2024-10-26 07:40:57 +06:00
2bf7ef5d18 Update actions/setup-java action to v4.5.0 (#1366) 2024-10-26 07:23:13 +06:00
df9fff60da Cleanup Slider usage 2024-10-26 07:15:01 +06:00
aae0e3459c Update dependency me.zhanghai.android.libarchive:library to v1.1.4 (#1378) 2024-10-26 03:55:19 +06:00
f7752a98b2 Avoid blocking call to load categories in settings (#1364) 2024-10-24 23:51:47 +06:00
2ba7ed3280 Fix settings SliderItem steps count (#1356) 2024-10-24 12:59:22 +00:00
c153ac01f5 Rework Auto Track on Mark as Read (#1365) 2024-10-24 12:23:28 +00:00
47b0e9d7be Pin actions/upload-artifact action to b4b15b8 (#1363)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-10-24 03:56:06 +06:00
d4bf19f957 Make renovate group github action deps 2024-10-24 03:35:46 +06:00
01b44c0458 Update actions/checkout action to v4.2.2 (#1361) 2024-10-24 01:52:12 +06:00
e1e3ca7a56 Update actions/dependency-review-action action to v4.3.5 (#1354) 2024-10-24 01:51:55 +06:00
78d2cc75d5 Update dependency com.google.firebase:firebase-bom to v33.5.1 (#1362) 2024-10-24 01:51:39 +06:00
c550a81598 Update shizuku.version to v13.1.0 2024-10-22 03:18:35 +06:00
0be36a10c3 Update dependency com.google.firebase:firebase-bom to v33.5.0 (#1352) 2024-10-21 19:53:17 +00:00
e16c3953c7 Update dependency org.junit.jupiter:junit-jupiter to v5.11.3 (#1351) 2024-10-22 01:49:40 +06:00
f3a2f566c8 Pass uncaught exception to default handler in GlobalExceptionHandler
Fixes #1347
2024-10-19 22:51:01 +06:00
15e3f28aa3 Rework Firebase setup
Fixes #1332
Closes #1339
2024-10-19 21:22:04 +06:00
3bf70b230f Address deprecation, suggestion and spotless 2024-10-19 20:19:06 +06:00
eb3bea8150 Revert "Tweak Preference.collectAsState"
This reverts commit 3bddb55385.

Fixes #1341
2024-10-19 20:02:15 +06:00
5612ae0149 Update dependency androidx.compose:compose-bom to v2024.10.00 (#1338) 2024-10-19 20:00:56 +06:00
dbf6ad2ca7 Update xml.serialization.version to v0.90.2 (#1331)
* Update xml.serialization.version to v0.90.2

* Fix build

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-10-19 20:00:30 +06:00
d2afbfe4ed Change "Invalidate downloads index" to "Reindex downloads" 2024-10-19 17:06:29 +06:00
337806d9e1 Update dependency androidx.annotation:annotation to v1.9.0 (#1336) 2024-10-19 16:19:39 +06:00
443f6e0ae5 Update dependency androidx.glance:glance-appwidget to v1.1.1 (#1335) 2024-10-19 16:19:07 +06:00
572ee2f02a Update dependency androidx.benchmark:benchmark-macro-junit4 to v1.3.3 (#1334) 2024-10-19 16:18:38 +06:00
ba1343bed8 Update dependency androidx.activity:activity-compose to v1.9.3 (#1333) 2024-10-19 16:17:55 +06:00
9f3d5d13d4 [skip ci] Update i18n readme (#1328) 2024-10-15 19:14:52 +06:00
48166b9b52 Update dependency com.android.tools.build:gradle to v8.7.1 (#1326) 2024-10-15 05:02:45 +06:00
2e2c8d36c1 Make sure random library sort is at the bottom 2024-10-15 05:00:56 +06:00
788235feec Reorder reader menu overflow items 2024-10-15 03:57:58 +06:00
afa5002988 Cleanup .gitignore files 2024-10-15 03:39:48 +06:00
9503082d44 Fix PR build check 2024-10-15 02:13:37 +06:00
de36357da8 Add option to backup non-library read entries (#1324)
Co-authored-by: jobobby04 <jobobby04@gmail.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-10-14 16:30:23 +00:00
eb6092bd0c Adjust expandable fab animation
Co-authored-by: p
2024-10-13 23:06:02 +06:00
32d2c2ac1b Refrain from running spotless on weblate files
Those are akin to generated files and are likely to not follow our formatting
2024-10-13 23:02:35 +06:00
4051f180a2 Run PR check when base strings are changed 2024-10-13 20:50:35 +06:00
3ed8a91c7b Fix EnhancedTracker not auto binding when adding manga to library (#1298) 2024-10-13 20:32:29 +06:00
87db3f90de Confirmation dialog when removing privately installed extensions (#1320)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-10-13 18:48:00 +06:00
0a4ad89b99 Update dependency me.zhanghai.android.libarchive:library to v1.1.3 (#1321) 2024-10-13 18:47:31 +06:00
a72db41bf1 Added random library sort (#1317)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-10-13 13:51:34 +06:00
6b2bba4e54 Add Quantity Badge to Upcoming Screen (#1250)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-10-12 17:51:34 +06:00
7c7af72f8c Add option to opt out of Analytics and Crashlytics (#1237) 2024-10-12 06:46:28 +06:00
87 changed files with 938 additions and 331 deletions

View File

@ -53,7 +53,7 @@ body:
label: Mihon version
description: You can find your Mihon version in **More → About**.
placeholder: |
Example: "0.16.5"
Example: "0.17.0"
validations:
required: true
@ -96,7 +96,7 @@ body:
required: true
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[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
- label: I have updated all installed extensions.
required: true

View File

@ -31,7 +31,7 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- label: I have updated the app to version **[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
- label: I will fill out all of the requested information in this form.
required: true

View File

@ -2,5 +2,12 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"labels": ["Dependencies"],
"semanticCommits": "disabled"
"semanticCommits": "disabled",
"packageRules": [
{
"groupName": "GitHub Actions",
"matchManagers": ["github-actions"],
"pinDigests": true,
}
]
}

View File

@ -1,10 +1,13 @@
name: PR build check
on:
pull_request:
paths-ignore:
- '**.md'
- 'i18n/src/commonMain/moko-resources/**/strings.xml'
- 'i18n/src/commonMain/moko-resources/**/plurals.xml'
paths:
- '**'
- '!**.md'
- '!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:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
@ -20,16 +23,16 @@ jobs:
steps:
- name: Clone repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Dependency Review
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
uses: actions/dependency-review-action@a6993e2c61fd5dc440b409aa1d6904921c5e1894 # v4.3.5
- name: Set up JDK
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
java-version: 17
distribution: adopt
@ -41,13 +44,13 @@ jobs:
run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
- name: Upload APK
uses: actions/upload-artifact@v4
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@v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/standardRelease

View File

@ -17,7 +17,7 @@ jobs:
steps:
- name: Clone repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
@ -27,7 +27,7 @@ jobs:
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
- name: Set up JDK
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
java-version: 17
distribution: adopt
@ -39,13 +39,13 @@ jobs:
run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
- name: Upload APK
uses: actions/upload-artifact@v4
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@v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/standardRelease

22
.gitignore vendored
View File

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

View File

@ -2,133 +2,267 @@
All notable changes to this project will be documented in this file.
The format is based on [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).
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]
## [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), [@null2264](https://github.com/null2264)) ([#949](https://github.com/mihonapp/mihon/pull/949), [#967](https://github.com/mihonapp/mihon/pull/967))
- 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))
- Save global search "Has result" choice ([@AntsyLich](https://github.com/AntsyLich)) ([`5a61ca5`](https://github.com/mihonapp/mihon/commit/5a61ca5535fe0d9e8e7bcb9e665ba2f9cb0cf649))
- 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 extracting files to internal storage ([@FooIbar](https://github.com/FooIbar)) ([#326](https://github.com/mihonapp/mihon/pull/326))
- Try to get resource from Extension before checking in the app ([@beer-psi](https://github.com/beer-psi)) ([#433](https://github.com/mihonapp/mihon/pull/433))
- Default user agent ([@AntsyLich](https://github.com/AntsyLich)) ([`8160b47`](https://github.com/mihonapp/mihon/commit/8160b47ff5fbbd9b32caeb462b5be881fabd3449))
- 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 ([@AntsyLich](https://github.com/AntsyLich), [@Animeboynz](https://github.com/Animeboynz) ([#570](https://github.com/mihonapp/mihon/pull/570), [#1057](https://github.com/mihonapp/mihon/pull/1057))
- 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))
### Improvement
- Long strip reader performance ([@FooIbar](https://github.com/FooIbar), [@wwww-wwww](https://github.com/wwww-wwww)) ([#687](https://github.com/mihonapp/mihon/pull/687))
### 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
- Creating `ComicInfo.xml` file for local source ([@FooIbar](https://github.com/FooIbar)) ([#325](https://github.com/mihonapp/mihon/pull/325))
- 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))
- Occasional black bar when scrolling in long strip reader ([@FooIbar](https://github.com/FooIbar)) ([#563](https://github.com/mihonapp/mihon/pull/563))
- 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))
- 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 0 item but only sticky header ([@cuong-tran](https://github.com/cuong-tran)) ([#1083](https://github.com/mihonapp/mihon/pull/1083))
- 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
- Setting to install custom color profiles to get true colors ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
- 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
- Fix 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), [#415](https://github.com/mihonapp/mihon/pull/415))
- Fix app infinitely retries tracker update instead of failing after 3 tries ([@MajorTanya](https://github.com/MajorTanya)) ([#411](https://github.com/mihonapp/mihon/pull/411))
- Fix crash on Pixel devices ([`ab06720`](https://github.com/mihonapp/mihon/commit/ab067209661eceefc04c65f6bdbfcaa8a1264651))
- Fix crash when opening some heif/heic images ([@az4521](https://github.com/az4521)) ([#466](https://github.com/mihonapp/mihon/pull/466))
- Fix crash in track date selection dialog ([@ivaniskandar](https://github.com/ivaniskandar)) ([`c348fac`](https://github.com/mihonapp/mihon/commit/c348fac78fac479fb123bd617c01c78b9ca851d5))
- Fix dates for saved images on Samsung devices ([@MajorTanya](https://github.com/MajorTanya)) ([#552](https://github.com/mihonapp/mihon/pull/552))
- Fix colors getting distorted when opening CMYK jpeg images ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
- 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-26
### Fixed
- Circumvent MAL block ([@AntsyLich](https://github.com/AntsyLich)) ([`085ad8d`](https://github.com/mihonapp/mihon/commit/085ad8d44637c375a8ed24aba3a6f75f5b0cc9ee))
## [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
- Rename extension update error file to `mihon_update_errors.txt` ([@mjishnu](https://github.com/mjishnu)) ([#253](https://github.com/mihonapp/mihon/pull/253))
- Hide display cutoff setting in reader settings sheet if fullscreen is off ([@Riztard](https://github.com/Riztard)) ([#241](https://github.com/mihonapp/mihon/pull/241))
- 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
- Fix bottom sheet display issues on non-Tablet UI ([@theolm](https://github.com/theolm)) ([#182](https://github.com/mihonapp/mihon/pull/182))
- Fix crash when switching screen while a list is scrolling ([@theolm](https://github.com/theolm)) ([#272](https://github.com/mihonapp/mihon/pull/272))
- Fix newly installed extensions not being recognized by Mihon ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#275](https://github.com/mihonapp/mihon/pull/275))
- Fix error handling when refreshing MAL OAuth token ([@AntsyLich](https://github.com/AntsyLich)) ([`0f4de03`](https://github.com/mihonapp/mihon/commit/0f4de03d7a77b52490dc9a95e96a308b93b26e4f))
- 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
### Added
- Scanlator filter is now part of Backup ([@jobobby04](https://github.com/jobobby04)) ([#166](https://github.com/mihonapp/mihon/pull/166))
### Changed
- Rename crash log filename to `mihon_crash_logs.txt` ([@MajorTanya](https://github.com/MajorTanya)) ([#234](https://github.com/mihonapp/mihon/pull/234))
- 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 goes blank ([@AntsyLich](https://github.com/AntsyLich)) ([`38d6ab8`](https://github.com/mihonapp/mihon/commit/38d6ab80ce868707829dbc81de4170afe3c2f2a5))
- App icon scaling ([@AntsyLich](https://github.com/AntsyLich)) ([`26815c7`](https://github.com/mihonapp/mihon/commit/26815c7356111394665467c1e81255ac9ee33c1a))
- "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 with some languages in "Data and storage" ([@theolm](https://github.com/theolm)) ([#202](https://github.com/mihonapp/mihon/pull/202))
- Fix chapter not being marked as read in some cases with Enhanced Trackers ([@Secozzi](https://github.com/Secozzi)) ([#219](https://github.com/mihonapp/mihon/pull/219))
- And various tracker related fixes ([@AntsyLich](https://github.com/AntsyLich), [@kitsumed](https://github.com/kitsumed), [@Secozzi](https://github.com/Secozzi)) ([`a024218`](https://github.com/mihonapp/mihon/commit/a024218410953a389b8af4880fa7ae6cc30124a2), [`e3f33e2`](https://github.com/mihonapp/mihon/commit/e3f33e24f5e928ac8a85d1f500fd42d4715fc6b5), [`32188f9`](https://github.com/mihonapp/mihon/commit/32188f9f65009a18250674ef1bd6e57d351c1fba))
- 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 filled ([@AntsyLich](https://github.com/AntsyLich)) ([`1849715`](https://github.com/mihonapp/mihon/commit/18497154183356bb0d469b27827f9f7d6b7a3130))
- 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))
"The end of 立ち読み (Tachiyomi) is the beginning of みほん (Mihon)"
Credit to LinkCable, the icon designer, for this poetic quote.
What's New?
Well, nothing, except you now you need Android 8+ to install the app.
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.16.5...HEAD
[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/releases/tag/v0.16.0
[v0.16.0]: https://github.com/mihonapp/mihon/compare/a9c7cbf...v0.16.0

3
app/.gitignore vendored
View File

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

View File

@ -28,8 +28,8 @@ android {
defaultConfig {
applicationId = "app.mihon"
versionCode = 7
versionName = "0.16.5"
versionCode = 8
versionName = "0.17.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")

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

@ -5,7 +5,7 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
fun <T : R, R : Any> List<T>.insertSeparators(
generator: (T?, T?) -> R?,
generator: (before: T?, after: T?) -> R?,
): List<R> {
if (isEmpty()) return emptyList()
val newList = mutableListOf<R>()
@ -19,6 +19,24 @@ fun <T : R, R : Any> List<T>.insertSeparators(
return newList
}
/**
* Similar to [eu.kanade.core.util.insertSeparators] but iterates from last to first element
*/
fun <T : R, R : Any> List<T>.insertSeparatorsReversed(
generator: (before: T?, after: T?) -> R?,
): List<R> {
if (isEmpty()) return emptyList()
val newList = mutableListOf<R>()
for (i in size downTo 0) {
val after = getOrNull(i)
after?.let(newList::add)
val before = getOrNull(i - 1)
val separator = generator.invoke(before, after)
separator?.let(newList::add)
}
return newList.asReversed()
}
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
if (shouldAdd) {
add(value)

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.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import logcat.LogPriority
@ -14,17 +15,16 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.time.ZoneOffset
class AddTracks(
private val getTracks: GetTracks,
private val insertTrack: InsertTrack,
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
private val getChaptersByMangaId: GetChaptersByMangaId,
private val trackerManager: TrackerManager,
) {
// TODO: update all trackers based on common data
@ -79,7 +79,7 @@ class AddTracks(
suspend fun bindEnhancedTrackers(manga: Manga, source: Source) = withNonCancellableContext {
withIOContext {
getTracks.await(manga.id)
trackerManager.loggedInTrackers()
.filterIsInstance<EnhancedTracker>()
.filter { it.accept(source) }
.forEach { service ->
@ -87,11 +87,11 @@ class AddTracks(
service.match(manga)?.let { track ->
track.manga_id = manga.id
(service as Tracker).bind(track)
insertTrack.await(track.toDomainTrack()!!)
insertTrack.await(track.toDomainTrack(idRequired = false)!!)
syncChapterProgressWithTrack.await(
manga.id,
track.toDomainTrack()!!,
track.toDomainTrack(idRequired = false)!!,
service,
)
}

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
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.anilist.Anilist
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum
class TrackPreferences(
private val preferenceStore: PreferenceStore,
@ -35,4 +37,9 @@ class TrackPreferences(
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum(
"pref_auto_update_manga_on_mark_read",
AutoTrackState.ALWAYS,
)
}

View File

@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -27,6 +29,7 @@ import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.library.model.sort
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.BaseSortItem
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.HeadingItem
import tachiyomi.presentation.core.components.SettingsChipRow
@ -163,22 +166,38 @@ private fun ColumnScope.SortPage(
val sortingMode = category.sort.type
val sortDescending = !category.sort.isAscending
val trackerSortOption = if (trackers.isEmpty()) {
emptyList()
} else {
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
val options = remember(trackers.isEmpty()) {
val trackerMeanPair = if (trackers.isNotEmpty()) {
MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean
} else {
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(
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
MR.strings.action_sort_total to LibrarySort.Type.TotalChapters,
MR.strings.action_sort_last_read to LibrarySort.Type.LastRead,
MR.strings.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
MR.strings.action_sort_unread_count to LibrarySort.Type.UnreadCount,
MR.strings.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
MR.strings.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
MR.strings.action_sort_date_added to LibrarySort.Type.DateAdded,
).plus(trackerSortOption).map { (titleRes, mode) ->
options.map { (titleRes, mode) ->
if (mode == LibrarySort.Type.Random) {
BaseSortItem(
label = stringResource(titleRes),
icon = Icons.Default.Refresh
.takeIf { sortingMode == LibrarySort.Type.Random },
onClick = {
screenModel.setSort(category, mode, LibrarySort.Direction.Ascending)
},
)
return@map
}
SortItem(
label = stringResource(titleRes),
sortDescending = sortDescending.takeIf { sortingMode == mode },

View File

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

View File

@ -15,7 +15,6 @@ import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences
@ -35,7 +34,7 @@ object SettingsDownloadScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetCategories>() }
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() })
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
return listOf(

View File

@ -24,7 +24,6 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.ResetCategoryFlags
import tachiyomi.domain.category.model.Category
@ -53,7 +52,7 @@ object SettingsLibraryScreen : SearchableSettings {
override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetCategories>() }
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() })
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
return listOf(
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),

View File

@ -7,6 +7,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.fragment.app.FragmentActivity
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.util.system.AuthenticatorUtil.authenticate
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
@ -28,55 +29,91 @@ object SettingsSecurityScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val context = LocalContext.current
val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
val authSupported = remember { context.isAuthenticationSupported() }
val privacyPreferences = remember { Injekt.get<PrivacyPreferences>() }
return listOf(
getSecurityGroup(securityPreferences),
getFirebaseGroup(privacyPreferences),
)
}
@Composable
private fun getSecurityGroup(
securityPreferences: SecurityPreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
val authSupported = remember { context.isAuthenticationSupported() }
val useAuthPref = securityPreferences.useAuthenticator()
val useAuth by useAuthPref.collectAsState()
return listOf(
Preference.PreferenceItem.SwitchPreference(
pref = useAuthPref,
title = stringResource(MR.strings.lock_with_biometrics),
enabled = authSupported,
onValueChanged = {
(context as FragmentActivity).authenticate(
title = context.stringResource(MR.strings.lock_with_biometrics),
)
},
),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.lockAppAfter(),
title = stringResource(MR.strings.lock_when_idle),
enabled = authSupported && useAuth,
entries = LockAfterValues
.associateWith {
when (it) {
-1 -> stringResource(MR.strings.lock_never)
0 -> stringResource(MR.strings.lock_always)
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_security),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = useAuthPref,
title = stringResource(MR.strings.lock_with_biometrics),
enabled = authSupported,
onValueChanged = {
(context as FragmentActivity).authenticate(
title = context.stringResource(MR.strings.lock_with_biometrics),
)
},
),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.lockAppAfter(),
title = stringResource(MR.strings.lock_when_idle),
enabled = authSupported && useAuth,
entries = LockAfterValues
.associateWith {
when (it) {
-1 -> stringResource(MR.strings.lock_never)
0 -> stringResource(MR.strings.lock_always)
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
}
}
}
.toImmutableMap(),
onValueChanged = {
(context as FragmentActivity).authenticate(
title = context.stringResource(MR.strings.lock_when_idle),
)
},
.toImmutableMap(),
onValueChanged = {
(context as FragmentActivity).authenticate(
title = context.stringResource(MR.strings.lock_when_idle),
)
},
),
Preference.PreferenceItem.SwitchPreference(
pref = securityPreferences.hideNotificationContent(),
title = stringResource(MR.strings.hide_notification_content),
),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.secureScreen(),
title = stringResource(MR.strings.secure_screen),
entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
),
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.unit.dp
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.data.track.EnhancedTracker
@ -53,6 +54,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentMap
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.domain.source.service.SourceManager
@ -85,6 +87,7 @@ object SettingsTrackingScreen : SearchableSettings {
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
val trackerManager = remember { Injekt.get<TrackerManager>() }
val sourceManager = remember { Injekt.get<SourceManager>() }
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead()
var dialog by remember { mutableStateOf<Any?>(null) }
dialog?.run {
@ -125,6 +128,13 @@ object SettingsTrackingScreen : SearchableSettings {
pref = trackPreferences.autoUpdateTrack(),
title = stringResource(MR.strings.pref_auto_update_manga_sync),
),
Preference.PreferenceItem.ListPreference(
pref = trackPreferences.autoUpdateTrackOnMarkRead(),
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
entries = AutoTrackState.entries
.associateWith { stringResource(it.titleRes) }
.toPersistentMap(),
),
Preference.PreferenceGroup(
title = stringResource(MR.strings.services),
preferenceItems = persistentListOf(

View File

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

View File

@ -4,6 +4,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@ -16,7 +17,6 @@ import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection
@ -38,8 +39,8 @@ import androidx.compose.ui.unit.dp
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.presentation.util.isTabletUi
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Slider
import tachiyomi.presentation.core.i18n.stringResource
import kotlin.math.roundToInt
@Composable
fun ChapterNavigator(
@ -50,7 +51,7 @@ fun ChapterNavigator(
enabledPrevious: Boolean,
currentPage: Int,
totalPages: Int,
onSliderValueChange: (Int) -> Unit,
onPageIndexChange: (Int) -> Unit,
) {
val isTabletUi = isTabletUi()
val horizontalPadding = if (isTabletUi) 24.dp else 8.dp
@ -97,7 +98,11 @@ fun ChapterNavigator(
.padding(horizontal = 16.dp),
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 sliderDragged by interactionSource.collectIsDraggedAsState()
@ -110,14 +115,11 @@ fun ChapterNavigator(
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp),
value = currentPage.toFloat(),
valueRange = 1f..totalPages.toFloat(),
steps = totalPages - 2,
onValueChange = {
val new = it.roundToInt() - 1
if (new != currentPage) {
onSliderValueChange(new)
}
value = currentPage,
valueRange = 1..totalPages,
onValueChange = f@{
if (it == currentPage) return@f
onPageIndexChange(it - 1)
},
interactionSource = interactionSource,
)
@ -158,7 +160,7 @@ private fun ChapterNavigatorPreview() {
enabledPrevious = true,
currentPage = currentPage,
totalPages = 10,
onSliderValueChange = { currentPage = it },
onPageIndexChange = { currentPage = (it + 1) },
)
}
}

View File

@ -26,6 +26,7 @@ import eu.kanade.domain.DomainModule
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences
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.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
@ -50,6 +51,7 @@ import kotlinx.coroutines.flow.onEach
import logcat.AndroidLogcatLogger
import logcat.LogPriority
import logcat.LogcatLogger
import mihon.core.firebase.FirebaseConfig
import mihon.core.migration.Migrator
import mihon.core.migration.migrations.migrations
import org.conscrypt.Conscrypt
@ -67,6 +69,7 @@ import java.security.Security
class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory {
private val basePreferences: BasePreferences by injectLazy()
private val privacyPreferences: PrivacyPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver()
@ -75,6 +78,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
override fun onCreate() {
super<Application>.onCreate()
patchInjekt()
FirebaseConfig.init(applicationContext)
GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java)
@ -97,6 +101,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
val scope = ProcessLifecycleOwner.get().lifecycleScope
// Show notification to disable Incognito Mode when it's enabled
basePreferences.incognitoMode().changes()
.onEach { enabled ->
@ -124,14 +130,22 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
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())
// Updates widget update
with(WidgetManager(Injekt.get(), Injekt.get())) {
init(ProcessLifecycleOwner.get().lifecycleScope)
}
WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) }
if (!LogcatLogger.isInstalled && networkPreferences.verboseLogging().get()) {
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))

View File

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

View File

@ -27,6 +27,7 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -43,6 +44,7 @@ class BackupCreator(
private val parser: ProtoBuf = Injekt.get(),
private val getFavorites: GetFavorites = Injekt.get(),
private val backupPreferences: BackupPreferences = Injekt.get(),
private val mangaRepository: MangaRepository = Injekt.get(),
private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(),
private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(),
@ -75,7 +77,9 @@ class BackupCreator(
throw IllegalStateException(context.stringResource(MR.strings.create_backup_file_error))
}
val backupManga = backupMangas(getFavorites.await(), options)
val nonFavoriteManga = if (options.readEntries) mangaRepository.getReadMangaNotInLibrary() else emptyList()
val backupManga = backupMangas(getFavorites.await() + nonFavoriteManga, options)
val backup = Backup(
backupManga = backupManga,
backupCategories = backupCategories(options),

View File

@ -10,6 +10,7 @@ data class BackupOptions(
val chapters: Boolean = true,
val tracking: Boolean = true,
val history: Boolean = true,
val readEntries: Boolean = true,
val appSettings: Boolean = true,
val extensionRepoSettings: Boolean = true,
val sourceSettings: Boolean = true,
@ -22,6 +23,7 @@ data class BackupOptions(
chapters,
tracking,
history,
readEntries,
appSettings,
extensionRepoSettings,
sourceSettings,
@ -60,6 +62,12 @@ data class BackupOptions(
getter = BackupOptions::categories,
setter = { options, enabled -> options.copy(categories = enabled) },
),
Entry(
label = MR.strings.non_library_settings,
getter = BackupOptions::readEntries,
setter = { options, enabled -> options.copy(readEntries = enabled) },
enabled = { it.libraryEntries },
),
)
val settingsOptions = persistentListOf(
@ -92,10 +100,11 @@ data class BackupOptions(
chapters = array[2],
tracking = array[3],
history = array[4],
appSettings = array[5],
extensionRepoSettings = array[6],
sourceSettings = array[7],
privateSettings = array[8],
readEntries = array[5],
appSettings = array[6],
extensionRepoSettings = array[7],
sourceSettings = array[8],
privateSettings = array[9],
)
}

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.database.models

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.database.models

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.database.models

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.database.models

View File

@ -96,13 +96,13 @@ class DownloadCache(
private val diskCacheFile: File
get() = File(context.cacheDir, "dl_index_cache_v3")
private val rootDownloadsDirLock = Mutex()
private val rootDownloadsDirMutex = Mutex()
private var rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory())
init {
// Attempt to read cache file
scope.launch {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
try {
if (diskCacheFile.exists()) {
val diskCache = diskCacheFile.inputStream().use {
@ -112,7 +112,7 @@ class DownloadCache(
lastRenew = System.currentTimeMillis()
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Failed to initialize disk cache" }
logcat(LogPriority.ERROR, e) { "Failed to initialize from disk cache" }
diskCacheFile.delete()
}
}
@ -198,7 +198,7 @@ class DownloadCache(
* @param manga the manga of the chapter.
*/
suspend fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
// Retrieve the cached source directory or cache a new one
var sourceDir = rootDownloadsDir.sourceDirs[manga.source]
if (sourceDir == null) {
@ -230,7 +230,7 @@ class DownloadCache(
* @param manga the manga of the chapter.
*/
suspend fun removeChapter(chapter: Chapter, manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
@ -250,7 +250,7 @@ class DownloadCache(
* @param manga the manga of the chapter.
*/
suspend fun removeChapters(chapters: List<Chapter>, manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return
chapters.forEach { chapter ->
@ -271,7 +271,7 @@ class DownloadCache(
* @param manga the manga to remove.
*/
suspend fun removeManga(manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDirName = provider.getMangaDirName(manga.title)
if (sourceDir.mangaDirs.containsKey(mangaDirName)) {
@ -283,7 +283,7 @@ class DownloadCache(
}
suspend fun removeSource(source: Source) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
rootDownloadsDir.sourceDirs -= source.id
}
@ -322,10 +322,10 @@ class DownloadCache(
val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id }
rootDownloadsDirLock.withLock {
rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory())
rootDownloadsDirMutex.withLock {
val updatedRootDir = RootDirectory(storageManager.getDownloadsDirectory())
val sourceDirs = rootDownloadsDir.dir?.listFiles().orEmpty()
updatedRootDir.sourceDirs = updatedRootDir.dir?.listFiles().orEmpty()
.filter { it.isDirectory && !it.name.isNullOrBlank() }
.mapNotNull { dir ->
val sourceId = sourceMap[dir.name!!.lowercase()]
@ -333,36 +333,35 @@ class DownloadCache(
}
.toMap()
rootDownloadsDir.sourceDirs = sourceDirs
updatedRootDir.sourceDirs.values.map { sourceDir ->
async {
sourceDir.mangaDirs = sourceDir.dir?.listFiles().orEmpty()
.filter { it.isDirectory && !it.name.isNullOrBlank() }
.associate { it.name!! to MangaDirectory(it) }
sourceDirs.values
.map { sourceDir ->
async {
sourceDir.mangaDirs = sourceDir.dir?.listFiles().orEmpty()
.filter { it.isDirectory && !it.name.isNullOrBlank() }
.associate { it.name!! to MangaDirectory(it) }
sourceDir.mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir?.listFiles().orEmpty()
.mapNotNull {
when {
// Ignore incomplete downloads
it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true -> null
// Folder of images
it.isDirectory -> it.name
// CBZ files
it.isFile && it.extension == "cbz" -> it.nameWithoutExtension
// Anything else is irrelevant
else -> null
}
sourceDir.mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir?.listFiles().orEmpty()
.mapNotNull {
when {
// Ignore incomplete downloads
it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true -> null
// Folder of images
it.isDirectory -> it.name
// CBZ files
it.isFile && it.extension == "cbz" -> it.nameWithoutExtension
// Anything else is irrelevant
else -> null
}
.toMutableSet()
}
.toMutableSet()
mangaDir.chapterDirs = chapterDirs
}
mangaDir.chapterDirs = chapterDirs
}
}
}
.awaitAll()
rootDownloadsDir = updatedRootDir
}
_isInitializing.emit(false)

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.track.model

View File

@ -5,6 +5,7 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
@ -39,6 +40,9 @@ class PreferenceModule(val app: Application) : InjektModule {
addSingletonFactory {
SecurityPreferences(get())
}
addSingletonFactory {
PrivacyPreferences(get())
}
addSingletonFactory {
LibraryPreferences(get())
}

View File

@ -1,8 +1,15 @@
package eu.kanade.tachiyomi.ui.browse.extension
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.ExtensionScreen
@ -12,6 +19,7 @@ import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
import eu.kanade.tachiyomi.util.system.isPackageInstalled
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
@ -21,7 +29,10 @@ fun extensionsTab(
extensionsScreenModel: ExtensionsScreenModel,
): TabContent {
val navigator = LocalNavigator.currentOrThrow
val context = LocalContext.current
val state by extensionsScreenModel.state.collectAsState()
var privateExtensionToUninstall by remember { mutableStateOf<Extension?>(null) }
return TabContent(
titleRes = MR.strings.label_extensions,
@ -45,7 +56,13 @@ fun extensionsTab(
onLongClickItem = { extension ->
when (extension) {
is Extension.Available -> extensionsScreenModel.installExtension(extension)
else -> extensionsScreenModel.uninstallExtension(extension)
else -> {
if (context.isPackageInstalled(extension.pkgName)) {
extensionsScreenModel.uninstallExtension(extension)
} else {
privateExtensionToUninstall = extension
}
}
}
},
onClickItemCancel = extensionsScreenModel::cancelInstallUpdateExtension,
@ -68,6 +85,50 @@ fun extensionsTab(
onUpdateExtension = extensionsScreenModel::updateExtension,
onRefresh = extensionsScreenModel::findAvailableExtensions,
)
privateExtensionToUninstall?.let { extension ->
ExtensionUninstallConfirmation(
extensionName = extension.name,
onClickConfirm = {
extensionsScreenModel.uninstallExtension(extension)
},
onDismissRequest = {
privateExtensionToUninstall = null
},
)
}
},
)
}
@Composable
private fun ExtensionUninstallConfirmation(
extensionName: String,
onClickConfirm: () -> Unit,
onDismissRequest: () -> Unit,
) {
AlertDialog(
title = {
Text(text = stringResource(MR.strings.ext_confirm_remove))
},
text = {
Text(text = stringResource(MR.strings.remove_private_extension_message, extensionName))
},
confirmButton = {
TextButton(
onClick = {
onClickConfirm()
onDismissRequest()
},
) {
Text(text = stringResource(MR.strings.ext_remove))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
onDismissRequest = onDismissRequest,
)
}

View File

@ -72,6 +72,7 @@ import tachiyomi.domain.track.model.Track
import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.random.Random
/**
* Typealias for the library manga, using the category as keys, and list of manga as values.
@ -300,10 +301,17 @@ class LibraryScreenModel(
val item2Score = trackerScores[i2.libraryManga.id] ?: defaultTrackerScoreSortValue
item1Score.compareTo(item2Score)
}
LibrarySort.Type.Random -> {
error("Why Are We Still Here? Just To Suffer?")
}
}
}
return mapValues { (key, value) ->
if (key.sort.type == LibrarySort.Type.Random) {
return@mapValues value.shuffled(Random(libraryPreferences.randomSortSeed().get()))
}
val comparator = key.sort.comparator()
.let { if (key.sort.isAscending) it else it.reversed() }
.thenComparator(sortAlphabetically)

View File

@ -278,12 +278,13 @@ class MainActivity : BaseActivity() {
@Composable
private fun HandleOnNewIntent(context: Context, navigator: Navigator) {
LaunchedEffect(Unit) {
callbackFlow<Intent> {
callbackFlow {
val componentActivity = context as ComponentActivity
val consumer = Consumer<Intent> { trySend(it) }
componentActivity.addOnNewIntentListener(consumer)
awaitClose { componentActivity.removeOnNewIntentListener(consumer) }
}.collectLatest { handleIntentAction(it, navigator) }
}
.collectLatest { handleIntentAction(it, navigator) }
}
}
@ -339,6 +340,7 @@ class MainActivity : BaseActivity() {
* When custom animation is used, status and navigation bar color will be set to transparent and will be restored
* after the animation is finished.
*/
@Suppress("Deprecation")
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
val root = findViewById<View>(android.R.id.content)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) {

View File

@ -25,6 +25,8 @@ import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.track.interactor.TrackChapter
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.util.formattedMessage
@ -38,6 +40,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.chapter.getNextUnread
import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.async
@ -92,6 +95,7 @@ class MangaScreenModel(
private val mangaId: Long,
private val isFromSource: Boolean,
private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val trackPreferences: TrackPreferences = Injekt.get(),
readerPreferences: ReaderPreferences = Injekt.get(),
private val trackerManager: TrackerManager = Injekt.get(),
private val trackChapter: TrackChapter = Injekt.get(),
@ -137,6 +141,7 @@ class MangaScreenModel(
val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get()
val chapterSwipeEndAction = libraryPreferences.swipeToStartAction().get()
var autoTrackState = trackPreferences.autoUpdateTrackOnMarkRead().get()
private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope)
@ -731,13 +736,25 @@ class MangaScreenModel(
chapters = chapters.toTypedArray(),
)
if (!read) return@launchIO
if (
successState?.hasLoggedInTrackers == false ||
!read || autoTrackState == AutoTrackState.NEVER
) {
return@launchIO
}
val tracks = getTracks.await(mangaId)
val maxChapterNumber = chapters.maxOf { it.chapterNumber }
val shouldPromptTrackingUpdate = tracks.any { track -> maxChapterNumber > track.lastChapterRead }
if (!shouldPromptTrackingUpdate) return@launchIO
if (autoTrackState == AutoTrackState.ALWAYS) {
trackChapter.await(context, mangaId, maxChapterNumber)
withUIContext {
context.toast(context.stringResource(MR.strings.trackers_updated_summary, maxChapterNumber.toInt()))
}
return@launchIO
}
val result = snackbarHostState.showSnackbar(
message = context.stringResource(MR.strings.confirm_tracker_update, maxChapterNumber.toInt()),

View File

@ -824,7 +824,11 @@ private data class TrackerRemoveScreen(
fun deleteMangaFromService() {
screenModelScope.launchNonCancellable {
(tracker as DeletableTracker).delete(track)
try {
(tracker as DeletableTracker).delete(track)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to delete entry from service" }
}
}
}

View File

@ -390,8 +390,8 @@ class ReaderActivity : BaseActivity() {
onClickTopAppBar = ::openMangaScreen,
bookmarked = state.bookmarked,
onToggleBookmarked = viewModel::toggleChapterBookmark,
onOpenInBrowser = ::openChapterInBrowser.takeIf { isHttpSource },
onOpenInWebView = ::openChapterInWebView.takeIf { isHttpSource },
onOpenInBrowser = ::openChapterInBrowser.takeIf { isHttpSource },
onShare = ::shareChapter.takeIf { isHttpSource },
viewer = state.viewer,
@ -401,7 +401,7 @@ class ReaderActivity : BaseActivity() {
enabledPrevious = state.viewerChapters?.prevChapter != null,
currentPage = state.currentPage,
totalPages = state.totalPages,
onSliderValueChange = {
onPageIndexChange = {
isScrollingThroughPages = true
moveToPageIndex(it)
},
@ -565,12 +565,6 @@ class ReaderActivity : BaseActivity() {
}
}
private fun openChapterInBrowser() {
assistUrl?.let {
openInBrowser(it.toUri(), forceDefaultBrowser = false)
}
}
private fun openChapterInWebView() {
val manga = viewModel.manga ?: return
val source = viewModel.getSource() ?: return
@ -580,6 +574,12 @@ class ReaderActivity : BaseActivity() {
}
}
private fun openChapterInBrowser() {
assistUrl?.let {
openInBrowser(it.toUri(), forceDefaultBrowser = false)
}
}
private fun shareChapter() {
assistUrl?.let {
val intent = it.toUri().toShareIntent(this, type = "text/plain")

View File

@ -1,18 +1,25 @@
package mihon.feature.upcoming
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material3.Badge
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.font.FontWeight
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
@ -27,9 +34,9 @@ import tachiyomi.core.common.Constants
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.ListGroupHeader
import tachiyomi.presentation.core.components.TwoPanelBox
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import java.time.LocalDate
import java.time.YearMonth
@ -99,6 +106,33 @@ private fun UpcomingToolbar() {
)
}
@Composable
private fun DateHeading(
date: LocalDate,
mangaCount: Int,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = relativeDateText(date),
modifier = Modifier
.padding(MaterialTheme.padding.small)
.padding(start = MaterialTheme.padding.small),
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.bodyMedium,
)
Badge(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary,
) {
Text("$mangaCount")
}
}
}
@Composable
private fun UpcomingScreenSmallImpl(
listState: LazyListState,
@ -140,7 +174,10 @@ private fun UpcomingScreenSmallImpl(
)
}
is UpcomingUIModel.Header -> {
ListGroupHeader(text = relativeDateText(item.date))
DateHeading(
date = item.date,
mangaCount = item.mangaCount,
)
}
}
}
@ -188,7 +225,10 @@ private fun UpcomingScreenLargeImpl(
)
}
is UpcomingUIModel.Header -> {
ListGroupHeader(text = relativeDateText(item.date))
DateHeading(
date = item.date,
mangaCount = item.mangaCount,
)
}
}
}

View File

@ -4,7 +4,7 @@ import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMapIndexedNotNull
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.util.insertSeparators
import eu.kanade.core.util.insertSeparatorsReversed
import eu.kanade.tachiyomi.util.lang.toLocalDate
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
@ -33,7 +33,7 @@ class UpcomingScreenModel(
val upcomingItems = it.toUpcomingUIModels()
state.copy(
items = upcomingItems,
events = it.toEvents(),
events = upcomingItems.toEvents(),
headerIndexes = upcomingItems.getHeaderIndexes(),
)
}
@ -42,13 +42,16 @@ class UpcomingScreenModel(
}
private fun List<Manga>.toUpcomingUIModels(): ImmutableList<UpcomingUIModel> {
var mangaCount = 0
return fastMap { UpcomingUIModel.Item(it) }
.insertSeparators { before, after ->
.insertSeparatorsReversed { before, after ->
if (after != null) mangaCount++
val beforeDate = before?.manga?.expectedNextUpdate?.toLocalDate()
val afterDate = after?.manga?.expectedNextUpdate?.toLocalDate()
if (beforeDate != afterDate && afterDate != null) {
UpcomingUIModel.Header(afterDate)
UpcomingUIModel.Header(afterDate, mangaCount).also { mangaCount = 0 }
} else {
null
}
@ -56,9 +59,9 @@ class UpcomingScreenModel(
.toImmutableList()
}
private fun List<Manga>.toEvents(): ImmutableMap<LocalDate, Int> {
return groupBy { it.expectedNextUpdate?.toLocalDate() ?: LocalDate.MAX }
.mapValues { it.value.size }
private fun List<UpcomingUIModel>.toEvents(): ImmutableMap<LocalDate, Int> {
return filterIsInstance<UpcomingUIModel.Header>()
.associate { it.date to it.mangaCount }
.toImmutableMap()
}

View File

@ -4,6 +4,6 @@ import tachiyomi.domain.manga.model.Manga
import java.time.LocalDate
sealed interface UpcomingUIModel {
data class Header(val date: LocalDate) : UpcomingUIModel
data class Header(val date: LocalDate, val mangaCount: Int) : UpcomingUIModel
data class Item(val manga: Manga) : UpcomingUIModel
}

View File

@ -20,6 +20,15 @@
tools:node="remove" />
<application>
<!-- Disable for manual opt-in -->
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<!-- Disable unnecessary stuff from Firebase -->
<meta-data
android:name="google_analytics_adid_collection_enabled"

View File

@ -0,0 +1,25 @@
package mihon.core.firebase
import android.content.Context
import com.google.firebase.FirebaseApp
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
object FirebaseConfig {
private lateinit var analytics: FirebaseAnalytics
private lateinit var crashlytics: FirebaseCrashlytics
fun init(context: Context) {
analytics = FirebaseAnalytics.getInstance(context)
FirebaseApp.initializeApp(context)
crashlytics = FirebaseCrashlytics.getInstance()
}
fun setAnalyticsEnabled(enabled: Boolean) {
analytics.setAnalyticsCollectionEnabled(enabled)
}
fun setCrashlyticsEnabled(enabled: Boolean) {
crashlytics.isCrashlyticsCollectionEnabled = enabled
}
}

1
buildSrc/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -6,6 +6,18 @@ plugins {
val libs = the<LibrariesForLibs>()
val xmlFormatExclude = buildList(2) {
add("**/build/**/*.xml")
projectDir
.resolve("src/commonMain/moko-resources")
.takeIf { it.isDirectory }
?.let(::fileTree)
?.matching { exclude("/base/**") }
?.let(::add)
}
.toTypedArray()
spotless {
kotlin {
target("**/*.kt", "**/*.kts")
@ -23,7 +35,7 @@ spotless {
}
format("xml") {
target("**/*.xml")
targetExclude("**/build/**/*.xml")
targetExclude(*xmlFormatExclude)
trimTrailingWhitespace()
endWithNewline()
}

View File

@ -1 +0,0 @@
/build

View File

@ -1 +0,0 @@
/build

View File

@ -1 +0,0 @@
/build

View File

@ -0,0 +1,11 @@
package eu.kanade.tachiyomi.core.security
import tachiyomi.core.common.preference.PreferenceStore
class PrivacyPreferences(
private val preferenceStore: PreferenceStore,
) {
fun crashlytics() = preferenceStore.getBoolean("crashlytics", true)
fun analytics() = preferenceStore.getBoolean("analytics", true)
}

View File

@ -1,4 +1,4 @@
@file:Suppress("FunctionName", "ktlint:standard:function-naming")
@file:Suppress("FunctionName")
package eu.kanade.tachiyomi.network

1
data/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -49,6 +49,10 @@ class MangaRepositoryImpl(
return handler.awaitList { mangasQueries.getFavorites(MangaMapper::mapManga) }
}
override suspend fun getReadMangaNotInLibrary(): List<Manga> {
return handler.awaitList { mangasQueries.getReadMangaNotInLibrary(MangaMapper::mapManga) }
}
override suspend fun getLibraryManga(): List<LibraryManga> {
return handler.awaitList { libraryViewQueries.library(MangaMapper::mapLibraryManga) }
}

View File

@ -78,6 +78,15 @@ SELECT *
FROM mangas
WHERE favorite = 1;
getReadMangaNotInLibrary:
SELECT *
FROM mangas
WHERE favorite = 0 AND _id IN (
SELECT DISTINCT chapters.manga_id
FROM chapters
WHERE read = 1 OR last_page_read != 0
);
getAllManga:
SELECT *
FROM mangas;

1
domain/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -6,6 +6,7 @@ import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.library.model.plus
import tachiyomi.domain.library.service.LibraryPreferences
import kotlin.random.Random
class SetSortModeForCategory(
private val preferences: LibraryPreferences,
@ -15,6 +16,9 @@ class SetSortModeForCategory(
suspend fun await(categoryId: Long?, type: LibrarySort.Type, direction: LibrarySort.Direction) {
val category = categoryId?.let { categoryRepository.get(it) }
val flags = (category?.flags ?: 0) + type + direction
if (type == LibrarySort.Type.Random) {
preferences.randomSortSeed().set(Random.nextInt())
}
if (category != null && preferences.categorizedDisplaySettings().get()) {
categoryRepository.updatePartial(
CategoryUpdate(

View File

@ -30,7 +30,8 @@ data class LibrarySort(
data object LatestChapter : Type(0b00010100)
data object ChapterFetchDate : Type(0b00011000)
data object DateAdded : Type(0b00011100)
data object TrackerMean : Type(0b000100000)
data object TrackerMean : Type(0b00100000)
data object Random : Type(0b00111100)
companion object {
fun valueOf(flag: Long): Type {
@ -77,6 +78,7 @@ data class LibrarySort(
Type.ChapterFetchDate,
Type.DateAdded,
Type.TrackerMean,
Type.Random,
)
}
val directions by lazy { setOf(Direction.Ascending, Direction.Descending) }
@ -104,6 +106,7 @@ data class LibrarySort(
"CHAPTER_FETCH_DATE" -> Type.ChapterFetchDate
"DATE_ADDED" -> Type.DateAdded
"TRACKER_MEAN" -> Type.TrackerMean
"RANDOM" -> Type.Random
else -> Type.Alphabetical
}
val ascending = if (values[1] == "ASCENDING") Direction.Ascending else Direction.Descending
@ -125,6 +128,7 @@ data class LibrarySort(
Type.ChapterFetchDate -> "CHAPTER_FETCH_DATE"
Type.DateAdded -> "DATE_ADDED"
Type.TrackerMean -> "TRACKER_MEAN"
Type.Random -> "RANDOM"
}
val direction = if (direction == Direction.Ascending) "ASCENDING" else "DESCENDING"
return "$type,$direction"

View File

@ -26,6 +26,8 @@ class LibraryPreferences(
LibrarySort.Serializer::deserialize,
)
fun randomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0)
fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0)
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)

View File

@ -17,6 +17,8 @@ interface MangaRepository {
suspend fun getFavorites(): List<Manga>
suspend fun getReadMangaNotInLibrary(): List<Manga>
suspend fun getLibraryManga(): List<LibraryManga>
fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>>

View File

@ -12,7 +12,7 @@ class LibraryFlagsTest {
@Test
fun `Check the amount of flags`() {
LibraryDisplayMode.values.size shouldBe 4
LibrarySort.types.size shouldBe 9
LibrarySort.types.size shouldBe 10
LibrarySort.directions.size shouldBe 2
}

View File

@ -1,5 +1,5 @@
[versions]
agp_version = "8.7.0"
agp_version = "8.7.1"
lifecycle_version = "2.8.6"
paging_version = "3.3.2"
interpolator_version = "1.0.0"
@ -7,7 +7,7 @@ interpolator_version = "1.0.0"
[libraries]
gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
annotation = "androidx.annotation:annotation:1.8.2"
annotation = "androidx.annotation:annotation:1.9.0"
appcompat = "androidx.appcompat:appcompat:1.7.0"
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
@ -28,7 +28,7 @@ paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pag
interpolator = { group = "androidx.interpolator", name = "interpolator", version.ref = "interpolator_version" }
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.3.2"
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.3.3"
test-ext = "androidx.test.ext:junit-ktx:1.2.1"
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.1"
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0"

View File

@ -1,8 +1,8 @@
[versions]
compose-bom = "2024.09.03"
compose-bom = "2024.10.00"
[libraries]
activity = "androidx.activity:activity-compose:1.9.2"
activity = "androidx.activity:activity-compose:1.9.3"
bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
foundation = { module = "androidx.compose.foundation:foundation" }
animation = { module = "androidx.compose.animation:animation" }
@ -15,4 +15,4 @@ ui-util = { module = "androidx.compose.ui:ui-util" }
material3-core = { module = "androidx.compose.material3:material3" }
material-icons = { module = "androidx.compose.material:material-icons-extended" }
glance = "androidx.glance:glance-appwidget:1.1.0"
glance = "androidx.glance:glance-appwidget:1.1.1"

View File

@ -1,7 +1,7 @@
[versions]
kotlin_version = "2.0.21"
serialization_version = "1.7.3"
xml_serialization_version = "0.86.3"
xml_serialization_version = "0.90.2"
[libraries]
reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin_version" }

View File

@ -4,13 +4,13 @@ leakcanary = "2.14"
moko = "0.24.2"
okhttp_version = "5.0.0-alpha.14"
richtext = "0.20.0"
shizuku_version = "12.2.0"
shizuku_version = "13.1.0"
sqldelight = "2.0.2"
sqlite = "2.4.0"
voyager = "1.0.0"
spotless = "6.25.0"
ktlint-core = "1.3.1"
firebase-bom = "33.4.0"
ktlint-core = "1.4.0"
firebase-bom = "33.5.1"
[libraries]
desugar = "com.android.tools:desugar_jdk_libs:2.1.2"
@ -32,7 +32,7 @@ jsoup = "org.jsoup:jsoup:1.18.1"
disklrucache = "com.jakewharton:disklrucache:2.0.2"
unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc"
libarchive = "me.zhanghai.android.libarchive:library:1.1.2"
libarchive = "me.zhanghai.android.libarchive:library:1.1.4"
sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" }
sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" }
@ -89,7 +89,7 @@ sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jv
sqldelight-android-paging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" }
sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" }
junit = "org.junit.jupiter:junit-jupiter:5.11.2"
junit = "org.junit.jupiter:junit-jupiter:5.11.3"
kotest-assertions = "io.kotest:kotest-assertions-core:5.9.1"
mockk = "io.mockk:mockk:1.13.13"

0
i18n/.gitignore vendored
View File

View File

@ -2,4 +2,4 @@
This module houses the string resources and translations.
Original English strings are manged in `src/commonMain/resources/MR/base/`. Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#translation) for more details.
Original English strings are managed in `src/commonMain/moko-resources/base/`. Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#translation) for more details.

View File

@ -69,6 +69,7 @@
<string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
<string name="action_sort_date_added">Date added</string>
<string name="action_sort_tracker_score">Tracker score</string>
<string name="action_sort_random">Random</string>
<string name="action_search">Search</string>
<string name="action_search_hint">Search…</string>
<string name="action_search_settings">Search settings</string>
@ -190,6 +191,10 @@
<string name="onboarding_permission_notifications_description">Get notified for library updates and more.</string>
<string name="onboarding_permission_ignore_battery_opts">Background battery usage</string>
<string name="onboarding_permission_ignore_battery_opts_description">Avoid interruptions to long-running library updates, downloads, and backup restores.</string>
<string name="onboarding_permission_crashlytics">Send crash logs</string>
<string name="onboarding_permission_crashlytics_description">Send anonymized crash logs to the developers.</string>
<string name="onboarding_permission_analytics">Allow analytics</string>
<string name="onboarding_permission_analytics_description">Send anonymized usage data to improve app features.</string>
<string name="onboarding_permission_action_grant">Grant</string>
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
<string name="onboarding_guides_returning_user">Reinstalling %s?</string>
@ -242,6 +247,9 @@
<string name="pref_app_language">App language</string>
<string name="pref_category_security">Security and privacy</string>
<string name="pref_security">Security</string>
<string name="pref_firebase">Analytics and Crash logs</string>
<string name="lock_with_biometrics">Require unlock</string>
<string name="lock_when_idle">Lock when idle</string>
<string name="lock_always">Always</string>
@ -249,6 +257,7 @@
<string name="hide_notification_content">Hide notification content</string>
<string name="secure_screen">Secure screen</string>
<string name="secure_screen_summary">Secure screen hides app contents when switching apps and block screenshots</string>
<string name="firebase_summary">Sending crash logs and analytics will allow us to identify and fix issues, improve performance, and make future updates more relevant to your needs</string>
<string name="pref_category_nsfw_content">NSFW (18+) sources</string>
<string name="pref_show_nsfw_source">Show in sources and extensions lists</string>
@ -317,10 +326,13 @@
<string name="ext_trust">Trust</string>
<string name="ext_untrusted">Untrusted</string>
<string name="ext_uninstall">Uninstall</string>
<string name="ext_remove">Remove</string>
<string name="ext_confirm_remove">Remove Extension?</string>
<string name="ext_app_info">App info</string>
<string name="untrusted_extension">Untrusted extension</string>
<string name="untrusted_extension_message">Malicious extensions can read any stored login credentials or execute arbitrary code.\n\nBy trusting this extension, you accept these risks.</string>
<string name="obsolete_extension_message">This extension is no longer available. It may not function properly and can cause issues with the app. Uninstalling it is recommended.</string>
<string name="remove_private_extension_message">Do you really want to remove \"%s\" extension?</string>
<string name="extension_api_error">Failed to fetch available extensions</string>
<string name="ext_info_version">Version</string>
<string name="ext_info_language">Language</string>
@ -497,6 +509,7 @@
<!-- Tracking section -->
<string name="tracking_guide">Tracking guide</string>
<string name="pref_auto_update_manga_sync">Update progress after reading</string>
<string name="pref_auto_update_manga_on_mark_read">Update progress when marked as read</string>
<string name="services">Trackers</string>
<string name="tracking_info">One-way sync to update the chapter progress in external tracker services. Set up tracking for individual entries from their tracking button.</string>
<string name="enhanced_services">Enhanced trackers</string>
@ -534,6 +547,7 @@
<string name="source_settings">Source settings</string>
<string name="extensionRepo_settings">Extension repos</string>
<string name="private_settings">Include sensitive settings (e.g., tracker login tokens)</string>
<string name="non_library_settings">All read entries</string>
<string name="creating_backup">Creating backup</string>
<string name="creating_backup_error">Backup failed</string>
<string name="missing_storage_permission">Storage permissions not granted</string>
@ -569,7 +583,7 @@
<string name="pref_reset_user_agent_string">Reset default user agent string</string>
<string name="requires_app_restart">Requires app restart to take effect</string>
<string name="cookies_cleared">Cookies cleared</string>
<string name="pref_invalidate_download_cache">Invalidate downloads index</string>
<string name="pref_invalidate_download_cache">Reindex downloads</string>
<string name="pref_invalidate_download_cache_summary">Force app to recheck downloaded chapters</string>
<string name="download_cache_invalidated">Downloads index invalidated</string>
<string name="pref_clear_database">Clear database</string>
@ -726,6 +740,7 @@
<string name="exclude_scanlators">Exclude scanlators</string>
<string name="no_scanlators_found">No scanlators found</string>
<string name="confirm_tracker_update">Update trackers to chapter %d?</string>
<string name="trackers_updated_summary">Trackers updated to chapter %d</string>
<!-- Tracking Screen -->
<string name="manga_tracking_tab">Tracking</string>

View File

@ -1 +0,0 @@
/build

View File

@ -1 +0,0 @@
/build

View File

@ -28,7 +28,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -46,6 +45,7 @@ import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.TriState
import tachiyomi.core.common.preference.toggle
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
import tachiyomi.presentation.core.components.material.Slider
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.theme.header
@ -98,12 +98,21 @@ fun SortItem(label: String, sortDescending: Boolean?, onClick: () -> Unit) {
null -> null
}
BaseSortItem(
label = label,
icon = arrowIcon,
onClick = onClick,
)
}
@Composable
fun BaseSortItem(label: String, icon: ImageVector?, onClick: () -> Unit) {
BaseSettingsItem(
label = label,
widget = {
if (arrowIcon != null) {
if (icon != null) {
Icon(
imageVector = arrowIcon,
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
@ -183,17 +192,14 @@ fun SliderItem(
}
Slider(
value = value.toFloat(),
onValueChange = {
val newValue = it.toInt()
if (newValue != value) {
onChange(newValue)
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}
},
modifier = Modifier.weight(1.5f),
valueRange = min.toFloat()..max.toFloat(),
steps = max - min,
value = value,
onValueChange = f@{
if (it == value) return@f
onChange(it)
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
},
valueRange = min..max,
)
}
}

View File

@ -9,11 +9,11 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.FloatingActionButtonElevation
@ -46,12 +46,8 @@ fun ExtendedFloatingActionButton(
contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
) {
val minWidth by animateDpAsState(
targetValue = if (expanded) ExtendedFabMinimumWidth else FabContainerWidth,
label = "minWidth",
)
FloatingActionButton(
modifier = modifier.sizeIn(minWidth = minWidth),
modifier = modifier,
onClick = onClick,
interactionSource = interactionSource,
shape = shape,
@ -59,18 +55,29 @@ fun ExtendedFloatingActionButton(
contentColor = contentColor,
elevation = elevation,
) {
val minWidth by animateDpAsState(
targetValue = if (expanded) ExtendedFabMinimumWidth else FabContainerWidth,
animationSpec = tween(
durationMillis = 500,
easing = EasingEmphasizedCubicBezier,
),
label = "minWidth",
)
val startPadding by animateDpAsState(
targetValue = if (expanded) ExtendedFabIconSize / 2 else 0.dp,
animationSpec = tween(
durationMillis = if (expanded) 300 else 900,
easing = EasingEmphasizedCubicBezier,
),
label = "startPadding",
)
val endPadding by animateDpAsState(
targetValue = if (expanded) ExtendedFabTextPadding else 0.dp,
label = "endPadding",
)
Row(
modifier = Modifier.padding(start = startPadding, end = endPadding),
modifier = Modifier
.sizeIn(minWidth = minWidth)
.padding(start = startPadding),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
icon()
AnimatedVisibility(
@ -78,8 +85,7 @@ fun ExtendedFloatingActionButton(
enter = ExtendedFabExpandAnimation,
exit = ExtendedFabCollapseAnimation,
) {
Row {
Spacer(Modifier.width(ExtendedFabIconPadding))
Box(modifier = Modifier.padding(start = ExtendedFabIconPadding, end = ExtendedFabTextPadding)) {
text()
}
}

View File

@ -0,0 +1,48 @@
package tachiyomi.presentation.core.components.material
import androidx.annotation.IntRange
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderColors
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.SliderState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@Composable
fun Slider(
value: Int,
onValueChange: (Int) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedRange<Int> = 0..1,
@IntRange(from = 0) steps: Int = with(valueRange) { (endInclusive - start) - 1 },
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
thumb: @Composable (SliderState) -> Unit = {
SliderDefaults.Thumb(
interactionSource = interactionSource,
colors = colors,
enabled = enabled,
)
},
track: @Composable (SliderState) -> Unit = { sliderState ->
SliderDefaults.Track(colors = colors, enabled = enabled, sliderState = sliderState)
},
) {
Slider(
value = value.toFloat(),
onValueChange = { onValueChange(it.toInt()) },
modifier = modifier,
enabled = enabled,
valueRange = with(valueRange) { start.toFloat()..endInclusive.toFloat() },
steps = steps,
onValueChangeFinished = onValueChangeFinished,
colors = colors,
interactionSource = interactionSource,
thumb = thumb,
track = track,
)
}

View File

@ -82,5 +82,5 @@ val CustomIcons.Discord: ImageVector
return _discord!!
}
@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming")
@Suppress("ObjectPropertyName")
private var _discord: ImageVector? = null

View File

@ -59,5 +59,5 @@ val CustomIcons.Facebook: ImageVector
return _facebook!!
}
@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming")
@Suppress("ObjectPropertyName")
private var _facebook: ImageVector? = null

View File

@ -64,5 +64,5 @@ val CustomIcons.Github: ImageVector
return _github!!
}
@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming")
@Suppress("ObjectPropertyName")
private var _github: ImageVector? = null

View File

@ -90,5 +90,5 @@ val CustomIcons.Reddit: ImageVector
return _reddit!!
}
@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming")
@Suppress("ObjectPropertyName")
private var _reddit: ImageVector? = null

View File

@ -57,5 +57,5 @@ val CustomIcons.X: ImageVector
return _x!!
}
@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming")
@Suppress("ObjectPropertyName")
private var _x: ImageVector? = null

View File

@ -4,12 +4,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.CoroutineScope
import tachiyomi.core.common.preference.Preference
@Composable
fun <T> Preference<T>.collectAsState(scope: CoroutineScope = rememberCoroutineScope()): State<T> {
val flow = remember(this) { stateIn(scope) }
return flow.collectAsState()
fun <T> Preference<T>.collectAsState(): State<T> {
val flow = remember(this) { changes() }
return flow.collectAsState(initial = get())
}

View File

@ -1 +0,0 @@
/build

View File

@ -20,6 +20,7 @@ dependencies {
api(projects.i18n)
implementation(compose.glance)
implementation(libs.material)
implementation(kotlinx.immutables)

View File

@ -1 +0,0 @@
/build

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.source.model

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.source.model

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.source.model

View File

@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.source.model

View File

@ -1 +0,0 @@
/build

View File

@ -18,7 +18,7 @@ import kotlinx.serialization.json.decodeFromStream
import logcat.LogPriority
import mihon.core.archive.archiveReader
import mihon.core.archive.epubReader
import nl.adaptivity.xmlutil.AndroidXmlReader
import nl.adaptivity.xmlutil.core.AndroidXmlReader
import nl.adaptivity.xmlutil.serialization.XML
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.extension
@ -55,10 +55,10 @@ actual class LocalSource(
private val json: Json by injectLazy()
private val xml: XML by injectLazy()
@Suppress("PrivatePropertyName", "ktlint:standard:property-naming")
@Suppress("PrivatePropertyName")
private val PopularFilters = FilterList(OrderBy.Popular(context))
@Suppress("PrivatePropertyName", "ktlint:standard:property-naming")
@Suppress("PrivatePropertyName")
private val LatestFilters = FilterList(OrderBy.Latest(context))
override val name: String = context.stringResource(MR.strings.local_source)