mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			v0.17.1
			...
			ab0893b2d4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ab0893b2d4 | ||
|  | 078758391e | ||
|  | 2eb1580788 | ||
|  | d328ded17f | ||
|  | 80f9dfb699 | ||
|  | 3d087f4428 | ||
|  | 0ab795bfa3 | 
							
								
								
									
										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.17.1" | ||||
|         Example: "0.16.5" | ||||
|     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.17.1](https://github.com/mihonapp/mihon/releases/latest)**. | ||||
|         - label: I have updated the app to version **[0.16.5](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.17.1](https://github.com/mihonapp/mihon/releases/latest)**. | ||||
|         - label: I have updated the app to version **[0.16.5](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,12 +2,5 @@ | ||||
|   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||
|   "extends": ["config:base"], | ||||
|   "labels": ["Dependencies"], | ||||
|   "semanticCommits": "disabled", | ||||
|   "packageRules": [ | ||||
|     { | ||||
|       "groupName": "GitHub Actions", | ||||
|       "matchManagers": ["github-actions"], | ||||
|       "pinDigests": true, | ||||
|     } | ||||
|   ] | ||||
|   "semanticCommits": "disabled" | ||||
| } | ||||
|   | ||||
							
								
								
									
										25
									
								
								.github/workflows/build_pull_request.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/build_pull_request.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,13 +1,10 @@ | ||||
| name: PR build check | ||||
| on: | ||||
|   pull_request: | ||||
|     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' | ||||
|     paths-ignore: | ||||
|       - '**.md' | ||||
|       - 'i18n/src/commonMain/moko-resources/**/strings.xml' | ||||
|       - 'i18n/src/commonMain/moko-resources/**/plurals.xml' | ||||
|  | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | ||||
| @@ -23,34 +20,34 @@ jobs: | ||||
|  | ||||
|     steps: | ||||
|       - name: Clone repo | ||||
|         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||||
|         uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 | ||||
|  | ||||
|       - name: Validate Gradle Wrapper | ||||
|         uses: gradle/actions/wrapper-validation@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1 | ||||
|         uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 | ||||
|  | ||||
|       - name: Dependency Review | ||||
|         uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 | ||||
|         uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 | ||||
|  | ||||
|       - name: Set up JDK | ||||
|         uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0 | ||||
|         uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0 | ||||
|         with: | ||||
|           java-version: 17 | ||||
|           distribution: adopt | ||||
|  | ||||
|       - name: Set up gradle | ||||
|         uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1 | ||||
|         uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 | ||||
|  | ||||
|       - name: Build app and run unit tests | ||||
|         run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest | ||||
|  | ||||
|       - name: Upload APK | ||||
|         uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: arm64-v8a-${{ github.sha }} | ||||
|           path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk | ||||
|  | ||||
|       - name: Upload mapping | ||||
|         uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: mapping-${{ github.sha }} | ||||
|           path: app/build/outputs/mapping/standardRelease | ||||
|   | ||||
							
								
								
									
										14
									
								
								.github/workflows/build_push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/build_push.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,35 +17,35 @@ jobs: | ||||
|  | ||||
|     steps: | ||||
|       - name: Clone repo | ||||
|         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||||
|         uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 | ||||
|  | ||||
|       - name: Validate Gradle Wrapper | ||||
|         uses: gradle/actions/wrapper-validation@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1 | ||||
|         uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 | ||||
|  | ||||
|       - name: Setup Android SDK | ||||
|         run: | | ||||
|           ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3" | ||||
|  | ||||
|       - name: Set up JDK | ||||
|         uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0 | ||||
|         uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0 | ||||
|         with: | ||||
|           java-version: 17 | ||||
|           distribution: adopt | ||||
|  | ||||
|       - name: Set up gradle | ||||
|         uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1 | ||||
|         uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 | ||||
|  | ||||
|       - name: Build app and run unit tests | ||||
|         run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest | ||||
|  | ||||
|       - name: Upload APK | ||||
|         uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: arm64-v8a-${{ github.sha }} | ||||
|           path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk | ||||
|  | ||||
|       - name: Upload mapping | ||||
|         uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: mapping-${{ github.sha }} | ||||
|           path: app/build/outputs/mapping/standardRelease | ||||
| @@ -95,7 +95,7 @@ jobs: | ||||
|  | ||||
|       - name: Create Release | ||||
|         if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon' | ||||
|         uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0 | ||||
|         uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 | ||||
|         with: | ||||
|           tag_name: ${{ env.VERSION_TAG }} | ||||
|           name: Mihon ${{ env.VERSION_TAG }} | ||||
|   | ||||
							
								
								
									
										28
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,16 +1,18 @@ | ||||
| # Build files | ||||
| .gradle | ||||
| .kotlin | ||||
| build | ||||
|  | ||||
| # IDE files | ||||
| *.iml | ||||
| .idea/* | ||||
| !.idea/icon.svg | ||||
| /captures | ||||
|  | ||||
| # Configuration files | ||||
| local.properties | ||||
|  | ||||
| # macOS specific files | ||||
| /local.properties | ||||
| /.idea/workspace.xml | ||||
| .DS_Store | ||||
| .idea/* | ||||
| !.idea/icon.png | ||||
| *iml | ||||
| *.iml | ||||
|  | ||||
| # Built files | ||||
| */build | ||||
| /build | ||||
| *.apk | ||||
| app/**/output.json | ||||
|  | ||||
| # Unnecessary file | ||||
| *.swp | ||||
							
								
								
									
										
											BIN
										
									
								
								.idea/icon.png
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.idea/icon.png
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 62 KiB | 
							
								
								
									
										6
									
								
								.idea/icon.svg
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								.idea/icon.svg
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +0,0 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" fill="none" viewBox="0 0 432 432"> | ||||
|   <circle cx="216" cy="216" r="216" fill="#2e3943"/> | ||||
|   <path fill="#f2faff" d="M398.073 216c0 97.433-81.517 176.419-182.073 176.419-100.556 0-182.073-78.986-182.073-176.419S115.444 39.581 216 39.581c100.556 0 182.073 78.986 182.073 176.419Z"/> | ||||
|   <path fill="#7ebbed" fill-rule="evenodd" d="M216 359.34c81.702 0 147.934-64.175 147.934-143.34S297.702 72.66 216 72.66 68.065 136.835 68.065 216 134.298 359.34 216 359.34zm0 33.079c100.556 0 182.073-78.986 182.073-176.419S316.556 39.581 216 39.581C115.444 39.581 33.927 118.567 33.927 216S115.444 392.419 216 392.419z" clip-rule="evenodd"/> | ||||
|   <path fill="#031019" d="m155.273 168.033-1.227-28.215c3.68.7 8.063.875 18.052.875 12.092 0 28.041-.7 36.279-1.752 3.504-.35 4.907-.876 7.185-2.103l18.928 16.124c-1.753 2.453-2.279 3.505-4.207 8.412-1.576 3.856-8.762 26.113-11.567 35.577 12.97 2.63 20.155 4.557 29.97 8.588 1.226-8.588 1.401-13.144 1.401-28.742 0-4.03-.175-6.31-.7-9.99l30.495 1.051c-.877 4.207-1.052 5.959-1.227 12.794-.701 16.475-1.403 24.361-3.154 36.279 12.092 6.134 12.092 6.134 18.226 9.464 3.154 1.752 3.855 2.102 5.959 2.804l-10.165 32.773c-4.908-4.381-11.743-9.113-21.732-14.721-8.763 20.855-23.31 36.103-45.392 48.195-7.36-9.814-12.97-15.772-21.907-22.783 12.969-6.134 18.928-9.99 25.763-16.475 6.66-6.484 11.04-12.793 15.247-22.258-11.217-5.082-18.403-7.36-30.846-9.989-7.185 21.382-12.969 35.052-18.051 43.29-6.835 11.04-16.124 16.824-26.815 16.824-8.237 0-16.65-3.68-22.784-9.99-7.01-7.185-10.69-17.175-10.69-28.742 0-17.176 8.238-32.072 22.609-41.361 9.288-5.959 19.103-8.588 34.7-9.465 3.155-10.34 5.785-19.278 8.238-29.267-7.712.701-17.35 1.227-29.093 1.752-6.309.175-8.412.35-13.495 1.051zm26.64 53.279c-8.238 1.402-13.145 4.031-17.527 9.64-3.33 3.855-4.907 8.412-4.907 13.32 0 5.432 2.63 9.464 5.959 9.464 4.03 0 8.588-9.114 16.475-32.424z"/> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										254
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										254
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -2,289 +2,133 @@ | ||||
|  | ||||
| All notable changes to this project will be documented in this file. | ||||
|  | ||||
| The format is a modified version of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||
| - `Added` - for new features. | ||||
| - `Changed ` - for changes in existing functionality. | ||||
| - `Improved` - for enhancement or optimization in existing functionality. | ||||
| - `Removed` - for now removed features. | ||||
| - `Fixed` - for any bug fixes. | ||||
| - `Other` - for technical stuff. | ||||
| 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). | ||||
|  | ||||
| ## [Unreleased] | ||||
|  | ||||
| ## [v0.17.1] - 2024-12-06 | ||||
| ### Changed | ||||
| - Bump default user agent ([@AntsyLich](https://github.com/AntsyLich)) ([`76dcf90`](https://github.com/mihonapp/mihon/commit/76dcf903403d565056f44c66d965c1ea8affffc3)) | ||||
|  | ||||
| ### Improved | ||||
| - Bangumi search now shows the score and summary of a search result ([@MajorTanya](https://github.com/MajorTanya)) ([#1396](https://github.com/mihonapp/mihon/pull/1396)) | ||||
| - Extension repo URLs are now auto-formatted ([@AntsyLich](https://github.com/AntsyLich), [@MajorTanya](https://github.com/MajorTanya)) | ||||
|   | ||||
| ### Fixed | ||||
| - Fix "currentTab was used multiple times" ([@AntsyLich](https://github.com/AntsyLich)) ([`371c143`](https://github.com/mihonapp/mihon/commit/371c1432e218f6dcf129f05405dceb2cd351c647)) | ||||
| - Fix a rare crash when invoking "Mark previous as read" action ([@AntsyLich](https://github.com/AntsyLich)) ([`f508d10`](https://github.com/mihonapp/mihon/commit/f508d10ad13560d7316df8642bc93fe66c05b9a8)) | ||||
| - Fix long strip images not loading in some old devices ([@AntsyLich](https://github.com/AntsyLich)) ([`06efc3b`](https://github.com/mihonapp/mihon/commit/06efc3b25c5af51f42448af27a269ee459d9093d)) | ||||
|   - Switch to hardware bitmap in reader only if device can handle it ([@AntsyLich](https://github.com/AntsyLich)) ([`e6d96bd`](https://github.com/mihonapp/mihon/commit/e6d96bd348ea5d18a005d6465222ad5f5123103e)) | ||||
|   - Add option to lower the threshold for hardware bitmaps ([@AntsyLich](https://github.com/AntsyLich)) ([`dcddac5`](https://github.com/mihonapp/mihon/commit/dcddac5daaff3ec89c8507c35dc13d345ffdb6d7)) | ||||
|     - Improve hardware bitmap threshold option ([@AntsyLich](https://github.com/AntsyLich)) ([`d6dfd24`](https://github.com/mihonapp/mihon/commit/d6dfd24548eaa05a8c3e478068fe2e08f2ee4473)) | ||||
|   - Always use software bitmap on certain devices ([@MajorTanya](https://github.com/MajorTanya)) ([#1543](https://github.com/mihonapp/mihon/pull/1543)) | ||||
| - Fix crash after removing last category while it's active in library ([@cuong-tran](https://github.com/cuong-tran)) ([#1450](https://github.com/mihonapp/mihon/pull/1450)) | ||||
| - Fix reader transition color scheme in auto background mode ([@cuong-tran](https://github.com/cuong-tran)) ([#1487](https://github.com/mihonapp/mihon/pull/1487)) | ||||
| - Fix app update error notification disappearing ([@cuong-tran](https://github.com/cuong-tran)) ([#1476](https://github.com/mihonapp/mihon/pull/1476)) | ||||
| - Fix browser not opening in some cases in Honor devices ([@AntsyLich](https://github.com/AntsyLich), [@MajorTanya](https://github.com/MajorTanya)) ([#1520](https://github.com/mihonapp/mihon/pull/1520)) | ||||
|  | ||||
| ## [v0.17.0] - 2024-10-26 | ||||
| ### Added | ||||
| - Option to disable reader zoom out ([@Splintorien](https://github.com/Splintorien)) ([#302](https://github.com/mihonapp/mihon/pull/302)) | ||||
| - Source name and tracker urls to app generated `ComicInfo.xml` file ([@Shamicen](https://github.com/Shamicen)) ([#459](https://github.com/mihonapp/mihon/pull/459)) | ||||
| - Option to migrate in Duplicate entry dialog ([@sirlag](https://github.com/sirlag)) ([#492](https://github.com/mihonapp/mihon/pull/492)) | ||||
| - Upcoming screen to visualize expected update dates ([@sirlag](https://github.com/sirlag)) ([#420](https://github.com/mihonapp/mihon/pull/420)) | ||||
|   - Only show upcoming updates in the future ([@sirlag](https://github.com/sirlag)) ([#606](https://github.com/mihonapp/mihon/pull/606)) | ||||
|   - Add Quantity Badge to Upcoming Screen ([@Animeboynz](https://github.com/Animeboynz), [@AntsyLich](https://github.com/AntsyLich)) ([#1250](https://github.com/mihonapp/mihon/pull/1250)) | ||||
| - Crash screen error message to the top of the crash log generated from that screen ([@FooIbar](https://github.com/FooIbar)) ([#742](https://github.com/mihonapp/mihon/pull/742)) | ||||
| - Support for 7Zip and RAR5 archives ([@FooIbar](https://github.com/FooIbar)) ([#949](https://github.com/mihonapp/mihon/pull/949)) | ||||
| - 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)) | ||||
| - 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 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)) | ||||
| - 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)) | ||||
| - Wait for sources to be initialized before performing source related tasks ([@jobobby04](https://github.com/jobobby04)) ([`a08e03f`](https://github.com/mihonapp/mihon/commit/a08e03f5cbf3f4e6be1de35f97ef8ebb26a1210e)) | ||||
| - Duplicate entry dialog UI ([@sirlag](https://github.com/sirlag)) ([#492](https://github.com/mihonapp/mihon/pull/492)) | ||||
| - Extension trust system | ||||
|   - Store extension repo details from `repo.json` in database ([@sirlag](https://github.com/sirlag)) ([#506](https://github.com/mihonapp/mihon/pull/506)) | ||||
|     - Fix extension repo migration not triggering ([@AntsyLich](https://github.com/AntsyLich)) ([`9672ea8`](https://github.com/mihonapp/mihon/commit/9672ea8b1b06f464800e310c96e060ead182f7ca)) | ||||
|     - Refactor the ExtensionRepoService to use DTOs ([@MajorTanya](https://github.com/MajorTanya)) ([#573](https://github.com/mihonapp/mihon/pull/573)) | ||||
|     - Fix extension repo name is used to construct URL instead of baseUrl ([@MajorTanya](https://github.com/MajorTanya)) ([#572](https://github.com/mihonapp/mihon/pull/572)) | ||||
|     - Fix crash with `TypeReference` issue when creating extension repo ([@AntsyLich](https://github.com/AntsyLich)) ([#574](https://github.com/mihonapp/mihon/pull/574), [`e020ae5`](https://github.com/mihonapp/mihon/commit/e020ae5ed558e80742ef0ad8bfa0f69af0959d5a)) | ||||
|       - Fix mishap in [`e020ae5`](https://github.com/mihonapp/mihon/commit/e020ae5ed558e80742ef0ad8bfa0f69af0959d5a) ([@AntsyLich](https://github.com/AntsyLich)) ([`6965e59`](https://github.com/mihonapp/mihon/commit/6965e59a643c67a2bf81b3c69ec70268e5da5797)) | ||||
|     - Backup and Restore ([@Animeboynz](https://github.com/Animeboynz)) ([#1057](https://github.com/mihonapp/mihon/pull/1057)) | ||||
|   - Trust extension by repo ([@AntsyLich](https://github.com/AntsyLich)) ([#570](https://github.com/mihonapp/mihon/pull/570))-  | ||||
| - From M2 ripple to M3 ([@FooIbar](https://github.com/FooIbar)) ([#675](https://github.com/mihonapp/mihon/pull/675)) | ||||
| - Increased continue reading button size ([@AntsyLich](https://github.com/AntsyLich), [@Animeboynz](https://github.com/Animeboynz)) ([`e17f70f`](https://github.com/mihonapp/mihon/commit/e17f70f7226ea031fc1f962c9dfea3e404ba53ad)) | ||||
| - Global search "Has result" choice is now sticky ([@AntsyLich](https://github.com/AntsyLich)) ([`5a61ca5`](https://github.com/mihonapp/mihon/commit/5a61ca5535fe0d9e8e7bcb9e665ba2f9cb0cf649)) | ||||
| - 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)) | ||||
| - Make category backup/restore not dependant on library backup ([@AntsyLich](https://github.com/AntsyLich)) ([`56fb4f6`](https://github.com/mihonapp/mihon/commit/56fb4f62a152e87a71892aa68c78cac51a2c8596)) | ||||
| - Rename backup restore error log file ([@AntsyLich](https://github.com/AntsyLich)) ([`2858ef8`](https://github.com/mihonapp/mihon/commit/2858ef835fec8d7278b1d0cad1b5664104d1e4b0)) | ||||
| - Keyboard type in add extension repo dialog ([@xbjfk](https://github.com/xbjfk)) ([#764](https://github.com/mihonapp/mihon/pull/764)) | ||||
| - Adjust collapse/open animation on manga description ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`1c16fc7`](https://github.com/mihonapp/mihon/commit/1c16fc79c2ac4c4be30308fed84ffb371dab5902)) | ||||
| - Kitsu domain to `kitsu.app` ([@MajorTanya](https://github.com/MajorTanya)) ([#1106](https://github.com/mihonapp/mihon/pull/1106)) | ||||
| - Respect privacy settings in extension update notification ([@Animeboynz](https://github.com/Animeboynz)) ([#1156](https://github.com/mihonapp/mihon/pull/1156)) | ||||
| - Hide keyboard when a Tracker SearchResultItem is clicked ([@Animeboynz](https://github.com/Animeboynz)) ([#1168](https://github.com/mihonapp/mihon/pull/1168)) | ||||
| - Enable 'Split Tall Images' by default ([@Smol-Ame](https://github.com/Smol-Ame)) ([#1185](https://github.com/mihonapp/mihon/pull/1185)) | ||||
| - Ignore "intent://" urls on webview ([@bapeey](https://github.com/bapeey)) ([#1193](https://github.com/mihonapp/mihon/pull/1193)) | ||||
| - Make reader chapter navigator slightly wider on small screens (p) ([#1202](https://github.com/mihonapp/mihon/pull/1202)) | ||||
| - Re-enable fetching chapters list for entries with licenced status ([@Animeboynz](https://github.com/Animeboynz)) ([#1230](https://github.com/mihonapp/mihon/pull/1230)) | ||||
| - Change casing for Extention Repos String ([@Animeboynz](https://github.com/Animeboynz)) ([#1248](https://github.com/mihonapp/mihon/pull/1248)) | ||||
| - Retain remote last chapter read if it's higher than the local one for EnhancedTracker ([@brewkunz](https://github.com/brewkunz)) ([#1301](https://github.com/mihonapp/mihon/pull/1301)) | ||||
| - Adjust expandable fab animation (p) ([`eb6092b`](https://github.com/mihonapp/mihon/commit/eb6092bd0cfa09694985a8bafdd8bbf2815190a1)) | ||||
| - "Invalidate downloads index" to "Reindex downloads" ([@AntsyLich](https://github.com/AntsyLich)) ([`d2afbfe`](https://github.com/mihonapp/mihon/commit/d2afbfe4ede283076aae40633c79c3f90b4390e7)) | ||||
|  | ||||
| ### Improved | ||||
| - Reader performance | ||||
|   - Avoid unnecessary copying when processing reader image ([@FooIbar](https://github.com/FooIbar)) ([#691](https://github.com/mihonapp/mihon/pull/691)) | ||||
|   - Significantly improve performance when loading extremely long images in long strip mode ([@FooIbar](https://github.com/FooIbar)) ([#692](https://github.com/mihonapp/mihon/pull/692)) | ||||
|   - Use `Bitmap.Config.HARDWARE` if possible to improve image loading speed ([@wwww-wwww](https://github.com/wwww-wwww)) ([#687](https://github.com/mihonapp/mihon/pull/687)) | ||||
|   - Improve preloading in long strip mode ([@FooIbar](https://github.com/FooIbar)) ([#1076](https://github.com/mihonapp/mihon/pull/1076)) | ||||
| ### 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)) | ||||
| - Performance when looking up specific files ([@raxod502](https://github.com/raxod502)) ([#728](https://github.com/mihonapp/mihon/pull/728)) | ||||
| - Chapter number parsing ([@Naputt1](https://github.com/Naputt1)) ([`6a80305`](https://github.com/mihonapp/mihon/commit/6a80305d6c572da6c08c0c69f5c25ff26ecf7383)) | ||||
| - Error message on restoring if backup decoding fails ([@vetleledaal](https://github.com/vetleledaal)) ([#1056](https://github.com/mihonapp/mihon/pull/1056)) | ||||
|  | ||||
| ### Removed | ||||
| - Legacy download folder names no longer supported ([@AntsyLich](https://github.com/AntsyLich)) ([`e55e5f6`](https://github.com/mihonapp/mihon/commit/e55e5f6f64f872475d370d6ce0c186e2601776e4)) | ||||
| - Remove legacy broken source and history backup ([@AntsyLich](https://github.com/AntsyLich)) ([`518abf0`](https://github.com/mihonapp/mihon/commit/518abf032ccb9bb45d197927be2a5faca4167d29)) | ||||
| - Remove more unnecessary permissions from Firebase dependency ([@AntsyLich](https://github.com/AntsyLich)) ([`02af9b1`](https://github.com/mihonapp/mihon/commit/02af9b1acf9f590d29560bc3fc90d206e8e6e1af)) | ||||
|   - Fix mishap in `02af9b1` ([@AntsyLich](https://github.com/AntsyLich)) ([`f22767d`](https://github.com/mihonapp/mihon/commit/f22767d863a0fa001f93f24092cd5ade87350502)) | ||||
|  | ||||
| ### Fixed | ||||
| - Extracting `ComicInfo.xml` from local source archives ([@FooIbar](https://github.com/FooIbar)) ([#325](https://github.com/mihonapp/mihon/pull/325)) | ||||
| - Creating `ComicInfo.xml` file for local source ([@FooIbar](https://github.com/FooIbar)) ([#325](https://github.com/mihonapp/mihon/pull/325)) | ||||
| - Chapter download indicator ([@ivaniskandar](https://github.com/ivaniskandar)) ([`d8b9a9f`](https://github.com/mihonapp/mihon/commit/d8b9a9f593911569ff2bceb49b4f020978d0d2e1)) | ||||
| - Issues with shizuku in a multi user setup ([@Redjard](https://github.com/Redjard)) ([#494](https://github.com/mihonapp/mihon/pull/494)) | ||||
| - Fix reader page image not being decoded until it's visible ([@FooIbar](https://github.com/FooIbar)) ([#563](https://github.com/mihonapp/mihon/pull/563)) | ||||
| - Reader chapter progress slider visuals ([@FooIbar](https://github.com/FooIbar)) ([#674](https://github.com/mihonapp/mihon/pull/674)) | ||||
| - Occasional black bar when scrolling in long strip reader ([@FooIbar](https://github.com/FooIbar)) ([#563](https://github.com/mihonapp/mihon/pull/563)) | ||||
| - Extension being marked as not installed instead of untrusted after updating with private installer ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42)) | ||||
| - Extension update counter not updating due to extension being marked as untrusted ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42)) | ||||
| - `Key "extension-XXX-YYY" was already used` crash ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42)) | ||||
| - Navigation layout tap zones shifting after zooming out in webtoon readers ([@FooIbar](https://github.com/FooIbar)) ([#767](https://github.com/mihonapp/mihon/pull/767)) | ||||
| - Some extension not loading due to missing classes ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#783](https://github.com/mihonapp/mihon/pull/783)) | ||||
| - Theme colors in accordance to upstream changes ([@CrepeTF](https://github.com/CrepeTF), [@AntsyLich](https://github.com/AntsyLich)) ([#766](https://github.com/mihonapp/mihon/pull/766), [#963](https://github.com/mihonapp/mihon/pull/963), [#976](https://github.com/mihonapp/mihon/pull/976), [9a34ace](https://github.com/mihonapp/mihon/commit/9a34ace09c66274e6c2b3f9446058a0fa99d4bd0)) | ||||
| - 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)) | ||||
| - Crash when requesting folder access on non-conforming devices ([@mainrs](https://github.com/mainrs)) ([#726](https://github.com/mihonapp/mihon/pull/726)) | ||||
| - Fix unexpected skips in strong skipping mode ([@FooIbar](https://github.com/FooIbar)) ([#940](https://github.com/mihonapp/mihon/pull/940)) | ||||
| - Bugged color for Date/Scanlator in chapter list for read chapters ([@ivaniskandar](https://github.com/ivaniskandar)) ([`15d9992`](https://github.com/mihonapp/mihon/commit/15d999229fcce865001d5fa77d0163e6e80e38db)) | ||||
| - Categories having same `order` after restoring backup ([@Cologler](https://github.com/Cologler)) ([`119bcbf`](https://github.com/mihonapp/mihon/commit/119bcbf8ed2415664922ea77fadf0da1165d1732)) | ||||
| - Filter by "Tracking" temporarily stuck after signing out of tracker ([@AntsyLich](https://github.com/AntsyLich)) ([#987](https://github.com/mihonapp/mihon/pull/987)) | ||||
|   - Fix login prompts despite being logged in to trackers in Manga screen ([@AntsyLich](https://github.com/AntsyLich)) ([`cbcd8bd`](https://github.com/mihonapp/mihon/commit/cbcd8bd6682023f728568f2b44da26124618aed7)) | ||||
| - JXL image downloading and loading ([@FooIbar](https://github.com/FooIbar)) ([#993](https://github.com/mihonapp/mihon/pull/993)) | ||||
| - Crash when using `%` in category name ([@Animeboynz](https://github.com/Animeboynz), [@FooIbar](https://github.com/FooIbar)) ([#1030](https://github.com/mihonapp/mihon/pull/1030)) | ||||
| - Fix item disappearing when fast scrolling ([@cuong-tran](https://github.com/cuong-tran)) ([#1035](https://github.com/mihonapp/mihon/pull/1035)) | ||||
| - Library is backed up while being disabled ([@AntsyLich](https://github.com/AntsyLich)) ([`56fb4f6`](https://github.com/mihonapp/mihon/commit/56fb4f62a152e87a71892aa68c78cac51a2c8596)) | ||||
| - Crash on list with only sticky header ([@cuong-tran](https://github.com/cuong-tran)) ([#1083](https://github.com/mihonapp/mihon/pull/1083)) | ||||
| - Crash 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 when trying to clear cookies of some source ([@FooIbar](https://github.com/FooIbar)) ([#1084](https://github.com/mihonapp/mihon/pull/1084)) | ||||
| - MAL search results not showing start dates ([@MajorTanya](https://github.com/MajorTanya)) ([#1098](https://github.com/mihonapp/mihon/pull/1098)) | ||||
| - Android SDK 35 API collision ([@AntsyLich](https://github.com/AntsyLich)) ([`fdb9617`](https://github.com/mihonapp/mihon/commit/fdb96179c6373eb0a8e7d6daea671a315d5ce5f0)) | ||||
| - Manga next update calculation when considering custom fetch interval ([@cuong-tran](https://github.com/cuong-tran)) ([#1206](https://github.com/mihonapp/mihon/pull/1206)) | ||||
| - WheelPicker Manual Input ([@Animeboynz](https://github.com/Animeboynz)) ([#1209](https://github.com/mihonapp/mihon/pull/1209)) | ||||
| - EnhancedTracker not auto binding when adding manga to library ([@brewkunz](https://github.com/brewkunz)) ([#1298](https://github.com/mihonapp/mihon/pull/1298)) | ||||
| - Step count in settings slider ([@abdurisaq](https://github.com/abdurisaq)) ([#1356](https://github.com/mihonapp/mihon/pull/1356)) | ||||
| - Freezing in some screens due to blocking call ([@cuong-tran](https://github.com/cuong-tran)) ([#1364](https://github.com/mihonapp/mihon/pull/1364)) | ||||
| - Crash when removing non-existent tracked entry from tracker ([@cuong-tran](https://github.com/cuong-tran)) ([#1380](https://github.com/mihonapp/mihon/pull/1380)) | ||||
|  | ||||
| ### Other | ||||
| - Code cleanup | ||||
|   - Minor refactor of theming when expressions ([@MajorTanya](https://github.com/MajorTanya)) ([#396](https://github.com/mihonapp/mihon/pull/396))  | ||||
|   - Inside `WorkerInfoScreen` ([@AntsyLich](https://github.com/AntsyLich)) ([`5aec8f8`](https://github.com/mihonapp/mihon/commit/5aec8f8018236a38106483da08f9cbc28261ac9b)) | ||||
|   - Inside `ChapterDownloadIndicator`, `MangaChapterListItem` ([@AntsyLich](https://github.com/AntsyLich)) ([`b7e091d`](https://github.com/mihonapp/mihon/commit/b7e091d5d039e00cababc7daf555280df6cf9c03)) | ||||
|   - MangaCoverFetcher ([@ivaniskandar](https://github.com/ivaniskandar)) ([`1365695`](https://github.com/mihonapp/mihon/commit/13656959ae0606736f6ca9eb62699dc23e467c2f)) | ||||
| - Cleanup `LibraryScreenModel` `LibraryMap.applySort` and some more ([@AntsyLich](https://github.com/AntsyLich)) ([`2beb89d`](https://github.com/mihonapp/mihon/commit/2beb89d53163a6d288f8acdebe0f5d26fea8ab3e)) | ||||
| - Address `overridePendingTransition` deprecation ([@MajorTanya](https://github.com/MajorTanya)) ([#410](https://github.com/mihonapp/mihon/pull/410)) | ||||
| - Prioritize extension classes and files over app ([@beer-psi](https://github.com/beer-psi)) ([#433](https://github.com/mihonapp/mihon/pull/433)) | ||||
| - Use compose pager implementation ([@ivaniskandar](https://github.com/ivaniskandar)) ([`84984ef`](https://github.com/mihonapp/mihon/commit/84984ef7e1d7242924120cd2f171cb9dd75bc916)) | ||||
| - Switch to coil3 from coil2 ([@ivaniskandar](https://github.com/ivaniskandar)) ([`f72b6e4`](https://github.com/mihonapp/mihon/commit/f72b6e4d7c1f2f93d705402e4d80c94160bef54d)) | ||||
|   - Fix GIF not playing ([@jobobby04](https://github.com/jobobby04)) ([`59bedb3`](https://github.com/mihonapp/mihon/commit/59bedb33ff59ad5db1df2e93567a2266fb63eacc)) | ||||
| - Accommodate db for sync support ([@kaiserbh](https://github.com/kaiserbh)) ([#450](https://github.com/mihonapp/mihon/pull/450)) | ||||
| - Fix webtoon last visible item position calculation ([@FooIbar](https://github.com/FooIbar)) ([#562](https://github.com/mihonapp/mihon/pull/562)) | ||||
| - Migrate from `com.google.accompanist:accompanist-webview` to `io.github.kevinnzou:compose-webview` ([@sirlag](https://github.com/sirlag)) ([#569](https://github.com/mihonapp/mihon/pull/569)) | ||||
| - Rewrite migrations ([@ghostbear](https://github.com/ghostbear)) ([#577](https://github.com/mihonapp/mihon/pull/577)) | ||||
|   - Further improve migration ([@ghostbear](https://github.com/ghostbear)) ([#588](https://github.com/mihonapp/mihon/pull/588)) | ||||
|   - Fix migrations not running ([@ghostbear](https://github.com/ghostbear)) ([#604](https://github.com/mihonapp/mihon/pull/604)) | ||||
|   - Fix MigratorTest after updating to Kotlin 2 ([@cuong-tran](https://github.com/cuong-tran)) ([#896](https://github.com/mihonapp/mihon/pull/896)) | ||||
|   - Add MigratorTest to build script ([@cuong-tran](https://github.com/cuong-tran)) ([#896](https://github.com/mihonapp/mihon/pull/896)) | ||||
|   - Fix UI freeze after migration ([@AntsyLich](https://github.com/AntsyLich)) ([`3f1d28c`](https://github.com/mihonapp/mihon/commit/3f1d28c3833e6b868152149ed02b3fb8c54eccef)) | ||||
|   - Fix some migrations never running ([@MajorTanya](https://github.com/MajorTanya), [@AntsyLich](https://github.com/AntsyLich)) ([#1030](https://github.com/mihonapp/mihon/pull/1030)) | ||||
| - Add ProGuard rule to keep `mihon` namespace classes ([@MajorTanya](https://github.com/MajorTanya)) ([#605](https://github.com/mihonapp/mihon/pull/605)) | ||||
| - Use gradle plugins to share build configuration instead of subprojects ([@AntsyLich](https://github.com/AntsyLich)) ([`e448e40`](https://github.com/mihonapp/mihon/commit/e448e40406e8d9916120a278e42829a6f1b25a7a)) | ||||
| - Remove dependency on compose material 2 components ([@AntsyLich](https://github.com/AntsyLich)) ([`fb94230`](https://github.com/mihonapp/mihon/commit/fb9423028eb017c110cb805f2d0601e5b02e50f9)) | ||||
| - Upload PR build artifacts to GitHub ([@FooIbar](https://github.com/FooIbar)) ([#941](https://github.com/mihonapp/mihon/pull/941)) | ||||
| - Refactor archive support with libarchive ([@FooIbar](https://github.com/FooIbar)) ([#949](https://github.com/mihonapp/mihon/pull/949)) | ||||
|   - Add safeguard to prevent ArchiveInputStream from being closed twice ([@null2264](https://github.com/null2264)) ([#967](https://github.com/mihonapp/mihon/pull/967)) | ||||
|   - Move archive related code to :core:archive ([@AntsyLich](https://github.com/AntsyLich)) ([`bd7b354`](https://github.com/mihonapp/mihon/commit/bd7b35419861df6d426d6ec0a188391910d0f615)) | ||||
| - Replace detekt with ktlint via spotless ([@AntsyLich](https://github.com/AntsyLich)) ([#1130](https://github.com/mihonapp/mihon/pull/1130), [#1136](https://github.com/mihonapp/mihon/pull/1136), [#1138](https://github.com/mihonapp/mihon/pull/1138)) | ||||
|   - Refrain from running spotless on weblate files ([@AntsyLich](https://github.com/AntsyLich)) ([`32d2c2a`](https://github.com/mihonapp/mihon/commit/32d2c2ac1bc224cbda2f09a4023d7d120ea0e954)) | ||||
| - Use feature flags in compose compiler plugin ([@AntsyLich](https://github.com/AntsyLich)) ([`8f9a325`](https://github.com/mihonapp/mihon/commit/8f9a325895bb7b94c2ec92dd969094fc30b3b5e2))- PagerPageHolder: lazy init loading indicator ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`a45eb5e`](https://github.com/mihonapp/mihon/commit/a45eb5e5288159dbbbbb5f92140ce0dd32a8f3ab)) | ||||
| - Collect MangaScreen state with lifecycle ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`03eb756`](https://github.com/mihonapp/mihon/commit/03eb756ecba0692d88d3a76254afc4c157fa225b)) | ||||
| - Add stable marker to Manga data class ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`03eb756`](https://github.com/mihonapp/mihon/commit/03eb756ecba0692d88d3a76254afc4c157fa225b)) | ||||
| - Use DTOs to parse tracking API responses ([@MajorTanya](https://github.com/MajorTanya)) ([#1103](https://github.com/mihonapp/mihon/pull/1103)) | ||||
|   - Fix Kitsu ratingTwenty being typed as String ([@MajorTanya](https://github.com/MajorTanya)) ([#1191](https://github.com/mihonapp/mihon/pull/1191)) | ||||
|   - Fix Kitsu `synopsis` nullability ([@MajorTanya](https://github.com/MajorTanya)) ([#1233](https://github.com/mihonapp/mihon/pull/1233)) | ||||
|   - Fix AniList `ALSearchItem.status` nullibility ([@Secozzi](https://github.com/Secozzi)) ([#1297](https://github.com/mihonapp/mihon/pull/1297)) | ||||
| - Migrate some classpaths to gradle plugins ([@AntsyLich](https://github.com/AntsyLich)) ([`fc1c804`](https://github.com/mihonapp/mihon/commit/fc1c804bfda1d76c0399bbb6214e75b3def951cc)) | ||||
| - Add crashlytics to standard builds ([@AntsyLich](https://github.com/AntsyLich)) ([`3c611b9`](https://github.com/mihonapp/mihon/commit/3c611b95fb79e5ac972019b76c7b24f46a3087fd)) | ||||
| - Switch to stable compose ([@AntsyLich](https://github.com/AntsyLich)) ([`2baffa6`](https://github.com/mihonapp/mihon/commit/2baffa62cade1abd978d5fd03151b47fc87fd31e)) | ||||
| - Switch from inorichi injekt to kohesive Injekt ([@AntsyLich](https://github.com/AntsyLich)) ([#1205](https://github.com/mihonapp/mihon/pull/1205)) | ||||
|   - Use custom injekt register with inorichi patch ([@AntsyLich](https://github.com/AntsyLich)) ([`83fd474`](https://github.com/mihonapp/mihon/commit/83fd4746eda1b99f35292b0c2211e606a421b3eb)) | ||||
| - Use TextFieldState in BasicTextField where applicable (p) ([#1201](https://github.com/mihonapp/mihon/pull/1201)) | ||||
| - Bump NDK version ([@AntsyLich](https://github.com/AntsyLich)) ([#1203](https://github.com/mihonapp/mihon/pull/1203)) | ||||
| - Move firebase permission removal to standard flavor ([@AntsyLich](https://github.com/AntsyLich)) ([`be671b4`](https://github.com/mihonapp/mihon/commit/be671b42cefd70180644e01bb065a18cb7701bf9)) | ||||
| - Adjust distinct checker in WidgetManager and run on default dispatcher (p) ([`9b8ab6a`](https://github.com/mihonapp/mihon/commit/9b8ab6acc25a5f99c9c5eebf9cc250975931c57c)) | ||||
| - Update resources exclusion rules (p) ([`481cfed`](https://github.com/mihonapp/mihon/commit/481cfedf08576cecfbb35616837bd8f627d8f959)) | ||||
| - Bump compile sdk to 35 (p) ([`37419cd`](https://github.com/mihonapp/mihon/commit/37419cdc26c2b5c4f8583fc2ba439b08fab42856)) | ||||
| - ChapterNavigator: dispatch page change only when needed (p) ([`f84d9a0`](https://github.com/mihonapp/mihon/commit/f84d9a08b4af768b1e9920c43cc445c86f5427fc)) | ||||
| - Remove usage of deprecated accompanist SystemUiController ([@AntsyLich](https://github.com/AntsyLich)) ([`2ba3f06`](https://github.com/mihonapp/mihon/commit/2ba3f0612c08c7021fed2f6d96cd538da2f34a13)) | ||||
| - Run PR check when base strings are changed ([@AntsyLich](https://github.com/AntsyLich)) ([`4051f18`](https://github.com/mihonapp/mihon/commit/4051f180a2e36e8a2cde6c55f0bea7952fdc4704)) | ||||
|   - Fix PR build check ([@AntsyLich](https://github.com/AntsyLich)) ([`9503082`](https://github.com/mihonapp/mihon/commit/9503082d44b5bd868ee1bfc42741dc978d1d9047)) | ||||
| - Cleanup .gitignore files ([@AntsyLich](https://github.com/AntsyLich)) ([`afa5002`](https://github.com/mihonapp/mihon/commit/afa50029882655af8d5eea40aed7644fce4564d8)) | ||||
| - Pass uncaught exception to default handler in GlobalExceptionHandler (so it's reported to crashlytics) ([@AntsyLich](https://github.com/AntsyLich)) ([`f3a2f56`](https://github.com/mihonapp/mihon/commit/f3a2f566c8a09ab862758ae69b43da2a2cd8f1db)) | ||||
|  | ||||
| ## [v0.16.5] - 2024-04-09 | ||||
| ### Added | ||||
| - Relative date for up to a week in the future ([@sirlag](https://github.com/sirlag)) ([#415](https://github.com/mihonapp/mihon/pull/415)) | ||||
| - Advance setting to install custom color profiles ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523)) | ||||
| - 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)) | ||||
|  | ||||
| ### Changed | ||||
| - Permanently enable 32-bit color mode ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523)) | ||||
|  | ||||
| ### Fixed | ||||
| - Wrong dates in Updates and History tab due to time zone issues ([@sirlag](https://github.com/sirlag)) ([#402](https://github.com/mihonapp/mihon/pull/402)) | ||||
|   - Fix extra date header introduced by parent PR ([@sirlag](https://github.com/sirlag)) ([#415](https://github.com/mihonapp/mihon/pull/415)) | ||||
|   - Fix build time in about screen displayed in UTC ([@AntsyLich](https://github.com/AntsyLich)) ([`aed53d3`](https://github.com/mihonapp/mihon/commit/aed53d3bdc85ce0e899fbb90b9f9cad0f1b86480)) | ||||
| - App infinitely retries tracker update instead of failing after 3 tries ([@MajorTanya](https://github.com/MajorTanya)) ([#411](https://github.com/mihonapp/mihon/pull/411)) | ||||
| - Crash on Pixel devices (was introduced due to compose update) ([`ab06720`](https://github.com/mihonapp/mihon/commit/ab067209661eceefc04c65f6bdbfcaa8a1264651)) | ||||
| - Crash when opening some heif/heic images ([@az4521](https://github.com/az4521)) ([#466](https://github.com/mihonapp/mihon/pull/466)) | ||||
| - Crash when putting app in background while track date selection dialog is open ([@ivaniskandar](https://github.com/ivaniskandar)) ([`c348fac`](https://github.com/mihonapp/mihon/commit/c348fac78fac479fb123bd617c01c78b9ca851d5)) | ||||
| - Dates for saved images not following the specification (fixes date issue mainly on Samsung devices) ([@MajorTanya](https://github.com/MajorTanya)) ([#552](https://github.com/mihonapp/mihon/pull/552)) | ||||
| - Colors getting distorted when opening CMYK jpeg images ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523)) | ||||
| - 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)) | ||||
|  | ||||
| ## [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.4] - 2024-02-26 | ||||
| ### Fixed | ||||
| - Circumvent MAL block ([@AntsyLich](https://github.com/AntsyLich)) ([`085ad8d`](https://github.com/mihonapp/mihon/commit/085ad8d44637c375a8ed24aba3a6f75f5b0cc9ee)) | ||||
|  | ||||
| ## [v0.16.3] - 2024-01-30 | ||||
| ### Added | ||||
| - Copy extension debug info when clicking logo or name in the extension details screen ([@MajorTanya](https://github.com/MajorTanya)) ([#271](https://github.com/mihonapp/mihon/pull/271)) | ||||
|  | ||||
| ### Changed | ||||
| - Hide display cutoff setting in reader settings sheet if fullscreen is disabled ([@Riztard](https://github.com/Riztard)) ([#241](https://github.com/mihonapp/mihon/pull/241)) | ||||
| - Library update error filename to `mihon_update_errors.txt` from `tachiyomi_update_errors.txt` ([@mjishnu](https://github.com/mjishnu)) ([#253](https://github.com/mihonapp/mihon/pull/253)) | ||||
| - 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)) | ||||
|  | ||||
| ### Fixed | ||||
| - Bottom sheet UI issues on non-tablet devices ([@theolm](https://github.com/theolm)) ([#182](https://github.com/mihonapp/mihon/pull/182)) | ||||
| - Crash when switching screen while a list is scrolling ([@theolm](https://github.com/theolm)) ([#272](https://github.com/mihonapp/mihon/pull/272)) | ||||
| - Newly installed extensions not being recognized by Mihon ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#275](https://github.com/mihonapp/mihon/pull/275)) | ||||
| - Failing to refresh MAL token being inferred as token expiration ([@AntsyLich](https://github.com/AntsyLich)) ([`0f4de03`](https://github.com/mihonapp/mihon/commit/0f4de03d7a77b52490dc9a95e96a308b93b26e4f)) | ||||
|  | ||||
| ### Other | ||||
| - Add `detekt` (kotlin code analyzer) to the project ([@theolm](https://github.com/theolm)) ([#216](https://github.com/mihonapp/mihon/pull/216)) | ||||
| - 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)) | ||||
|  | ||||
| ## [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 | ||||
| - 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)) | ||||
| - Rename crash log filename to `mihon_crash_logs.txt` ([@MajorTanya](https://github.com/MajorTanya)) ([#234](https://github.com/mihonapp/mihon/pull/234)) | ||||
|  | ||||
| ### Fixed | ||||
| - "Flash screen on page change" making the screen full black ([@AntsyLich](https://github.com/AntsyLich)) ([`38d6ab8`](https://github.com/mihonapp/mihon/commit/38d6ab80ce868707829dbc81de4170afe3c2f2a5)) | ||||
| - Faulty MangaUpdates score in database ([@AntsyLich](https://github.com/AntsyLich) ([`a024218`](https://github.com/mihonapp/mihon/commit/a024218410953a389b8af4880fa7ae6cc30124a2) | ||||
| - "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)) | ||||
| - Updating extension not reflecting correctly ([@AntsyLich](https://github.com/AntsyLich)) ([`cb06898`](https://github.com/mihonapp/mihon/commit/cb068984303f811692531bf6f14902ae118d8ac7)) | ||||
| - Inconsistent button height in "Data and storage" for some languages ([@theolm](https://github.com/theolm)) ([#202](https://github.com/mihonapp/mihon/pull/202)) | ||||
| - Chapter not being marked as read locally when refreshing Enhanced Trackers ([@Secozzi](https://github.com/Secozzi)) ([#219](https://github.com/mihonapp/mihon/pull/219)) | ||||
|  | ||||
| ### Other | ||||
| - Make `last_modified_at` field in database be `0` on insert ([@kaiserbh](https://github.com/kaiserbh)) ([#113](https://github.com/mihonapp/mihon/pull/113)) | ||||
| - Remove usage of `.not()` where possible in code ([@AntsyLich](https://github.com/AntsyLich)) ([`3940740`](https://github.com/mihonapp/mihon/commit/39407407f282dbb7fa972b12053c26b3e3bd66d8)) | ||||
| - Use type-safe project accessors ([@theolm](https://github.com/theolm)) ([#194](https://github.com/mihonapp/mihon/pull/194)) | ||||
| - Legacy tracker model properties now has the same type as the domain ones ([@AntsyLich](https://github.com/AntsyLich)) ([#245](https://github.com/mihonapp/mihon/pull/245)) | ||||
| - 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)) | ||||
|  | ||||
| ## [v0.16.1] - 2024-01-18 | ||||
| ### Changed | ||||
| - Branding to Mihon (for references we missed) ([@AntsyLich](https://github.com/AntsyLich)) ([`6539406`](https://github.com/mihonapp/mihon/commit/653940613d661eb371aab3b3c3a8181e4e308c43)) | ||||
| - Preview builds are now called Beta builds ([@AntsyLich](https://github.com/AntsyLich)) ([`3c3a1cd`](https://github.com/mihonapp/mihon/commit/3c3a1cd448ab1f653ddd12b2afe0cba38968d1b9)) | ||||
|  | ||||
| ### Fixed | ||||
| - App icon not following the [specification](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive) ([@AntsyLich](https://github.com/AntsyLich)) ([`1849715`](https://github.com/mihonapp/mihon/commit/18497154183356bb0d469b27827f9f7d6b7a3130)) | ||||
| - App Icon not filled ([@AntsyLich](https://github.com/AntsyLich)) ([`1849715`](https://github.com/mihonapp/mihon/commit/18497154183356bb0d469b27827f9f7d6b7a3130)) | ||||
| - MangaUpdates default score being set to -1.0 ([@AntsyLich](https://github.com/AntsyLich)) ([`99fd273`](https://github.com/mihonapp/mihon/commit/99fd2731f5d9d374700e89fa67d4d5bf611bbafa)) | ||||
|  | ||||
| ## [v0.16.0] - 2024-01-16 | ||||
| ### Changed | ||||
| - Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich)) | ||||
| - Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e)) | ||||
|  | ||||
| [unreleased]: https://github.com/mihonapp/mihon/compare/v0.17.1...main | ||||
| [v0.17.1]: https://github.com/mihonapp/mihon/compare/v0.17.0...v0.17.1 | ||||
| [v0.17.0]: https://github.com/mihonapp/mihon/compare/v0.16.5...v0.17.0 | ||||
| "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 | ||||
| [v0.16.5]: https://github.com/mihonapp/mihon/compare/v0.16.4...v0.16.5 | ||||
| [v0.16.4]: https://github.com/mihonapp/mihon/compare/v0.16.3...v0.16.4 | ||||
| [v0.16.3]: https://github.com/mihonapp/mihon/compare/v0.16.2...v0.16.3 | ||||
| [v0.16.2]: https://github.com/mihonapp/mihon/compare/v0.16.1...v0.16.2 | ||||
| [v0.16.1]: https://github.com/mihonapp/mihon/compare/v0.16.0...v0.16.1 | ||||
| [v0.16.0]: https://github.com/mihonapp/mihon/compare/a9c7cbf...v0.16.0 | ||||
| [v0.16.0]: https://github.com/mihonapp/mihon/releases/tag/v0.16.0 | ||||
|   | ||||
| @@ -68,7 +68,7 @@ The developer(s) of this application does not have any affiliation with the cont | ||||
|  | ||||
| <pre> | ||||
| Copyright © 2015 Javier Tomás | ||||
| Copyright © 2024 Mihon Open Source Project | ||||
| Copyright © 2024 The Mihon Open Source Project | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
|   | ||||
							
								
								
									
										3
									
								
								app/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| /build | ||||
| *iml | ||||
| *.iml | ||||
| @@ -1,8 +1,7 @@ | ||||
| @file:Suppress("ChromeOsAbiSupport") | ||||
|  | ||||
| import mihon.buildlogic.getBuildTime | ||||
| import mihon.buildlogic.getCommitCount | ||||
| import mihon.buildlogic.getGitSha | ||||
| import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||||
|  | ||||
| plugins { | ||||
|     id("mihon.android.application") | ||||
| @@ -29,8 +28,8 @@ android { | ||||
|     defaultConfig { | ||||
|         applicationId = "app.mihon" | ||||
|  | ||||
|         versionCode = 9 | ||||
|         versionName = "0.17.1" | ||||
|         versionCode = 7 | ||||
|         versionName = "0.16.5" | ||||
|  | ||||
|         buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") | ||||
|         buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") | ||||
| @@ -143,24 +142,6 @@ android { | ||||
|     } | ||||
| } | ||||
|  | ||||
| kotlin { | ||||
|     compilerOptions { | ||||
|         freeCompilerArgs.addAll( | ||||
|             "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", | ||||
|             "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi", | ||||
|             "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", | ||||
|             "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", | ||||
|             "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", | ||||
|             "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", | ||||
|             "-opt-in=coil3.annotation.ExperimentalCoilApi", | ||||
|             "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", | ||||
|             "-opt-in=kotlinx.coroutines.FlowPreview", | ||||
|             "-opt-in=kotlinx.coroutines.InternalCoroutinesApi", | ||||
|             "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation(projects.i18n) | ||||
|     implementation(projects.core.archive) | ||||
| @@ -298,6 +279,28 @@ androidComponents { | ||||
|     } | ||||
| } | ||||
|  | ||||
| tasks { | ||||
|     // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers) | ||||
|     withType<KotlinCompile> { | ||||
|         compilerOptions.freeCompilerArgs.addAll( | ||||
|             "-Xcontext-receivers", | ||||
|             "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", | ||||
|             "-opt-in=androidx.compose.material.ExperimentalMaterialApi", | ||||
|             "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", | ||||
|             "-opt-in=androidx.compose.material.ExperimentalMaterialApi", | ||||
|             "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", | ||||
|             "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", | ||||
|             "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", | ||||
|             "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi", | ||||
|             "-opt-in=coil3.annotation.ExperimentalCoilApi", | ||||
|             "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", | ||||
|             "-opt-in=kotlinx.coroutines.FlowPreview", | ||||
|             "-opt-in=kotlinx.coroutines.InternalCoroutinesApi", | ||||
|             "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| buildscript { | ||||
|     dependencies { | ||||
|         classpath(kotlinx.gradle) | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| package mihon.core.firebase | ||||
|  | ||||
| import android.content.Context | ||||
|  | ||||
| object FirebaseConfig { | ||||
|     fun init(context: Context) = Unit | ||||
|  | ||||
|     fun setAnalyticsEnabled(enabled: Boolean) = Unit | ||||
|  | ||||
|     fun setCrashlyticsEnabled(enabled: Boolean) = Unit | ||||
| } | ||||
| @@ -1,12 +1,11 @@ | ||||
| package eu.kanade.core.util | ||||
|  | ||||
| import androidx.compose.ui.util.fastFilter | ||||
| import androidx.compose.ui.util.fastForEach | ||||
| import kotlin.contracts.ExperimentalContracts | ||||
| import kotlin.contracts.contract | ||||
|  | ||||
| fun <T : R, R : Any> List<T>.insertSeparators( | ||||
|     generator: (before: T?, after: T?) -> R?, | ||||
|     generator: (T?, T?) -> R?, | ||||
| ): List<R> { | ||||
|     if (isEmpty()) return emptyList() | ||||
|     val newList = mutableListOf<R>() | ||||
| @@ -20,24 +19,6 @@ 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) | ||||
| @@ -46,6 +27,21 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns a list containing only elements matching the given [predicate]. | ||||
|  * | ||||
|  * **Do not use for collections that come from public APIs**, since they may not support random | ||||
|  * access in an efficient way, and this method may actually be a lot slower. Only use for | ||||
|  * collections that are created by code we control and are known to support random access. | ||||
|  */ | ||||
| @OptIn(ExperimentalContracts::class) | ||||
| inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> { | ||||
|     contract { callsInPlace(predicate) } | ||||
|     val destination = ArrayList<T>() | ||||
|     fastForEach { if (predicate(it)) destination.add(it) } | ||||
|     return destination | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns a list containing all elements not matching the given [predicate]. | ||||
|  * | ||||
| @@ -56,7 +52,27 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) { | ||||
| @OptIn(ExperimentalContracts::class) | ||||
| inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> { | ||||
|     contract { callsInPlace(predicate) } | ||||
|     return fastFilter { !predicate(it) } | ||||
|     val destination = ArrayList<T>() | ||||
|     fastForEach { if (!predicate(it)) destination.add(it) } | ||||
|     return destination | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns a list containing only the non-null results of applying the | ||||
|  * given [transform] function to each element in the original collection. | ||||
|  * | ||||
|  * **Do not use for collections that come from public APIs**, since they may not support random | ||||
|  * access in an efficient way, and this method may actually be a lot slower. Only use for | ||||
|  * collections that are created by code we control and are known to support random access. | ||||
|  */ | ||||
| @OptIn(ExperimentalContracts::class) | ||||
| inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> { | ||||
|     contract { callsInPlace(transform) } | ||||
|     val destination = ArrayList<R>() | ||||
|     fastForEach { element -> | ||||
|         transform(element)?.let(destination::add) | ||||
|     } | ||||
|     return destination | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -97,3 +113,26 @@ inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int { | ||||
|     fastForEach { if (predicate(it)) --count } | ||||
|     return count | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns a list containing only elements from the given collection | ||||
|  * having distinct keys returned by the given [selector] function. | ||||
|  * | ||||
|  * Among elements of the given collection with equal keys, only the first one will be present in the resulting list. | ||||
|  * The elements in the resulting list are in the same order as they were in the source collection. | ||||
|  * | ||||
|  * **Do not use for collections that come from public APIs**, since they may not support random | ||||
|  * access in an efficient way, and this method may actually be a lot slower. Only use for | ||||
|  * collections that are created by code we control and are known to support random access. | ||||
|  */ | ||||
| @OptIn(ExperimentalContracts::class) | ||||
| inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> { | ||||
|     contract { callsInPlace(selector) } | ||||
|     val set = HashSet<K>() | ||||
|     val list = ArrayList<T>() | ||||
|     fastForEach { | ||||
|         val key = selector(it) | ||||
|         if (set.add(key)) list.add(it) | ||||
|     } | ||||
|     return list | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,6 @@ package eu.kanade.domain.base | ||||
|  | ||||
| import android.content.Context | ||||
| import dev.icerock.moko.resources.StringResource | ||||
| import eu.kanade.tachiyomi.util.system.GLUtil | ||||
| import tachiyomi.core.common.preference.Preference | ||||
| import tachiyomi.core.common.preference.PreferenceStore | ||||
| import tachiyomi.i18n.MR | ||||
| @@ -31,6 +30,4 @@ class BasePreferences( | ||||
|     } | ||||
|  | ||||
|     fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "") | ||||
|  | ||||
|     fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT) | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,6 @@ 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 | ||||
| @@ -15,16 +14,17 @@ 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 { | ||||
|             trackerManager.loggedInTrackers() | ||||
|             getTracks.await(manga.id) | ||||
|                 .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(idRequired = false)!!) | ||||
|                             insertTrack.await(track.toDomainTrack()!!) | ||||
|  | ||||
|                             syncChapterProgressWithTrack.await( | ||||
|                                 manga.id, | ||||
|                                 track.toDomainTrack(idRequired = false)!!, | ||||
|                                 track.toDomainTrack()!!, | ||||
|                                 service, | ||||
|                             ) | ||||
|                         } | ||||
|   | ||||
| @@ -1,10 +0,0 @@ | ||||
| package eu.kanade.domain.track.model | ||||
|  | ||||
| import dev.icerock.moko.resources.StringResource | ||||
| import tachiyomi.i18n.MR | ||||
|  | ||||
| enum class AutoTrackState(val titleRes: StringResource) { | ||||
|     ALWAYS(MR.strings.lock_always), | ||||
|     ASK(MR.strings.default_category_summary), | ||||
|     NEVER(MR.strings.lock_never), | ||||
| } | ||||
| @@ -1,11 +1,9 @@ | ||||
| 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, | ||||
| @@ -37,9 +35,4 @@ 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, | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.calculateStartPadding | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.pager.HorizontalPager | ||||
| import androidx.compose.foundation.pager.PagerState | ||||
| import androidx.compose.foundation.pager.rememberPagerState | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.PrimaryTabRow | ||||
| @@ -15,6 +14,7 @@ import androidx.compose.material3.SnackbarHost | ||||
| import androidx.compose.material3.SnackbarHostState | ||||
| import androidx.compose.material3.Tab | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.LaunchedEffect | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.rememberCoroutineScope | ||||
| import androidx.compose.ui.Alignment | ||||
| @@ -33,13 +33,20 @@ import tachiyomi.presentation.core.i18n.stringResource | ||||
| fun TabbedScreen( | ||||
|     titleRes: StringResource, | ||||
|     tabs: ImmutableList<TabContent>, | ||||
|     state: PagerState = rememberPagerState { tabs.size }, | ||||
|     startIndex: Int? = null, | ||||
|     searchQuery: String? = null, | ||||
|     onChangeSearchQuery: (String?) -> Unit = {}, | ||||
| ) { | ||||
|     val scope = rememberCoroutineScope() | ||||
|     val state = rememberPagerState { tabs.size } | ||||
|     val snackbarHostState = remember { SnackbarHostState() } | ||||
|  | ||||
|     LaunchedEffect(startIndex) { | ||||
|         if (startIndex != null) { | ||||
|             state.scrollToPage(startIndex) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Scaffold( | ||||
|         topBar = { | ||||
|             val tab = tabs[state.currentPage] | ||||
|   | ||||
| @@ -166,32 +166,32 @@ private fun ColumnScope.SortPage( | ||||
|     val sortingMode = category.sort.type | ||||
|     val sortDescending = !category.sort.isAscending | ||||
|  | ||||
|     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, | ||||
|         ) | ||||
|     val trackerSortOption = if (trackers.isEmpty()) { | ||||
|         emptyList() | ||||
|     } else { | ||||
|         listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean) | ||||
|     } | ||||
|  | ||||
|     options.map { (titleRes, mode) -> | ||||
|     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) -> | ||||
|         if (mode == LibrarySort.Type.Random) { | ||||
|             val enabledIcon = if (sortingMode == LibrarySort.Type.Random) { | ||||
|                 Icons.Default.Refresh | ||||
|             } else { | ||||
|                 null | ||||
|             } | ||||
|             BaseSortItem( | ||||
|                 label = stringResource(titleRes), | ||||
|                 icon = Icons.Default.Refresh | ||||
|                     .takeIf { sortingMode == LibrarySort.Type.Random }, | ||||
|                 icon = enabledIcon, | ||||
|                 onClick = { | ||||
|                     screenModel.setSort(category, mode, LibrarySort.Direction.Ascending) | ||||
|                 }, | ||||
|   | ||||
| @@ -21,12 +21,11 @@ internal fun LibraryTabs( | ||||
|     getNumberOfMangaForCategory: (Category) -> Int?, | ||||
|     onTabItemClick: (Int) -> Unit, | ||||
| ) { | ||||
|     val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex) | ||||
|     Column( | ||||
|         modifier = Modifier.zIndex(1f), | ||||
|     ) { | ||||
|         PrimaryScrollableTabRow( | ||||
|             selectedTabIndex = currentPageIndex, | ||||
|             selectedTabIndex = pagerState.currentPage, | ||||
|             edgePadding = 0.dp, | ||||
|             // TODO: use default when width is fixed upstream | ||||
|             // https://issuetracker.google.com/issues/242879624 | ||||
| @@ -34,7 +33,7 @@ internal fun LibraryTabs( | ||||
|         ) { | ||||
|             categories.forEachIndexed { index, category -> | ||||
|                 Tab( | ||||
|                     selected = currentPageIndex == index, | ||||
|                     selected = pagerState.currentPage == index, | ||||
|                     onClick = { onTabItemClick(index) }, | ||||
|                     text = { | ||||
|                         TabText( | ||||
|   | ||||
| @@ -14,13 +14,11 @@ 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 | ||||
| @@ -36,18 +34,13 @@ 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) | ||||
|  | ||||
| @@ -80,7 +73,7 @@ internal class PermissionStep : OnboardingStep { | ||||
|         } | ||||
|  | ||||
|         Column { | ||||
|             PermissionCheckbox( | ||||
|             PermissionItem( | ||||
|                 title = stringResource(MR.strings.onboarding_permission_install_apps), | ||||
|                 subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description), | ||||
|                 granted = installGranted, | ||||
| @@ -96,7 +89,7 @@ internal class PermissionStep : OnboardingStep { | ||||
|                         // no-op. resulting checks is being done on resume | ||||
|                     }, | ||||
|                 ) | ||||
|                 PermissionCheckbox( | ||||
|                 PermissionItem( | ||||
|                     title = stringResource(MR.strings.onboarding_permission_notifications), | ||||
|                     subtitle = stringResource(MR.strings.onboarding_permission_notifications_description), | ||||
|                     granted = notificationGranted, | ||||
| @@ -104,7 +97,7 @@ internal class PermissionStep : OnboardingStep { | ||||
|                 ) | ||||
|             } | ||||
|  | ||||
|             PermissionCheckbox( | ||||
|             PermissionItem( | ||||
|                 title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts), | ||||
|                 subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description), | ||||
|                 granted = batteryGranted, | ||||
| @@ -116,29 +109,6 @@ 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, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -157,7 +127,7 @@ internal class PermissionStep : OnboardingStep { | ||||
|     } | ||||
|  | ||||
|     @Composable | ||||
|     private fun PermissionCheckbox( | ||||
|     private fun PermissionItem( | ||||
|         title: String, | ||||
|         subtitle: String, | ||||
|         granted: Boolean, | ||||
| @@ -187,26 +157,4 @@ 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), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -162,12 +162,12 @@ sealed class Preference { | ||||
|  | ||||
|         data class CustomPreference( | ||||
|             override val title: String, | ||||
|             val content: @Composable () -> Unit, | ||||
|         ) : PreferenceItem<Unit>() { | ||||
|             val content: @Composable (PreferenceItem<String>) -> Unit, | ||||
|         ) : PreferenceItem<String>() { | ||||
|             override val enabled: Boolean = true | ||||
|             override val subtitle: String? = null | ||||
|             override val icon: ImageVector? = null | ||||
|             override val onValueChanged: suspend (newValue: Unit) -> Boolean = { true } | ||||
|             override val onValueChanged: suspend (newValue: String) -> Boolean = { true } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -167,7 +167,7 @@ internal fun PreferenceItem( | ||||
|                 InfoWidget(text = item.title) | ||||
|             } | ||||
|             is Preference.PreferenceItem.CustomPreference -> { | ||||
|                 item.content() | ||||
|                 item.content(item) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -47,7 +47,6 @@ import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9 | ||||
| import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN | ||||
| import eu.kanade.tachiyomi.ui.more.OnboardingScreen | ||||
| import eu.kanade.tachiyomi.util.CrashLogUtil | ||||
| import eu.kanade.tachiyomi.util.system.GLUtil | ||||
| import eu.kanade.tachiyomi.util.system.isDevFlavor | ||||
| import eu.kanade.tachiyomi.util.system.isPreviewBuildType | ||||
| import eu.kanade.tachiyomi.util.system.isShizukuInstalled | ||||
| @@ -62,7 +61,6 @@ import logcat.LogPriority | ||||
| import okhttp3.Headers | ||||
| import tachiyomi.core.common.util.lang.launchNonCancellable | ||||
| import tachiyomi.core.common.util.lang.withUIContext | ||||
| import tachiyomi.core.common.util.system.ImageUtil | ||||
| import tachiyomi.core.common.util.system.logcat | ||||
| import tachiyomi.domain.manga.interactor.ResetViewerFlags | ||||
| import tachiyomi.i18n.MR | ||||
| @@ -336,26 +334,6 @@ object SettingsAdvancedScreen : SearchableSettings { | ||||
|         return Preference.PreferenceGroup( | ||||
|             title = stringResource(MR.strings.pref_category_reader), | ||||
|             preferenceItems = persistentListOf( | ||||
|                 Preference.PreferenceItem.ListPreference( | ||||
|                     pref = basePreferences.hardwareBitmapThreshold(), | ||||
|                     title = stringResource(MR.strings.pref_hardware_bitmap_threshold), | ||||
|                     subtitleProvider = { value, options -> | ||||
|                         stringResource(MR.strings.pref_hardware_bitmap_threshold_summary, options[value].orEmpty()) | ||||
|                     }, | ||||
|                     enabled = !ImageUtil.HARDWARE_BITMAP_UNSUPPORTED && | ||||
|                         GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT, | ||||
|                     entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS | ||||
|                         .mapIndexed { index, option -> | ||||
|                             val display = if (index == 0) { | ||||
|                                 stringResource(MR.strings.pref_hardware_bitmap_threshold_default, option) | ||||
|                             } else { | ||||
|                                 option.toString() | ||||
|                             } | ||||
|                             option to display | ||||
|                         } | ||||
|                         .toMap() | ||||
|                         .toImmutableMap(), | ||||
|                 ), | ||||
|                 Preference.PreferenceItem.TextPreference( | ||||
|                     title = stringResource(MR.strings.pref_display_profile), | ||||
|                     subtitle = basePreferences.displayProfile().get(), | ||||
|   | ||||
| @@ -15,6 +15,7 @@ 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 | ||||
| @@ -34,7 +35,7 @@ object SettingsDownloadScreen : SearchableSettings { | ||||
|     @Composable | ||||
|     override fun getPreferences(): List<Preference> { | ||||
|         val getCategories = remember { Injekt.get<GetCategories>() } | ||||
|         val allCategories by getCategories.subscribe().collectAsState(initial = emptyList()) | ||||
|         val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() }) | ||||
|  | ||||
|         val downloadPreferences = remember { Injekt.get<DownloadPreferences>() } | ||||
|         return listOf( | ||||
|   | ||||
| @@ -24,6 +24,7 @@ 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 | ||||
| @@ -52,7 +53,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 = emptyList()) | ||||
|         val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() }) | ||||
|  | ||||
|         return listOf( | ||||
|             getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences), | ||||
|   | ||||
| @@ -7,7 +7,6 @@ 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 | ||||
| @@ -29,91 +28,55 @@ object SettingsSecurityScreen : SearchableSettings { | ||||
|  | ||||
|     @Composable | ||||
|     override fun getPreferences(): List<Preference> { | ||||
|         val securityPreferences = remember { Injekt.get<SecurityPreferences>() } | ||||
|         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 securityPreferences = remember { Injekt.get<SecurityPreferences>() } | ||||
|         val authSupported = remember { context.isAuthenticationSupported() } | ||||
|  | ||||
|         val useAuthPref = securityPreferences.useAuthenticator() | ||||
|         val useAuth by useAuthPref.collectAsState() | ||||
|  | ||||
|         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) | ||||
|                             } | ||||
|         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) | ||||
|                         } | ||||
|                         .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)), | ||||
|                     } | ||||
|                     .toImmutableMap(), | ||||
|                 onValueChanged = { | ||||
|                     (context as FragmentActivity).authenticate( | ||||
|                         title = context.stringResource(MR.strings.lock_when_idle), | ||||
|                     ) | ||||
|                 }, | ||||
|             ), | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @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.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)), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -40,7 +40,6 @@ 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 | ||||
| @@ -54,7 +53,6 @@ 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 | ||||
| @@ -87,7 +85,6 @@ 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 { | ||||
| @@ -128,13 +125,6 @@ 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, | ||||
|     onOpenInWebView: (() -> Unit)?, | ||||
|     onOpenInBrowser: (() -> Unit)?, | ||||
|     onOpenInWebView: (() -> Unit)?, | ||||
|     onShare: (() -> Unit)?, | ||||
|  | ||||
|     viewer: Viewer?, | ||||
| @@ -56,7 +56,7 @@ fun ReaderAppBars( | ||||
|     enabledPrevious: Boolean, | ||||
|     currentPage: Int, | ||||
|     totalPages: Int, | ||||
|     onPageIndexChange: (Int) -> Unit, | ||||
|     onSliderValueChange: (Int) -> Unit, | ||||
|  | ||||
|     readingMode: ReadingMode, | ||||
|     onClickReadingMode: () -> Unit, | ||||
| @@ -120,14 +120,6 @@ fun ReaderAppBars( | ||||
|                                         onClick = onToggleBookmarked, | ||||
|                                     ), | ||||
|                                 ) | ||||
|                                 onOpenInWebView?.let { | ||||
|                                     add( | ||||
|                                         AppBar.OverflowAction( | ||||
|                                             title = stringResource(MR.strings.action_open_in_web_view), | ||||
|                                             onClick = it, | ||||
|                                         ), | ||||
|                                     ) | ||||
|                                 } | ||||
|                                 onOpenInBrowser?.let { | ||||
|                                     add( | ||||
|                                         AppBar.OverflowAction( | ||||
| @@ -136,6 +128,14 @@ fun ReaderAppBars( | ||||
|                                         ), | ||||
|                                     ) | ||||
|                                 } | ||||
|                                 onOpenInWebView?.let { | ||||
|                                     add( | ||||
|                                         AppBar.OverflowAction( | ||||
|                                             title = stringResource(MR.strings.action_open_in_web_view), | ||||
|                                             onClick = it, | ||||
|                                         ), | ||||
|                                     ) | ||||
|                                 } | ||||
|                                 onShare?.let { | ||||
|                                     add( | ||||
|                                         AppBar.OverflowAction( | ||||
| @@ -176,8 +176,9 @@ fun ReaderAppBars( | ||||
|                     enabledPrevious = enabledPrevious, | ||||
|                     currentPage = currentPage, | ||||
|                     totalPages = totalPages, | ||||
|                     onPageIndexChange = onPageIndexChange, | ||||
|                     onSliderValueChange = onSliderValueChange, | ||||
|                 ) | ||||
|  | ||||
|                 BottomReaderBar( | ||||
|                     backgroundColor = backgroundColor, | ||||
|                     readingMode = readingMode, | ||||
|   | ||||
| @@ -4,7 +4,6 @@ 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 | ||||
| @@ -17,6 +16,7 @@ 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,7 +29,6 @@ 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 | ||||
| @@ -39,8 +38,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( | ||||
| @@ -51,7 +50,7 @@ fun ChapterNavigator( | ||||
|     enabledPrevious: Boolean, | ||||
|     currentPage: Int, | ||||
|     totalPages: Int, | ||||
|     onPageIndexChange: (Int) -> Unit, | ||||
|     onSliderValueChange: (Int) -> Unit, | ||||
| ) { | ||||
|     val isTabletUi = isTabletUi() | ||||
|     val horizontalPadding = if (isTabletUi) 24.dp else 8.dp | ||||
| @@ -98,11 +97,7 @@ fun ChapterNavigator( | ||||
|                             .padding(horizontal = 16.dp), | ||||
|                         verticalAlignment = Alignment.CenterVertically, | ||||
|                     ) { | ||||
|                         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) | ||||
|                         } | ||||
|                         Text(text = currentPage.toString()) | ||||
|  | ||||
|                         val interactionSource = remember { MutableInteractionSource() } | ||||
|                         val sliderDragged by interactionSource.collectIsDraggedAsState() | ||||
| @@ -115,11 +110,14 @@ fun ChapterNavigator( | ||||
|                             modifier = Modifier | ||||
|                                 .weight(1f) | ||||
|                                 .padding(horizontal = 8.dp), | ||||
|                             value = currentPage, | ||||
|                             valueRange = 1..totalPages, | ||||
|                             onValueChange = f@{ | ||||
|                                 if (it == currentPage) return@f | ||||
|                                 onPageIndexChange(it - 1) | ||||
|                             value = currentPage.toFloat(), | ||||
|                             valueRange = 1f..totalPages.toFloat(), | ||||
|                             steps = totalPages - 2, | ||||
|                             onValueChange = { | ||||
|                                 val new = it.roundToInt() - 1 | ||||
|                                 if (new != currentPage) { | ||||
|                                     onSliderValueChange(new) | ||||
|                                 } | ||||
|                             }, | ||||
|                             interactionSource = interactionSource, | ||||
|                         ) | ||||
| @@ -160,7 +158,7 @@ private fun ChapterNavigatorPreview() { | ||||
|             enabledPrevious = true, | ||||
|             currentPage = currentPage, | ||||
|             totalPages = 10, | ||||
|             onPageIndexChange = { currentPage = (it + 1) }, | ||||
|             onSliderValueChange = { currentPage = it }, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -26,7 +26,6 @@ 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 | ||||
| @@ -41,7 +40,6 @@ import eu.kanade.tachiyomi.network.NetworkHelper | ||||
| import eu.kanade.tachiyomi.network.NetworkPreferences | ||||
| import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate | ||||
| import eu.kanade.tachiyomi.util.system.DeviceUtil | ||||
| import eu.kanade.tachiyomi.util.system.GLUtil | ||||
| import eu.kanade.tachiyomi.util.system.WebViewUtil | ||||
| import eu.kanade.tachiyomi.util.system.animatorDurationScale | ||||
| import eu.kanade.tachiyomi.util.system.cancelNotification | ||||
| @@ -52,14 +50,12 @@ 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 | ||||
| import tachiyomi.core.common.i18n.stringResource | ||||
| import tachiyomi.core.common.preference.Preference | ||||
| import tachiyomi.core.common.preference.PreferenceStore | ||||
| import tachiyomi.core.common.util.system.ImageUtil | ||||
| import tachiyomi.core.common.util.system.logcat | ||||
| import tachiyomi.i18n.MR | ||||
| import tachiyomi.presentation.widget.WidgetManager | ||||
| @@ -71,7 +67,6 @@ 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() | ||||
| @@ -80,7 +75,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor | ||||
|     override fun onCreate() { | ||||
|         super<Application>.onCreate() | ||||
|         patchInjekt() | ||||
|         FirebaseConfig.init(applicationContext) | ||||
|  | ||||
|         GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java) | ||||
|  | ||||
| @@ -103,8 +97,6 @@ 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 -> | ||||
| @@ -132,30 +124,14 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor | ||||
|                     cancelNotification(Notifications.ID_INCOGNITO_MODE) | ||||
|                 } | ||||
|             } | ||||
|             .launchIn(scope) | ||||
|  | ||||
|         privacyPreferences.analytics() | ||||
|             .changes() | ||||
|             .onEach(FirebaseConfig::setAnalyticsEnabled) | ||||
|             .launchIn(scope) | ||||
|  | ||||
|         privacyPreferences.crashlytics() | ||||
|             .changes() | ||||
|             .onEach(FirebaseConfig::setCrashlyticsEnabled) | ||||
|             .launchIn(scope) | ||||
|  | ||||
|         basePreferences.hardwareBitmapThreshold().let { preference -> | ||||
|             if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT) | ||||
|         } | ||||
|  | ||||
|         basePreferences.hardwareBitmapThreshold().changes() | ||||
|             .onEach { ImageUtil.hardwareBitmapThreshold = it } | ||||
|             .launchIn(scope) | ||||
|             .launchIn(ProcessLifecycleOwner.get().lifecycleScope) | ||||
|  | ||||
|         setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get()) | ||||
|  | ||||
|         // Updates widget update | ||||
|         WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) } | ||||
|         with(WidgetManager(Injekt.get(), Injekt.get())) { | ||||
|             init(ProcessLifecycleOwner.get().lifecycleScope) | ||||
|         } | ||||
|  | ||||
|         if (!LogcatLogger.isInstalled && networkPreferences.verboseLogging().get()) { | ||||
|             LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE)) | ||||
|   | ||||
| @@ -11,6 +11,7 @@ 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, | ||||
| @@ -30,9 +31,13 @@ class GlobalExceptionHandler private constructor( | ||||
|     } | ||||
|  | ||||
|     override fun uncaughtException(thread: Thread, exception: Throwable) { | ||||
|         logcat(priority = LogPriority.ERROR, throwable = exception) | ||||
|         launchActivity(applicationContext, activityToBeLaunched, exception) | ||||
|         defaultHandler.uncaughtException(thread, exception) | ||||
|         try { | ||||
|             logcat(priority = LogPriority.ERROR, throwable = exception) | ||||
|             launchActivity(applicationContext, activityToBeLaunched, exception) | ||||
|             exitProcess(0) | ||||
|         } catch (_: Exception) { | ||||
|             defaultHandler.uncaughtException(thread, exception) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun launchActivity( | ||||
|   | ||||
| @@ -27,7 +27,6 @@ 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 | ||||
| @@ -44,7 +43,6 @@ 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(), | ||||
| @@ -77,9 +75,7 @@ class BackupCreator( | ||||
|                 throw IllegalStateException(context.stringResource(MR.strings.create_backup_file_error)) | ||||
|             } | ||||
|  | ||||
|             val nonFavoriteManga = if (options.readEntries) mangaRepository.getReadMangaNotInLibrary() else emptyList() | ||||
|             val backupManga = backupMangas(getFavorites.await() + nonFavoriteManga, options) | ||||
|  | ||||
|             val backupManga = backupMangas(getFavorites.await(), options) | ||||
|             val backup = Backup( | ||||
|                 backupManga = backupManga, | ||||
|                 backupCategories = backupCategories(options), | ||||
|   | ||||
| @@ -10,7 +10,6 @@ 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, | ||||
| @@ -23,7 +22,6 @@ data class BackupOptions( | ||||
|         chapters, | ||||
|         tracking, | ||||
|         history, | ||||
|         readEntries, | ||||
|         appSettings, | ||||
|         extensionRepoSettings, | ||||
|         sourceSettings, | ||||
| @@ -62,12 +60,6 @@ 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( | ||||
| @@ -100,11 +92,10 @@ data class BackupOptions( | ||||
|             chapters = array[2], | ||||
|             tracking = array[3], | ||||
|             history = array[4], | ||||
|             readEntries = array[5], | ||||
|             appSettings = array[6], | ||||
|             extensionRepoSettings = array[7], | ||||
|             sourceSettings = array[8], | ||||
|             privateSettings = array[9], | ||||
|             appSettings = array[5], | ||||
|             extensionRepoSettings = array[6], | ||||
|             sourceSettings = array[7], | ||||
|             privateSettings = array[8], | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import coil3.decode.ImageSource | ||||
| import coil3.fetch.SourceFetchResult | ||||
| import coil3.request.Options | ||||
| import coil3.request.bitmapConfig | ||||
| import eu.kanade.tachiyomi.util.system.GLUtil | ||||
| import okio.BufferedSource | ||||
| import tachiyomi.core.common.util.system.ImageUtil | ||||
| import tachiyomi.decoder.ImageDecoder | ||||
| @@ -45,7 +46,10 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti | ||||
|  | ||||
|         check(bitmap != null) { "Failed to decode image" } | ||||
|  | ||||
|         if (options.bitmapConfig == Bitmap.Config.HARDWARE && ImageUtil.canUseHardwareBitmap(bitmap)) { | ||||
|         if ( | ||||
|             options.bitmapConfig == Bitmap.Config.HARDWARE && | ||||
|             maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize | ||||
|         ) { | ||||
|             val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false) | ||||
|             if (hwBitmap != null) { | ||||
|                 bitmap.recycle() | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName") | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.database.models | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName") | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.database.models | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName") | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.database.models | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName") | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
|  | ||||
| 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 rootDownloadsDirMutex = Mutex() | ||||
|     private val rootDownloadsDirLock = Mutex() | ||||
|     private var rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory()) | ||||
|  | ||||
|     init { | ||||
|         // Attempt to read cache file | ||||
|         scope.launch { | ||||
|             rootDownloadsDirMutex.withLock { | ||||
|             rootDownloadsDirLock.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 from disk cache" } | ||||
|                     logcat(LogPriority.ERROR, e) { "Failed to initialize 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) { | ||||
|         rootDownloadsDirMutex.withLock { | ||||
|         rootDownloadsDirLock.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) { | ||||
|         rootDownloadsDirMutex.withLock { | ||||
|         rootDownloadsDirLock.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) { | ||||
|         rootDownloadsDirMutex.withLock { | ||||
|         rootDownloadsDirLock.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) { | ||||
|         rootDownloadsDirMutex.withLock { | ||||
|         rootDownloadsDirLock.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) { | ||||
|         rootDownloadsDirMutex.withLock { | ||||
|         rootDownloadsDirLock.withLock { | ||||
|             rootDownloadsDir.sourceDirs -= source.id | ||||
|         } | ||||
|  | ||||
| @@ -322,10 +322,10 @@ class DownloadCache( | ||||
|  | ||||
|             val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id } | ||||
|  | ||||
|             rootDownloadsDirMutex.withLock { | ||||
|                 val updatedRootDir = RootDirectory(storageManager.getDownloadsDirectory()) | ||||
|             rootDownloadsDirLock.withLock { | ||||
|                 rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory()) | ||||
|  | ||||
|                 updatedRootDir.sourceDirs = updatedRootDir.dir?.listFiles().orEmpty() | ||||
|                 val sourceDirs = rootDownloadsDir.dir?.listFiles().orEmpty() | ||||
|                     .filter { it.isDirectory && !it.name.isNullOrBlank() } | ||||
|                     .mapNotNull { dir -> | ||||
|                         val sourceId = sourceMap[dir.name!!.lowercase()] | ||||
| @@ -333,35 +333,36 @@ class DownloadCache( | ||||
|                     } | ||||
|                     .toMap() | ||||
|  | ||||
|                 updatedRootDir.sourceDirs.values.map { sourceDir -> | ||||
|                     async { | ||||
|                         sourceDir.mangaDirs = sourceDir.dir?.listFiles().orEmpty() | ||||
|                             .filter { it.isDirectory && !it.name.isNullOrBlank() } | ||||
|                             .associate { it.name!! to MangaDirectory(it) } | ||||
|                 rootDownloadsDir.sourceDirs = sourceDirs | ||||
|  | ||||
|                         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 | ||||
|                 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 | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                                 .toMutableSet() | ||||
|                                     .toMutableSet() | ||||
|  | ||||
|                             mangaDir.chapterDirs = chapterDirs | ||||
|                                 mangaDir.chapterDirs = chapterDirs | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                     .awaitAll() | ||||
|  | ||||
|                 rootDownloadsDir = updatedRootDir | ||||
|             } | ||||
|  | ||||
|             _isInitializing.emit(false) | ||||
|   | ||||
| @@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.data.library | ||||
|  | ||||
| import android.content.Context | ||||
| import android.content.pm.ServiceInfo | ||||
| import android.net.NetworkCapabilities | ||||
| import android.net.NetworkRequest | ||||
| import android.os.Build | ||||
| import androidx.work.BackoffPolicy | ||||
| import androidx.work.Constraints | ||||
| @@ -94,12 +92,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet | ||||
|  | ||||
|     override suspend fun doWork(): Result { | ||||
|         if (tags.contains(WORK_NAME_AUTO)) { | ||||
|             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { | ||||
|                 val preferences = Injekt.get<LibraryPreferences>() | ||||
|                 val restrictions = preferences.autoUpdateDeviceRestrictions().get() | ||||
|                 if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) { | ||||
|                     return Result.retry() | ||||
|                 } | ||||
|             val preferences = Injekt.get<LibraryPreferences>() | ||||
|             val restrictions = preferences.autoUpdateDeviceRestrictions().get() | ||||
|             if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) { | ||||
|                 return Result.retry() | ||||
|             } | ||||
|  | ||||
|             // Find a running manual worker. If exists, try again later | ||||
| @@ -436,24 +432,15 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet | ||||
|             val interval = prefInterval ?: preferences.autoUpdateInterval().get() | ||||
|             if (interval > 0) { | ||||
|                 val restrictions = preferences.autoUpdateDeviceRestrictions().get() | ||||
|                 val networkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { | ||||
|                     NetworkType.UNMETERED | ||||
|                 } else { | ||||
|                     NetworkType.CONNECTED | ||||
|                 } | ||||
|                 val networkRequestBuilder = NetworkRequest.Builder() | ||||
|                 if (DEVICE_ONLY_ON_WIFI in restrictions) { | ||||
|                     networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI) | ||||
|                 } | ||||
|                 if (DEVICE_NETWORK_NOT_METERED in restrictions) { | ||||
|                     networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) | ||||
|                 } | ||||
|                 val constraints = Constraints.Builder() | ||||
|                     // 'networkRequest' only applies to Android 9+, otherwise 'networkType' is used | ||||
|                     .setRequiredNetworkRequest(networkRequestBuilder.build(), networkType) | ||||
|                     .setRequiresCharging(DEVICE_CHARGING in restrictions) | ||||
|                     .setRequiresBatteryNotLow(true) | ||||
|                     .build() | ||||
|                 val constraints = Constraints( | ||||
|                     requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { | ||||
|                         NetworkType.UNMETERED | ||||
|                     } else { | ||||
|                         NetworkType.CONNECTED | ||||
|                     }, | ||||
|                     requiresCharging = DEVICE_CHARGING in restrictions, | ||||
|                     requiresBatteryNotLow = true, | ||||
|                 ) | ||||
|  | ||||
|                 val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>( | ||||
|                     interval.toLong(), | ||||
|   | ||||
| @@ -71,7 +71,6 @@ object Notifications { | ||||
|     const val CHANNEL_APP_UPDATE = "app_apk_update_channel" | ||||
|     const val ID_APP_UPDATER = 1 | ||||
|     const val ID_APP_UPDATE_PROMPT = 2 | ||||
|     const val ID_APP_UPDATE_ERROR = 3 | ||||
|     const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel" | ||||
|     const val ID_UPDATES_TO_EXTS = -401 | ||||
|     const val ID_EXTENSION_INSTALLER = -402 | ||||
|   | ||||
| @@ -175,7 +175,6 @@ sealed class Image( | ||||
| } | ||||
|  | ||||
| sealed interface Location { | ||||
|     @ConsistentCopyVisibility | ||||
|     data class Pictures private constructor(val relativePath: String) : Location { | ||||
|         companion object { | ||||
|             fun create(relativePath: String = ""): Pictures { | ||||
|   | ||||
| @@ -71,8 +71,6 @@ class BangumiApi( | ||||
|             val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}" | ||||
|                 .toUri() | ||||
|                 .buildUpon() | ||||
|                 .appendQueryParameter("type", "1") | ||||
|                 .appendQueryParameter("responseGroup", "large") | ||||
|                 .appendQueryParameter("max_results", "20") | ||||
|                 .build() | ||||
|             with(json) { | ||||
| @@ -83,6 +81,7 @@ class BangumiApi( | ||||
|                         if (result.code == 404) emptyList<TrackSearch>() | ||||
|  | ||||
|                         result.list | ||||
|                             ?.filter { it.type == 1 } | ||||
|                             ?.map { it.toTrackSearch(trackId) } | ||||
|                             .orEmpty() | ||||
|                     } | ||||
|   | ||||
| @@ -17,7 +17,6 @@ data class BGMSearchItem( | ||||
|     val nameCn: String, | ||||
|     val name: String, | ||||
|     val type: Int, | ||||
|     val summary: String?, | ||||
|     val images: BGMSearchItemCovers?, | ||||
|     @SerialName("eps_count") | ||||
|     val epsCount: Long?, | ||||
| @@ -26,13 +25,9 @@ data class BGMSearchItem( | ||||
| ) { | ||||
|     fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply { | ||||
|         remote_id = this@BGMSearchItem.id | ||||
|         title = nameCn.ifBlank { name } | ||||
|         cover_url = images?.common.orEmpty() | ||||
|         summary = if (nameCn.isNotBlank()) { | ||||
|             "作品原名:$name" + this@BGMSearchItem.summary?.let { "\n$it" }.orEmpty() | ||||
|         } else { | ||||
|             this@BGMSearchItem.summary.orEmpty() | ||||
|         } | ||||
|         title = nameCn | ||||
|         cover_url = images?.common ?: "" | ||||
|         summary = this@BGMSearchItem.name | ||||
|         score = rating?.score ?: -1.0 | ||||
|         tracking_url = url | ||||
|         total_chapters = epsCount ?: 0 | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("PropertyName") | ||||
| @file:Suppress("PropertyName", "ktlint:standard:property-naming") | ||||
|  | ||||
| package eu.kanade.tachiyomi.data.track.model | ||||
|  | ||||
|   | ||||
| @@ -181,9 +181,9 @@ internal class AppUpdateNotifier(private val context: Context) { | ||||
|             addAction( | ||||
|                 R.drawable.ic_close_24dp, | ||||
|                 context.stringResource(MR.strings.action_cancel), | ||||
|                 NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATE_ERROR), | ||||
|                 NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATER), | ||||
|             ) | ||||
|         } | ||||
|         notificationBuilder.show(Notifications.ID_APP_UPDATE_ERROR) | ||||
|         notificationBuilder.show(Notifications.ID_APP_UPDATER) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,6 @@ 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 | ||||
| @@ -40,9 +39,6 @@ class PreferenceModule(val app: Application) : InjektModule { | ||||
|         addSingletonFactory { | ||||
|             SecurityPreferences(get()) | ||||
|         } | ||||
|         addSingletonFactory { | ||||
|             PrivacyPreferences(get()) | ||||
|         } | ||||
|         addSingletonFactory { | ||||
|             LibraryPreferences(get()) | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.browse | ||||
| import androidx.compose.animation.graphics.res.animatedVectorResource | ||||
| import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter | ||||
| import androidx.compose.animation.graphics.vector.AnimatedImageVector | ||||
| import androidx.compose.foundation.pager.rememberPagerState | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.LaunchedEffect | ||||
| import androidx.compose.runtime.collectAsState | ||||
| @@ -23,14 +22,12 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen | ||||
| import eu.kanade.tachiyomi.ui.browse.source.sourcesTab | ||||
| import eu.kanade.tachiyomi.ui.main.MainActivity | ||||
| import kotlinx.collections.immutable.persistentListOf | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.channels.Channel | ||||
| import kotlinx.coroutines.flow.collectLatest | ||||
| import kotlinx.coroutines.flow.receiveAsFlow | ||||
| import tachiyomi.i18n.MR | ||||
| import tachiyomi.presentation.core.i18n.stringResource | ||||
|  | ||||
| data object BrowseTab : Tab { | ||||
| data class BrowseTab( | ||||
|     private val toExtensions: Boolean = false, | ||||
| ) : Tab { | ||||
|  | ||||
|     override val options: TabOptions | ||||
|         @Composable | ||||
| @@ -48,12 +45,6 @@ data object BrowseTab : Tab { | ||||
|         navigator.push(GlobalSearchScreen()) | ||||
|     } | ||||
|  | ||||
|     private val switchToExtensionTabChannel = Channel<Unit>(1, BufferOverflow.DROP_OLDEST) | ||||
|  | ||||
|     fun showExtension() { | ||||
|         switchToExtensionTabChannel.trySend(Unit) | ||||
|     } | ||||
|  | ||||
|     @Composable | ||||
|     override fun Content() { | ||||
|         val context = LocalContext.current | ||||
| @@ -62,25 +53,17 @@ data object BrowseTab : Tab { | ||||
|         val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() } | ||||
|         val extensionsState by extensionsScreenModel.state.collectAsState() | ||||
|  | ||||
|         val tabs = persistentListOf( | ||||
|             sourcesTab(), | ||||
|             extensionsTab(extensionsScreenModel), | ||||
|             migrateSourceTab(), | ||||
|         ) | ||||
|  | ||||
|         val state = rememberPagerState { tabs.size } | ||||
|  | ||||
|         TabbedScreen( | ||||
|             titleRes = MR.strings.browse, | ||||
|             tabs = tabs, | ||||
|             state = state, | ||||
|             tabs = persistentListOf( | ||||
|                 sourcesTab(), | ||||
|                 extensionsTab(extensionsScreenModel), | ||||
|                 migrateSourceTab(), | ||||
|             ), | ||||
|             startIndex = 1.takeIf { toExtensions }, | ||||
|             searchQuery = extensionsState.searchQuery, | ||||
|             onChangeSearchQuery = extensionsScreenModel::search, | ||||
|         ) | ||||
|         LaunchedEffect(Unit) { | ||||
|             switchToExtensionTabChannel.receiveAsFlow() | ||||
|                 .collectLatest { state.scrollToPage(1) } | ||||
|         } | ||||
|  | ||||
|         LaunchedEffect(Unit) { | ||||
|             (context as? MainActivity)?.ready = true | ||||
|   | ||||
| @@ -1,15 +1,8 @@ | ||||
| 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 | ||||
| @@ -19,7 +12,6 @@ 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 | ||||
| @@ -29,10 +21,7 @@ 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, | ||||
| @@ -56,13 +45,7 @@ fun extensionsTab( | ||||
|                 onLongClickItem = { extension -> | ||||
|                     when (extension) { | ||||
|                         is Extension.Available -> extensionsScreenModel.installExtension(extension) | ||||
|                         else -> { | ||||
|                             if (context.isPackageInstalled(extension.pkgName)) { | ||||
|                                 extensionsScreenModel.uninstallExtension(extension) | ||||
|                             } else { | ||||
|                                 privateExtensionToUninstall = extension | ||||
|                             } | ||||
|                         } | ||||
|                         else -> extensionsScreenModel.uninstallExtension(extension) | ||||
|                     } | ||||
|                 }, | ||||
|                 onClickItemCancel = extensionsScreenModel::cancelInstallUpdateExtension, | ||||
| @@ -85,50 +68,6 @@ 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, | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.rememberCoroutineScope | ||||
| import androidx.compose.ui.platform.LocalConfiguration | ||||
| import androidx.compose.ui.platform.LocalUriHandler | ||||
| import androidx.paging.compose.collectAsLazyPagingItems | ||||
| import cafe.adriel.voyager.core.model.rememberScreenModel | ||||
| import cafe.adriel.voyager.navigator.LocalNavigator | ||||
| import cafe.adriel.voyager.navigator.currentOrThrow | ||||
| @@ -28,7 +29,6 @@ import eu.kanade.tachiyomi.ui.home.HomeScreen | ||||
| import eu.kanade.tachiyomi.ui.manga.MangaScreen | ||||
| import eu.kanade.tachiyomi.ui.webview.WebViewScreen | ||||
| import kotlinx.coroutines.launch | ||||
| import mihon.presentation.core.util.collectAsLazyPagingItems | ||||
| import tachiyomi.core.common.Constants | ||||
| import tachiyomi.domain.manga.model.Manga | ||||
| import tachiyomi.i18n.MR | ||||
| @@ -81,12 +81,13 @@ data class SourceSearchScreen( | ||||
|             }, | ||||
|             snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, | ||||
|         ) { paddingValues -> | ||||
|             val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState() | ||||
|             val openMigrateDialog: (Manga) -> Unit = { | ||||
|                 screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(newManga = it, oldManga = oldManga)) | ||||
|             } | ||||
|             BrowseSourceContent( | ||||
|                 source = screenModel.source, | ||||
|                 mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(), | ||||
|                 mangaList = pagingFlow.collectAsLazyPagingItems(), | ||||
|                 columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), | ||||
|                 displayMode = screenModel.displayMode, | ||||
|                 snackbarHostState = snackbarHostState, | ||||
|   | ||||
| @@ -31,6 +31,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType | ||||
| import androidx.compose.ui.platform.LocalConfiguration | ||||
| import androidx.compose.ui.platform.LocalHapticFeedback | ||||
| import androidx.compose.ui.platform.LocalUriHandler | ||||
| import androidx.paging.compose.collectAsLazyPagingItems | ||||
| import cafe.adriel.voyager.core.model.rememberScreenModel | ||||
| import cafe.adriel.voyager.navigator.LocalNavigator | ||||
| import cafe.adriel.voyager.navigator.currentOrThrow | ||||
| @@ -55,7 +56,6 @@ import eu.kanade.tachiyomi.ui.webview.WebViewScreen | ||||
| import kotlinx.coroutines.channels.Channel | ||||
| import kotlinx.coroutines.flow.collectLatest | ||||
| import kotlinx.coroutines.flow.receiveAsFlow | ||||
| import mihon.presentation.core.util.collectAsLazyPagingItems | ||||
| import tachiyomi.core.common.Constants | ||||
| import tachiyomi.core.common.util.lang.launchIO | ||||
| import tachiyomi.domain.source.model.StubSource | ||||
| @@ -206,9 +206,11 @@ data class BrowseSourceScreen( | ||||
|             }, | ||||
|             snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, | ||||
|         ) { paddingValues -> | ||||
|             val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState() | ||||
|  | ||||
|             BrowseSourceContent( | ||||
|                 source = screenModel.source, | ||||
|                 mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(), | ||||
|                 mangaList = pagingFlow.collectAsLazyPagingItems(), | ||||
|                 columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), | ||||
|                 displayMode = screenModel.displayMode, | ||||
|                 snackbarHostState = snackbarHostState, | ||||
|   | ||||
| @@ -32,7 +32,7 @@ import tachiyomi.domain.chapter.model.Chapter | ||||
| import tachiyomi.i18n.MR | ||||
| import tachiyomi.presentation.core.i18n.stringResource | ||||
|  | ||||
| data object HistoryTab : Tab { | ||||
| object HistoryTab : Tab { | ||||
|  | ||||
|     private val snackbarHostState = SnackbarHostState() | ||||
|  | ||||
|   | ||||
| @@ -69,11 +69,11 @@ object HomeScreen : Screen() { | ||||
|     private const val TAB_FADE_DURATION = 200 | ||||
|     private const val TAB_NAVIGATOR_KEY = "HomeTabs" | ||||
|  | ||||
|     private val TABS = listOf( | ||||
|     private val tabs = listOf( | ||||
|         LibraryTab, | ||||
|         UpdatesTab, | ||||
|         HistoryTab, | ||||
|         BrowseTab, | ||||
|         BrowseTab(), | ||||
|         MoreTab, | ||||
|     ) | ||||
|  | ||||
| @@ -90,7 +90,7 @@ object HomeScreen : Screen() { | ||||
|                     startBar = { | ||||
|                         if (isTabletUi()) { | ||||
|                             NavigationRail { | ||||
|                                 TABS.fastForEach { | ||||
|                                 tabs.fastForEach { | ||||
|                                     NavigationRailItem(it) | ||||
|                                 } | ||||
|                             } | ||||
| @@ -107,7 +107,7 @@ object HomeScreen : Screen() { | ||||
|                                 exit = shrinkVertically(), | ||||
|                             ) { | ||||
|                                 NavigationBar { | ||||
|                                     TABS.fastForEach { | ||||
|                                     tabs.fastForEach { | ||||
|                                         NavigationBarItem(it) | ||||
|                                     } | ||||
|                                 } | ||||
| @@ -159,12 +159,7 @@ object HomeScreen : Screen() { | ||||
|                             is Tab.Library -> LibraryTab | ||||
|                             Tab.Updates -> UpdatesTab | ||||
|                             Tab.History -> HistoryTab | ||||
|                             is Tab.Browse -> { | ||||
|                                 if (it.toExtensions) { | ||||
|                                     BrowseTab.showExtension() | ||||
|                                 } | ||||
|                                 BrowseTab | ||||
|                             } | ||||
|                             is Tab.Browse -> BrowseTab(it.toExtensions) | ||||
|                             is Tab.More -> MoreTab | ||||
|                         } | ||||
|  | ||||
|   | ||||
| @@ -4,15 +4,15 @@ import androidx.compose.runtime.Immutable | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.util.fastAny | ||||
| import androidx.compose.ui.util.fastDistinctBy | ||||
| import androidx.compose.ui.util.fastFilter | ||||
| import androidx.compose.ui.util.fastMap | ||||
| import androidx.compose.ui.util.fastMapNotNull | ||||
| import cafe.adriel.voyager.core.model.StateScreenModel | ||||
| import cafe.adriel.voyager.core.model.screenModelScope | ||||
| import eu.kanade.core.preference.PreferenceMutableState | ||||
| import eu.kanade.core.preference.asState | ||||
| import eu.kanade.core.util.fastDistinctBy | ||||
| import eu.kanade.core.util.fastFilter | ||||
| import eu.kanade.core.util.fastFilterNot | ||||
| import eu.kanade.core.util.fastMapNotNull | ||||
| import eu.kanade.core.util.fastPartition | ||||
| import eu.kanade.domain.base.BasePreferences | ||||
| import eu.kanade.domain.chapter.interactor.SetReadStatus | ||||
| @@ -302,15 +302,16 @@ class LibraryScreenModel( | ||||
|                     item1Score.compareTo(item2Score) | ||||
|                 } | ||||
|                 LibrarySort.Type.Random -> { | ||||
|                     error("Why Are We Still Here? Just To Suffer?") | ||||
|                     error("A comparator should not be requested for the random sort style. Instead, intercept this " + | ||||
|                         "case and call .shuffle()") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return mapValues { (key, value) -> | ||||
|             if (key.sort.type == LibrarySort.Type.Random) { | ||||
|                 return@mapValues value.shuffled(Random(libraryPreferences.randomSortSeed().get())) | ||||
|             } | ||||
|            if (key.sort.type == LibrarySort.Type.Random) { | ||||
|                return@mapValues value.shuffled(Random(libraryPreferences.currentRandomSortSeed().get())) | ||||
|            } | ||||
|  | ||||
|             val comparator = key.sort.comparator() | ||||
|                 .let { if (key.sort.isAscending) it else it.reversed() } | ||||
|   | ||||
| @@ -61,7 +61,7 @@ import tachiyomi.presentation.core.screens.EmptyScreenAction | ||||
| import tachiyomi.presentation.core.screens.LoadingScreen | ||||
| import tachiyomi.source.local.isLocal | ||||
|  | ||||
| data object LibraryTab : Tab { | ||||
| object LibraryTab : Tab { | ||||
|  | ||||
|     override val options: TabOptions | ||||
|         @Composable | ||||
|   | ||||
| @@ -278,13 +278,12 @@ class MainActivity : BaseActivity() { | ||||
|     @Composable | ||||
|     private fun HandleOnNewIntent(context: Context, navigator: Navigator) { | ||||
|         LaunchedEffect(Unit) { | ||||
|             callbackFlow { | ||||
|             callbackFlow<Intent> { | ||||
|                 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) } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -340,7 +339,6 @@ 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,8 +25,6 @@ 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 | ||||
| @@ -40,7 +38,6 @@ 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 | ||||
| @@ -95,7 +92,6 @@ 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(), | ||||
| @@ -141,7 +137,6 @@ 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) | ||||
|  | ||||
| @@ -730,29 +725,19 @@ class MangaScreenModel( | ||||
|      */ | ||||
|     fun markChaptersRead(chapters: List<Chapter>, read: Boolean) { | ||||
|         toggleAllSelection(false) | ||||
|         if (chapters.isEmpty()) return | ||||
|         screenModelScope.launchIO { | ||||
|             setReadStatus.await( | ||||
|                 read = read, | ||||
|                 chapters = chapters.toTypedArray(), | ||||
|             ) | ||||
|  | ||||
|             if (!read || successState?.hasLoggedInTrackers == false || autoTrackState == AutoTrackState.NEVER) { | ||||
|                 return@launchIO | ||||
|             } | ||||
|             if (!read) 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,11 +824,7 @@ private data class TrackerRemoveScreen( | ||||
|  | ||||
|         fun deleteMangaFromService() { | ||||
|             screenModelScope.launchNonCancellable { | ||||
|                 try { | ||||
|                     (tracker as DeletableTracker).delete(track) | ||||
|                 } catch (e: Exception) { | ||||
|                     logcat(LogPriority.ERROR, e) { "Failed to delete entry from service" } | ||||
|                 } | ||||
|                 (tracker as DeletableTracker).delete(track) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -38,7 +38,7 @@ import tachiyomi.presentation.core.i18n.stringResource | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
|  | ||||
| data object MoreTab : Tab { | ||||
| object MoreTab : Tab { | ||||
|  | ||||
|     override val options: TabOptions | ||||
|         @Composable | ||||
|   | ||||
| @@ -390,8 +390,8 @@ class ReaderActivity : BaseActivity() { | ||||
|                 onClickTopAppBar = ::openMangaScreen, | ||||
|                 bookmarked = state.bookmarked, | ||||
|                 onToggleBookmarked = viewModel::toggleChapterBookmark, | ||||
|                 onOpenInWebView = ::openChapterInWebView.takeIf { isHttpSource }, | ||||
|                 onOpenInBrowser = ::openChapterInBrowser.takeIf { isHttpSource }, | ||||
|                 onOpenInWebView = ::openChapterInWebView.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, | ||||
|                 onPageIndexChange = { | ||||
|                 onSliderValueChange = { | ||||
|                     isScrollingThroughPages = true | ||||
|                     moveToPageIndex(it) | ||||
|                 }, | ||||
| @@ -565,6 +565,12 @@ 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 | ||||
| @@ -574,12 +580,6 @@ 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") | ||||
|   | ||||
| @@ -36,10 +36,10 @@ import com.github.chrisbanes.photoview.PhotoView | ||||
| import eu.kanade.tachiyomi.data.coil.cropBorders | ||||
| import eu.kanade.tachiyomi.data.coil.customDecoder | ||||
| import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView | ||||
| import eu.kanade.tachiyomi.util.system.GLUtil | ||||
| import eu.kanade.tachiyomi.util.system.animatorDurationScale | ||||
| import eu.kanade.tachiyomi.util.view.isVisibleOnScreen | ||||
| import okio.BufferedSource | ||||
| import tachiyomi.core.common.util.system.ImageUtil | ||||
|  | ||||
| /** | ||||
|  * A wrapper view for showing page image. | ||||
| @@ -233,7 +233,7 @@ open class ReaderPageImageView @JvmOverloads constructor( | ||||
|         } else { | ||||
|             SubsamplingScaleImageView(context) | ||||
|         }.apply { | ||||
|             setMaxTileSize(ImageUtil.hardwareBitmapThreshold) | ||||
|             setMaxTileSize(GLUtil.maxTextureSize) | ||||
|             setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER) | ||||
|             setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) | ||||
|             setMinimumTileDpi(180) | ||||
| @@ -288,44 +288,35 @@ open class ReaderPageImageView @JvmOverloads constructor( | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|         when (data) { | ||||
|             is BitmapDrawable -> { | ||||
|                 setImage(ImageSource.bitmap(data.bitmap)) | ||||
|                 isVisible = true | ||||
|             } | ||||
|             is BufferedSource -> { | ||||
|                 if (!isWebtoon) { | ||||
|                     setHardwareConfig(ImageUtil.canUseHardwareBitmap(data)) | ||||
|                     setImage(ImageSource.inputStream(data.inputStream())) | ||||
|                     isVisible = true | ||||
|                     return@apply | ||||
|                 } | ||||
|  | ||||
|                 ImageRequest.Builder(context) | ||||
|                     .data(data) | ||||
|                     .memoryCachePolicy(CachePolicy.DISABLED) | ||||
|                     .diskCachePolicy(CachePolicy.DISABLED) | ||||
|                     .target( | ||||
|                         onSuccess = { result -> | ||||
|                             val image = result as BitmapImage | ||||
|                             setImage(ImageSource.bitmap(image.bitmap)) | ||||
|                             isVisible = true | ||||
|                         }, | ||||
|                         onError = { | ||||
|                             onImageLoadError() | ||||
|                         }, | ||||
|                     ) | ||||
|                     .size(ViewSizeResolver(this@ReaderPageImageView)) | ||||
|                     .precision(Precision.INEXACT) | ||||
|                     .cropBorders(config.cropBorders) | ||||
|                     .customDecoder(true) | ||||
|                     .crossfade(false) | ||||
|                     .build() | ||||
|                     .let(context.imageLoader::enqueue) | ||||
|             } | ||||
|             else -> { | ||||
|                 throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}") | ||||
|         if (isWebtoon) { | ||||
|             val request = ImageRequest.Builder(context) | ||||
|                 .data(data) | ||||
|                 .memoryCachePolicy(CachePolicy.DISABLED) | ||||
|                 .diskCachePolicy(CachePolicy.DISABLED) | ||||
|                 .target( | ||||
|                     onSuccess = { result -> | ||||
|                         val image = result as BitmapImage | ||||
|                         setImage(ImageSource.bitmap(image.bitmap)) | ||||
|                         isVisible = true | ||||
|                     }, | ||||
|                     onError = { | ||||
|                         this@ReaderPageImageView.onImageLoadError() | ||||
|                     }, | ||||
|                 ) | ||||
|                 .size(ViewSizeResolver(this@ReaderPageImageView)) | ||||
|                 .precision(Precision.INEXACT) | ||||
|                 .cropBorders(config.cropBorders) | ||||
|                 .customDecoder(true) | ||||
|                 .crossfade(false) | ||||
|                 .build() | ||||
|             context.imageLoader.enqueue(request) | ||||
|         } else { | ||||
|             when (data) { | ||||
|                 is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap)) | ||||
|                 is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream())) | ||||
|                 else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}") | ||||
|             } | ||||
|             isVisible = true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| package eu.kanade.tachiyomi.ui.stats | ||||
|  | ||||
| import androidx.compose.ui.util.fastDistinctBy | ||||
| import androidx.compose.ui.util.fastFilter | ||||
| import androidx.compose.ui.util.fastMapNotNull | ||||
| import cafe.adriel.voyager.core.model.StateScreenModel | ||||
| import cafe.adriel.voyager.core.model.screenModelScope | ||||
| import eu.kanade.core.util.fastCountNot | ||||
| import eu.kanade.core.util.fastDistinctBy | ||||
| import eu.kanade.core.util.fastFilter | ||||
| import eu.kanade.core.util.fastFilterNot | ||||
| import eu.kanade.core.util.fastMapNotNull | ||||
| import eu.kanade.presentation.more.stats.StatsScreenState | ||||
| import eu.kanade.presentation.more.stats.data.StatsData | ||||
| import eu.kanade.tachiyomi.data.download.DownloadManager | ||||
|   | ||||
| @@ -31,7 +31,7 @@ import tachiyomi.core.common.i18n.stringResource | ||||
| import tachiyomi.i18n.MR | ||||
| import tachiyomi.presentation.core.i18n.stringResource | ||||
|  | ||||
| data object UpdatesTab : Tab { | ||||
| object UpdatesTab : Tab { | ||||
|  | ||||
|     override val options: TabOptions | ||||
|         @Composable | ||||
|   | ||||
| @@ -15,7 +15,6 @@ import androidx.core.content.getSystemService | ||||
| import androidx.core.net.toUri | ||||
| import com.hippo.unifile.UniFile | ||||
| import eu.kanade.domain.ui.UiPreferences | ||||
| import eu.kanade.domain.ui.model.ThemeMode | ||||
| import eu.kanade.tachiyomi.BuildConfig | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate | ||||
| @@ -108,13 +107,9 @@ fun Context.createFileInCacheDir(name: String): File { | ||||
| fun Context.createReaderThemeContext(): Context { | ||||
|     val preferences = Injekt.get<UiPreferences>() | ||||
|     val readerPreferences = Injekt.get<ReaderPreferences>() | ||||
|     val themeMode = preferences.themeMode().get() | ||||
|     val isDarkBackground = when (readerPreferences.readerTheme().get()) { | ||||
|         1, 2 -> true // Black, Gray | ||||
|         3 -> when (themeMode) { // Automatic bg uses activity background by default | ||||
|             ThemeMode.SYSTEM -> applicationContext.isNightMode() | ||||
|             else -> themeMode == ThemeMode.DARK | ||||
|         } | ||||
|         3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default | ||||
|         else -> false // White | ||||
|     } | ||||
|     val expected = if (isDarkBackground) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import javax.microedition.khronos.egl.EGLContext | ||||
| import kotlin.math.max | ||||
| 
 | ||||
| object GLUtil { | ||||
|     val DEVICE_TEXTURE_LIMIT: Int by lazy { | ||||
|     val maxTextureSize: Int by lazy { | ||||
|         // Get EGL Display | ||||
|         val egl = EGLContext.getEGL() as EGL10 | ||||
|         val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) | ||||
| @@ -38,23 +38,10 @@ object GLUtil { | ||||
|         // Release | ||||
|         egl.eglTerminate(display) | ||||
| 
 | ||||
|         // Return largest texture size found (after making it a multiplier of [Multiplier]), or default | ||||
|         max(maximumTextureSize, SAFE_TEXTURE_LIMIT) | ||||
|     } | ||||
| 
 | ||||
|     const val SAFE_TEXTURE_LIMIT: Int = 2048 | ||||
| 
 | ||||
|     val CUSTOM_TEXTURE_LIMIT_OPTIONS: List<Int> by lazy { | ||||
|         val steps = DEVICE_TEXTURE_LIMIT / MULTIPLIER | ||||
|         buildList(steps) { | ||||
|             add(DEVICE_TEXTURE_LIMIT) | ||||
|             for (step in steps downTo 2) { | ||||
|                 val value = step * MULTIPLIER | ||||
|                 if (value >= DEVICE_TEXTURE_LIMIT) continue | ||||
|                 add(value) | ||||
|             } | ||||
|         } | ||||
|         // Return largest texture size found, or default | ||||
|         max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| private const val MULTIPLIER: Int = 1024 | ||||
| // Safe minimum default size | ||||
| private const val IMAGE_MAX_BITMAP_DIMENSION = 2048 | ||||
| @@ -1,25 +1,18 @@ | ||||
| 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 | ||||
| @@ -34,9 +27,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 | ||||
| @@ -106,33 +99,6 @@ 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, | ||||
| @@ -174,10 +140,7 @@ private fun UpcomingScreenSmallImpl( | ||||
|                     ) | ||||
|                 } | ||||
|                 is UpcomingUIModel.Header -> { | ||||
|                     DateHeading( | ||||
|                         date = item.date, | ||||
|                         mangaCount = item.mangaCount, | ||||
|                     ) | ||||
|                     ListGroupHeader(text = relativeDateText(item.date)) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -225,10 +188,7 @@ private fun UpcomingScreenLargeImpl( | ||||
|                             ) | ||||
|                         } | ||||
|                         is UpcomingUIModel.Header -> { | ||||
|                             DateHeading( | ||||
|                                 date = item.date, | ||||
|                                 mangaCount = item.mangaCount, | ||||
|                             ) | ||||
|                             ListGroupHeader(text = relativeDateText(item.date)) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|   | ||||
| @@ -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.insertSeparatorsReversed | ||||
| import eu.kanade.core.util.insertSeparators | ||||
| 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 = upcomingItems.toEvents(), | ||||
|                         events = it.toEvents(), | ||||
|                         headerIndexes = upcomingItems.getHeaderIndexes(), | ||||
|                     ) | ||||
|                 } | ||||
| @@ -42,16 +42,13 @@ class UpcomingScreenModel( | ||||
|     } | ||||
|  | ||||
|     private fun List<Manga>.toUpcomingUIModels(): ImmutableList<UpcomingUIModel> { | ||||
|         var mangaCount = 0 | ||||
|         return fastMap { UpcomingUIModel.Item(it) } | ||||
|             .insertSeparatorsReversed { before, after -> | ||||
|                 if (after != null) mangaCount++ | ||||
|  | ||||
|             .insertSeparators { before, after -> | ||||
|                 val beforeDate = before?.manga?.expectedNextUpdate?.toLocalDate() | ||||
|                 val afterDate = after?.manga?.expectedNextUpdate?.toLocalDate() | ||||
|  | ||||
|                 if (beforeDate != afterDate && afterDate != null) { | ||||
|                     UpcomingUIModel.Header(afterDate, mangaCount).also { mangaCount = 0 } | ||||
|                     UpcomingUIModel.Header(afterDate) | ||||
|                 } else { | ||||
|                     null | ||||
|                 } | ||||
| @@ -59,9 +56,9 @@ class UpcomingScreenModel( | ||||
|             .toImmutableList() | ||||
|     } | ||||
|  | ||||
|     private fun List<UpcomingUIModel>.toEvents(): ImmutableMap<LocalDate, Int> { | ||||
|         return filterIsInstance<UpcomingUIModel.Header>() | ||||
|             .associate { it.date to it.mangaCount } | ||||
|     private fun List<Manga>.toEvents(): ImmutableMap<LocalDate, Int> { | ||||
|         return groupBy { it.expectedNextUpdate?.toLocalDate() ?: LocalDate.MAX } | ||||
|             .mapValues { it.value.size } | ||||
|             .toImmutableMap() | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,6 @@ import tachiyomi.domain.manga.model.Manga | ||||
| import java.time.LocalDate | ||||
|  | ||||
| sealed interface UpcomingUIModel { | ||||
|     data class Header(val date: LocalDate, val mangaCount: Int) : UpcomingUIModel | ||||
|     data class Header(val date: LocalDate) : UpcomingUIModel | ||||
|     data class Item(val manga: Manga) : UpcomingUIModel | ||||
| } | ||||
|   | ||||
| @@ -20,15 +20,6 @@ | ||||
|         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" | ||||
|   | ||||
| @@ -1,25 +0,0 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								buildSrc/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /build | ||||
| @@ -6,18 +6,6 @@ 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") | ||||
| @@ -35,7 +23,7 @@ spotless { | ||||
|     } | ||||
|     format("xml") { | ||||
|         target("**/*.xml") | ||||
|         targetExclude(*xmlFormatExclude) | ||||
|         targetExclude("**/build/**/*.xml") | ||||
|         trimTrailingWhitespace() | ||||
|         endWithNewline() | ||||
|     } | ||||
|   | ||||
| @@ -45,8 +45,8 @@ internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, | ||||
|         compilerOptions { | ||||
|             jvmTarget.set(AndroidConfig.JvmTarget) | ||||
|             freeCompilerArgs.addAll( | ||||
|                 "-Xcontext-receivers", | ||||
|                 "-opt-in=kotlin.RequiresOptIn", | ||||
|                 "-Xcontext-receivers", | ||||
|             ) | ||||
|  | ||||
|             // Treat all Kotlin warnings as errors (disabled by default) | ||||
|   | ||||
							
								
								
									
										1
									
								
								core-metadata/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								core-metadata/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /build | ||||
							
								
								
									
										1
									
								
								core/archive/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								core/archive/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /build | ||||
							
								
								
									
										1
									
								
								core/common/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								core/common/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /build | ||||
| @@ -6,11 +6,10 @@ plugins { | ||||
|  | ||||
| android { | ||||
|     namespace = "eu.kanade.tachiyomi.core.common" | ||||
| } | ||||
|  | ||||
| kotlin { | ||||
|     compilerOptions { | ||||
|         freeCompilerArgs.addAll( | ||||
|     kotlinOptions { | ||||
|         freeCompilerArgs += listOf( | ||||
|             "-Xcontext-receivers", | ||||
|             "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", | ||||
|             "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", | ||||
|         ) | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| 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) | ||||
| } | ||||
| @@ -19,7 +19,7 @@ class NetworkPreferences( | ||||
|     fun defaultUserAgent(): Preference<String> { | ||||
|         return preferenceStore.getString( | ||||
|             "default_user_agent", | ||||
|             "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0", | ||||
|             "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0", | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| @file:Suppress("FunctionName") | ||||
| @file:Suppress("FunctionName", "ktlint:standard:function-naming") | ||||
|  | ||||
| package eu.kanade.tachiyomi.network | ||||
|  | ||||
|   | ||||
| @@ -64,7 +64,6 @@ object DeviceUtil { | ||||
|  | ||||
|     val invalidDefaultBrowsers = listOf( | ||||
|         "android", | ||||
|         "com.hihonor.android.internal.app", | ||||
|         "com.huawei.android.internal.app", | ||||
|         "com.zui.resolver", | ||||
|     ) | ||||
|   | ||||
| @@ -68,6 +68,7 @@ fun WebView.setDefaultSettings() { | ||||
|     with(settings) { | ||||
|         javaScriptEnabled = true | ||||
|         domStorageEnabled = true | ||||
|         databaseEnabled = true | ||||
|         useWideViewPort = true | ||||
|         loadWithOverviewMode = true | ||||
|         cacheMode = WebSettings.LOAD_DEFAULT | ||||
|   | ||||
| @@ -22,7 +22,6 @@ import androidx.core.graphics.get | ||||
| import androidx.core.graphics.green | ||||
| import androidx.core.graphics.red | ||||
| import com.hippo.unifile.UniFile | ||||
| import eu.kanade.tachiyomi.util.system.GLUtil | ||||
| import logcat.LogPriority | ||||
| import okio.Buffer | ||||
| import okio.BufferedSource | ||||
| @@ -310,23 +309,6 @@ object ImageUtil { | ||||
|         val bottomOffset = topOffset + splitHeight | ||||
|     } | ||||
|  | ||||
|     fun canUseHardwareBitmap(bitmap: Bitmap): Boolean { | ||||
|         return canUseHardwareBitmap(bitmap.width, bitmap.height) | ||||
|     } | ||||
|  | ||||
|     fun canUseHardwareBitmap(imageSource: BufferedSource): Boolean { | ||||
|         return with(extractImageOptions(imageSource)) { | ||||
|             canUseHardwareBitmap(outWidth, outHeight) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     var hardwareBitmapThreshold: Int = GLUtil.SAFE_TEXTURE_LIMIT | ||||
|  | ||||
|     private fun canUseHardwareBitmap(width: Int, height: Int): Boolean { | ||||
|         if (HARDWARE_BITMAP_UNSUPPORTED) return false | ||||
|         return maxOf(width, height) <= hardwareBitmapThreshold | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Algorithm for determining what background to accompany a comic/manga page | ||||
|      */ | ||||
| @@ -573,121 +555,6 @@ object ImageUtil { | ||||
|     } | ||||
|  | ||||
|     private val optimalImageHeight = getDisplayMaxHeightInPx * 2 | ||||
|  | ||||
|     /** | ||||
|      * Taken from Coil | ||||
|      * (https://github.com/coil-kt/coil/blob/1674d3516f061aeacbe749a435b1924f9648fd41/coil-core/src/androidMain/kotlin/coil3/util/hardwareBitmaps.kt) | ||||
|      * --- | ||||
|      * Maintains a list of devices with broken/incomplete/unstable hardware bitmap implementations. | ||||
|      * | ||||
|      * Model names are retrieved from | ||||
|      * [Google's official device list](https://support.google.com/googleplay/answer/1727131?hl=en). | ||||
|      * | ||||
|      */ | ||||
|     val HARDWARE_BITMAP_UNSUPPORTED = when (Build.VERSION.SDK_INT) { | ||||
|         26 -> run { | ||||
|             val model = Build.MODEL ?: return@run false | ||||
|  | ||||
|             // Samsung Galaxy (ALL) | ||||
|             if (model.removePrefix("SAMSUNG-").startsWith("SM-")) return@run true | ||||
|  | ||||
|             val device = Build.DEVICE ?: return@run false | ||||
|  | ||||
|             return@run device in arrayOf( | ||||
|                 "nora", "nora_8917", "nora_8917_n", // Moto E5 | ||||
|                 "james", "rjames_f", "rjames_go", "pettyl", // Moto E5 Play | ||||
|                 "hannah", "ahannah", "rhannah", // Moto E5 Plus | ||||
|  | ||||
|                 "ali", "ali_n", // Moto G6 | ||||
|                 "aljeter", "aljeter_n", "jeter", // Moto G6 Play | ||||
|                 "evert", "evert_n", "evert_nt", // Moto G6 Plus | ||||
|  | ||||
|                 "G3112", "G3116", "G3121", "G3123", "G3125", // Xperia XA1 | ||||
|                 "G3412", "G3416", "G3421", "G3423", "G3426", // Xperia XA1 Plus | ||||
|                 "G3212", "G3221", "G3223", "G3226", // Xperia XA1 Ultra | ||||
|  | ||||
|                 "BV6800Pro", // BlackView BV6800Pro | ||||
|                 "CatS41", // Cat S41 | ||||
|                 "Hi9Pro", // CHUWI Hi9 Pro | ||||
|                 "manning", // Lenovo K8 Note | ||||
|                 "N5702L", // NUU Mobile G3 | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         27 -> run { | ||||
|             val device = Build.DEVICE ?: return@run false | ||||
|  | ||||
|             return@run device in arrayOf( | ||||
|                 "mcv1s", // LG Tribute Empire | ||||
|                 "mcv3", // LG K11 | ||||
|                 "mcv5a", // LG Q7 | ||||
|                 "mcv7a", // LG Stylo 4 | ||||
|  | ||||
|                 "A30ATMO", // T-Mobile REVVL 2 | ||||
|                 "A70AXLTMO", // T-Mobile REVVL 2 PLUS | ||||
|  | ||||
|                 "A3A_8_4G_TMO", // Alcatel 9027W | ||||
|                 "Edison_CKT", // Alcatel ONYX | ||||
|                 "EDISON_TF", // Alcatel TCL XL2 | ||||
|                 "FERMI_TF", // Alcatel A501DL | ||||
|                 "U50A_ATT", // Alcatel TETRA | ||||
|                 "U50A_PLUS_ATT", // Alcatel 5059R | ||||
|                 "U50A_PLUS_TF", // Alcatel TCL LX | ||||
|                 "U50APLUSTMO", // Alcatel 5059Z | ||||
|                 "U5A_PLUS_4G", // Alcatel 1X | ||||
|  | ||||
|                 "RCT6513W87DK5e", // RCA Galileo Pro | ||||
|                 "RCT6873W42BMF9A", // RCA Voyager | ||||
|                 "RCT6A03W13", // RCA 10 Viking | ||||
|                 "RCT6B03W12", // RCA Atlas 10 Pro | ||||
|                 "RCT6B03W13", // RCA Atlas 10 Pro+ | ||||
|                 "RCT6T06E13", // RCA Artemis 10 | ||||
|  | ||||
|                 "A3_Pro", // Umidigi A3 Pro | ||||
|                 "One", // Umidigi One | ||||
|                 "One_Max", // Umidigi One Max | ||||
|                 "One_Pro", // Umidigi One Pro | ||||
|                 "Z2", // Umidigi Z2 | ||||
|                 "Z2_PRO", // Umidigi Z2 Pro | ||||
|  | ||||
|                 "Armor_3", // Ulefone Armor 3 | ||||
|                 "Armor_6", // Ulefone Armor 6 | ||||
|  | ||||
|                 "Blackview", // Blackview BV6000 | ||||
|                 "BV9500", // Blackview BV9500 | ||||
|                 "BV9500Pro", // Blackview BV9500Pro | ||||
|  | ||||
|                 "A6L-C", // Nuu A6L-C | ||||
|                 "N5002LA", // Nuu A7L | ||||
|                 "N5501LA", // Nuu A5L | ||||
|  | ||||
|                 "Power_2_Pro", // Leagoo Power 2 Pro | ||||
|                 "Power_5", // Leagoo Power 5 | ||||
|                 "Z9", // Leagoo Z9 | ||||
|  | ||||
|                 "V0310WW", // Blu VIVO VI+ | ||||
|                 "V0330WW", // Blu VIVO XI | ||||
|  | ||||
|                 "A3", // BenQ A3 | ||||
|                 "ASUS_X018_4", // Asus ZenFone Max Plus M1 (ZB570TL) | ||||
|                 "C210AE", // Wiko Life | ||||
|                 "fireball", // DROID Incredible 4G LTE | ||||
|                 "ILA_X1", // iLA X1 | ||||
|                 "Infinix-X605_sprout", // Infinix NOTE 5 Stylus | ||||
|                 "j7maxlte", // Samsung Galaxy J7 Max | ||||
|                 "KING_KONG_3", // Cubot King Kong 3 | ||||
|                 "M10500", // Packard Bell M10500 | ||||
|                 "S70", // Altice ALTICE S70 | ||||
|                 "S80Lite", // Doogee S80Lite | ||||
|                 "SGINO6", // SGiNO 6 | ||||
|                 "st18c10bnn", // Barnes and Noble BNTV650 | ||||
|                 "TECNO-CA8", // Tecno CAMON X Pro, | ||||
|                 "SHIFT6m", // SHIFT 6m | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         else -> false | ||||
|     } | ||||
| } | ||||
|  | ||||
| val getDisplayMaxHeightInPx: Int | ||||
|   | ||||
							
								
								
									
										1
									
								
								data/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								data/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /build | ||||
| @@ -23,12 +23,6 @@ android { | ||||
|     } | ||||
| } | ||||
|  | ||||
| kotlin { | ||||
|     compilerOptions { | ||||
|         freeCompilerArgs.add("-opt-in=kotlinx.serialization.ExperimentalSerializationApi") | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation(projects.sourceApi) | ||||
|     implementation(projects.domain) | ||||
| @@ -36,3 +30,12 @@ dependencies { | ||||
|  | ||||
|     api(libs.bundles.sqldelight) | ||||
| } | ||||
|  | ||||
| tasks { | ||||
|     withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { | ||||
|         compilerOptions.freeCompilerArgs.addAll( | ||||
|             "-Xcontext-receivers", | ||||
|             "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -49,10 +49,6 @@ 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,15 +78,6 @@ 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
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								domain/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /build | ||||
| @@ -13,12 +13,6 @@ android { | ||||
|     } | ||||
| } | ||||
|  | ||||
| kotlin { | ||||
|     compilerOptions { | ||||
|         freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation(projects.sourceApi) | ||||
|     implementation(projects.core.common) | ||||
| @@ -36,3 +30,12 @@ dependencies { | ||||
|     testImplementation(libs.bundles.test) | ||||
|     testImplementation(kotlinx.coroutines.test) | ||||
| } | ||||
|  | ||||
| tasks { | ||||
|     withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { | ||||
|         compilerOptions.freeCompilerArgs.addAll( | ||||
|             "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", | ||||
|             "-Xcontext-receivers", | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import mihon.domain.extensionrepo.exception.SaveExtensionRepoException | ||||
| import mihon.domain.extensionrepo.model.ExtensionRepo | ||||
| import mihon.domain.extensionrepo.repository.ExtensionRepoRepository | ||||
| import mihon.domain.extensionrepo.service.ExtensionRepoService | ||||
| import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||
| import tachiyomi.core.common.util.system.logcat | ||||
|  | ||||
| class CreateExtensionRepo( | ||||
| @@ -14,13 +13,12 @@ class CreateExtensionRepo( | ||||
| ) { | ||||
|     private val repoRegex = """^https://.*/index\.min\.json$""".toRegex() | ||||
|  | ||||
|     suspend fun await(indexUrl: String): Result { | ||||
|         val formattedIndexUrl = indexUrl.toHttpUrlOrNull() | ||||
|             ?.toString() | ||||
|             ?.takeIf { it.matches(repoRegex) } | ||||
|             ?: return Result.InvalidUrl | ||||
|     suspend fun await(repoUrl: String): Result { | ||||
|         if (!repoUrl.matches(repoRegex)) { | ||||
|             return Result.InvalidUrl | ||||
|         } | ||||
|  | ||||
|         val baseUrl = formattedIndexUrl.removeSuffix("/index.min.json") | ||||
|         val baseUrl = repoUrl.removeSuffix("/index.min.json") | ||||
|         return service.fetchRepoDetails(baseUrl)?.let { insert(it) } ?: Result.InvalidUrl | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package mihon.domain.extensionrepo.service | ||||
|  | ||||
| import androidx.core.net.toUri | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.NetworkHelper | ||||
| import eu.kanade.tachiyomi.network.awaitSuccess | ||||
| @@ -20,9 +21,11 @@ class ExtensionRepoService( | ||||
|         repo: String, | ||||
|     ): ExtensionRepo? { | ||||
|         return withIOContext { | ||||
|             val url = "$repo/repo.json".toUri() | ||||
|  | ||||
|             try { | ||||
|                 with(json) { | ||||
|                     client.newCall(GET("$repo/repo.json")) | ||||
|                     client.newCall(GET(url.toString())) | ||||
|                         .awaitSuccess() | ||||
|                         .parseAs<ExtensionRepoMetaDto>() | ||||
|                         .toExtensionRepo(baseUrl = repo) | ||||
|   | ||||
| @@ -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.randomSortSeed().set(Random.nextInt()) | ||||
|             preferences.currentRandomSortSeed().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(0b00111100) | ||||
|         data object Random : Type(0b00100100) | ||||
|  | ||||
|         companion object { | ||||
|             fun valueOf(flag: Long): Type { | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class LibraryPreferences( | ||||
|         LibrarySort.Serializer::deserialize, | ||||
|     ) | ||||
|  | ||||
|     fun randomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0) | ||||
|     fun currentRandomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0) | ||||
|  | ||||
|     fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0) | ||||
|  | ||||
|   | ||||
| @@ -17,8 +17,6 @@ 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 10 | ||||
|         LibrarySort.types.size shouldBe 9 | ||||
|         LibrarySort.directions.size shouldBe 2 | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,34 +1,34 @@ | ||||
| [versions] | ||||
| agp_version = "8.7.2" | ||||
| lifecycle_version = "2.8.7" | ||||
| paging_version = "3.3.4" | ||||
| agp_version = "8.7.0" | ||||
| lifecycle_version = "2.8.6" | ||||
| paging_version = "3.3.2" | ||||
| interpolator_version = "1.0.0" | ||||
|  | ||||
| [libraries] | ||||
| gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" } | ||||
|  | ||||
| annotation = "androidx.annotation:annotation:1.9.1" | ||||
| annotation = "androidx.annotation:annotation:1.8.2" | ||||
| appcompat = "androidx.appcompat:appcompat:1.7.0" | ||||
| biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05" | ||||
| constraintlayout = "androidx.constraintlayout:constraintlayout:2.2.0" | ||||
| corektx = "androidx.core:core-ktx:1.15.0" | ||||
| constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" | ||||
| corektx = "androidx.core:core-ktx:1.13.1" | ||||
| splashscreen = "androidx.core:core-splashscreen:1.0.1" | ||||
| recyclerview = "androidx.recyclerview:recyclerview:1.3.2" | ||||
| viewpager = "androidx.viewpager:viewpager:1.1.0-rc01" | ||||
| viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01" | ||||
| profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.1" | ||||
|  | ||||
| lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle_version" } | ||||
| lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" } | ||||
| lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" } | ||||
|  | ||||
| workmanager = "androidx.work:work-runtime:2.10.0" | ||||
| workmanager = "androidx.work:work-runtime:2.9.1" | ||||
|  | ||||
| paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" } | ||||
| paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" } | ||||
|  | ||||
| interpolator = { group = "androidx.interpolator", name = "interpolator", version.ref = "interpolator_version" } | ||||
|  | ||||
| benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.3.3" | ||||
| benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.3.2" | ||||
| 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.10.01" | ||||
| compose-bom = "2024.09.03" | ||||
|  | ||||
| [libraries] | ||||
| activity = "androidx.activity:activity-compose:1.9.3" | ||||
| activity = "androidx.activity:activity-compose:1.9.2" | ||||
| 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.1" | ||||
| glance = "androidx.glance:glance-appwidget:1.1.0" | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user