mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	Compare commits
	
		
			47 Commits
		
	
	
		
			ab0893b2d4
			...
			v0.17.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 38d5fc9160 | ||
|  | 9454fe4482 | ||
|  | 6de06419f8 | ||
|  | fc2f339ea1 | ||
|  | 264030d6ec | ||
|  | 140083ee39 | ||
|  | 2bf7ef5d18 | ||
|  | df9fff60da | ||
|  | aae0e3459c | ||
|  | f7752a98b2 | ||
|  | 2ba7ed3280 | ||
|  | c153ac01f5 | ||
|  | 47b0e9d7be | ||
|  | d4bf19f957 | ||
|  | 01b44c0458 | ||
|  | e1e3ca7a56 | ||
|  | 78d2cc75d5 | ||
|  | c550a81598 | ||
|  | 0be36a10c3 | ||
|  | e16c3953c7 | ||
|  | f3a2f566c8 | ||
|  | 15e3f28aa3 | ||
|  | 3bf70b230f | ||
|  | eb3bea8150 | ||
|  | 5612ae0149 | ||
|  | dbf6ad2ca7 | ||
|  | d2afbfe4ed | ||
|  | 337806d9e1 | ||
|  | 443f6e0ae5 | ||
|  | 572ee2f02a | ||
|  | ba1343bed8 | ||
|  | 9f3d5d13d4 | ||
|  | 48166b9b52 | ||
|  | 2e2c8d36c1 | ||
|  | 788235feec | ||
|  | afa5002988 | ||
|  | 9503082d44 | ||
|  | de36357da8 | ||
|  | eb6092bd0c | ||
|  | 32d2c2ac1b | ||
|  | 4051f180a2 | ||
|  | 3ed8a91c7b | ||
|  | 87db3f90de | ||
|  | 0a4ad89b99 | ||
|  | a72db41bf1 | ||
|  | 6b2bba4e54 | ||
|  | 7c7af72f8c | 
							
								
								
									
										4
									
								
								.github/ISSUE_TEMPLATE/report_issue.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/ISSUE_TEMPLATE/report_issue.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/request_feature.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/request_feature.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										9
									
								
								.github/renovate.json5
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/renovate.json5
									
									
									
									
										vendored
									
									
								
							| @@ -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, | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|   | ||||
							
								
								
									
										21
									
								
								.github/workflows/build_pull_request.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/build_pull_request.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/build_push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/build_push.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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
									
									
								
							
							
						
						
									
										22
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										232
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										232
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -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
									
									
								
							
							
						
						
									
										3
									
								
								app/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +0,0 @@ | ||||
| /build | ||||
| *iml | ||||
| *.iml | ||||
| @@ -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()}\"") | ||||
|   | ||||
							
								
								
									
										11
									
								
								app/src/dev/java/mihon/core/firebase/FirebaseConfig.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/src/dev/java/mihon/core/firebase/FirebaseConfig.kt
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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, | ||||
|                             ) | ||||
|                         } | ||||
|   | ||||
| @@ -0,0 +1,10 @@ | ||||
| package eu.kanade.domain.track.model | ||||
|  | ||||
| import dev.icerock.moko.resources.StringResource | ||||
| import tachiyomi.i18n.MR | ||||
|  | ||||
| enum class AutoTrackState(val titleRes: StringResource) { | ||||
|     ALWAYS(MR.strings.lock_always), | ||||
|     ASK(MR.strings.default_category_summary), | ||||
|     NEVER(MR.strings.lock_never), | ||||
| } | ||||
| @@ -1,9 +1,11 @@ | ||||
| package eu.kanade.domain.track.service | ||||
|  | ||||
| import eu.kanade.domain.track.model.AutoTrackState | ||||
| import eu.kanade.tachiyomi.data.track.Tracker | ||||
| import eu.kanade.tachiyomi.data.track.anilist.Anilist | ||||
| import tachiyomi.core.common.preference.Preference | ||||
| import tachiyomi.core.common.preference.PreferenceStore | ||||
| import tachiyomi.core.common.preference.getEnum | ||||
|  | ||||
| class TrackPreferences( | ||||
|     private val preferenceStore: PreferenceStore, | ||||
| @@ -35,4 +37,9 @@ class TrackPreferences( | ||||
|     fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10) | ||||
|  | ||||
|     fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true) | ||||
|  | ||||
|     fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum( | ||||
|         "pref_auto_update_manga_on_mark_read", | ||||
|         AutoTrackState.ALWAYS, | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -166,32 +166,32 @@ 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, | ||||
|         MR.strings.action_sort_random to LibrarySort.Type.Random, | ||||
|     ).plus(trackerSortOption).map { (titleRes, mode) -> | ||||
|     options.map { (titleRes, mode) -> | ||||
|         if (mode == LibrarySort.Type.Random) { | ||||
|             val enabledIcon = if (sortingMode == LibrarySort.Type.Random) { | ||||
|                 Icons.Default.Refresh | ||||
|             } else { | ||||
|                 null | ||||
|             } | ||||
|             BaseSortItem( | ||||
|                 label = stringResource(titleRes), | ||||
|                 icon = enabledIcon, | ||||
|                 icon = Icons.Default.Refresh | ||||
|                     .takeIf { sortingMode == LibrarySort.Type.Random }, | ||||
|                 onClick = { | ||||
|                     screenModel.setSort(category, mode, LibrarySort.Direction.Ascending) | ||||
|                 }, | ||||
|   | ||||
| @@ -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), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
| @@ -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)), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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) }, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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)) | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
| @@ -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], | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.database.models | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.database.models | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.database.models | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.database.models | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.track.model | ||||
|  | ||||
|   | ||||
| @@ -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()) | ||||
|         } | ||||
|   | ||||
| @@ -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, | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -302,16 +302,15 @@ class LibraryScreenModel( | ||||
|                     item1Score.compareTo(item2Score) | ||||
|                 } | ||||
|                 LibrarySort.Type.Random -> { | ||||
|                     error("A comparator should not be requested for the random sort style. Instead, intercept this " + | ||||
|                         "case and call .shuffle()") | ||||
|                     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.currentRandomSortSeed().get())) | ||||
|            } | ||||
|             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() } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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()), | ||||
|   | ||||
| @@ -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" } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -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, | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|   | ||||
| @@ -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() | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
							
								
								
									
										25
									
								
								app/src/standard/java/mihon/core/firebase/FirebaseConfig.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/src/standard/java/mihon/core/firebase/FirebaseConfig.kt
									
									
									
									
									
										Normal 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
									
									
								
							
							
						
						
									
										1
									
								
								buildSrc/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -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() | ||||
|     } | ||||
|   | ||||
							
								
								
									
										1
									
								
								core-metadata/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								core-metadata/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
							
								
								
									
										1
									
								
								core/archive/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								core/archive/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
							
								
								
									
										1
									
								
								core/common/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								core/common/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -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) | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("FunctionName", "ktlint:standard:function-naming") | ||||
| @file:Suppress("FunctionName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.network | ||||
|  | ||||
|   | ||||
							
								
								
									
										1
									
								
								data/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								data/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -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) } | ||||
|     } | ||||
|   | ||||
| @@ -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
									
									
								
							
							
						
						
									
										1
									
								
								domain/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -17,7 +17,7 @@ class SetSortModeForCategory( | ||||
|         val category = categoryId?.let { categoryRepository.get(it) } | ||||
|         val flags = (category?.flags ?: 0) + type + direction | ||||
|         if (type == LibrarySort.Type.Random) { | ||||
|             preferences.currentRandomSortSeed().set(Random.nextInt()) | ||||
|             preferences.randomSortSeed().set(Random.nextInt()) | ||||
|         } | ||||
|         if (category != null && preferences.categorizedDisplaySettings().get()) { | ||||
|             categoryRepository.updatePartial( | ||||
|   | ||||
| @@ -31,7 +31,7 @@ data class LibrarySort( | ||||
|         data object ChapterFetchDate : Type(0b00011000) | ||||
|         data object DateAdded : Type(0b00011100) | ||||
|         data object TrackerMean : Type(0b00100000) | ||||
|         data object Random : Type(0b00100100) | ||||
|         data object Random : Type(0b00111100) | ||||
|  | ||||
|         companion object { | ||||
|             fun valueOf(flag: Long): Type { | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class LibraryPreferences( | ||||
|         LibrarySort.Serializer::deserialize, | ||||
|     ) | ||||
|  | ||||
|     fun currentRandomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0) | ||||
|     fun randomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0) | ||||
|  | ||||
|     fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0) | ||||
|  | ||||
|   | ||||
| @@ -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>> | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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" } | ||||
|   | ||||
| @@ -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
									
									
								
							
							
						
						
									
										0
									
								
								i18n/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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.  | ||||
| @@ -191,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> | ||||
| @@ -243,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> | ||||
| @@ -250,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> | ||||
| @@ -318,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> | ||||
| @@ -498,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> | ||||
| @@ -535,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> | ||||
| @@ -570,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> | ||||
| @@ -727,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> | ||||
|   | ||||
							
								
								
									
										1
									
								
								macrobenchmark/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								macrobenchmark/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
							
								
								
									
										1
									
								
								presentation-core/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								presentation-core/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -17,7 +17,6 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.filled.ArrowDownward | ||||
| import androidx.compose.material.icons.filled.ArrowUpward | ||||
| import androidx.compose.material.icons.filled.Refresh | ||||
| import androidx.compose.material.icons.rounded.CheckBox | ||||
| import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank | ||||
| import androidx.compose.material.icons.rounded.DisabledByDefault | ||||
| @@ -29,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 | ||||
| @@ -47,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 | ||||
| @@ -193,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, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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() | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -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, | ||||
|     ) | ||||
| } | ||||
| @@ -82,5 +82,5 @@ val CustomIcons.Discord: ImageVector | ||||
|         return _discord!! | ||||
|     } | ||||
|  | ||||
| @Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") | ||||
| @Suppress("ObjectPropertyName") | ||||
| private var _discord: ImageVector? = null | ||||
|   | ||||
| @@ -59,5 +59,5 @@ val CustomIcons.Facebook: ImageVector | ||||
|         return _facebook!! | ||||
|     } | ||||
|  | ||||
| @Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") | ||||
| @Suppress("ObjectPropertyName") | ||||
| private var _facebook: ImageVector? = null | ||||
|   | ||||
| @@ -64,5 +64,5 @@ val CustomIcons.Github: ImageVector | ||||
|         return _github!! | ||||
|     } | ||||
|  | ||||
| @Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") | ||||
| @Suppress("ObjectPropertyName") | ||||
| private var _github: ImageVector? = null | ||||
|   | ||||
| @@ -90,5 +90,5 @@ val CustomIcons.Reddit: ImageVector | ||||
|         return _reddit!! | ||||
|     } | ||||
|  | ||||
| @Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") | ||||
| @Suppress("ObjectPropertyName") | ||||
| private var _reddit: ImageVector? = null | ||||
|   | ||||
| @@ -57,5 +57,5 @@ val CustomIcons.X: ImageVector | ||||
|         return _x!! | ||||
|     } | ||||
|  | ||||
| @Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") | ||||
| @Suppress("ObjectPropertyName") | ||||
| private var _x: ImageVector? = null | ||||
|   | ||||
| @@ -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()) | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								presentation-widget/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								presentation-widget/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -20,6 +20,7 @@ dependencies { | ||||
|     api(projects.i18n) | ||||
|  | ||||
|     implementation(compose.glance) | ||||
|     implementation(libs.material) | ||||
|  | ||||
|     implementation(kotlinx.immutables) | ||||
|  | ||||
|   | ||||
							
								
								
									
										1
									
								
								source-api/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								source-api/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.source.model | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.source.model | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.source.model | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
| @file:Suppress("PropertyName") | ||||
|  | ||||
| package eu.kanade.tachiyomi.source.model | ||||
|  | ||||
|   | ||||
							
								
								
									
										1
									
								
								source-local/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								source-local/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user