Compare commits

...
1952 Commits
Author SHA1 Message Date
FooIbar 0195f1c9b8 Prepare for release 1.14.6
Releases / build (push) Has been cancelled
2025-12-17 22:12:05 +08:00
renovate[bot] 18d2d992a4 Update dependency io.arrow-kt:arrow-stack to v2.2.1.1 (#2800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 13:44:04 +00:00
FooIbar 5582353c44 Enable group key based Compose stack traces 2025-12-17 15:08:04 +08:00
renovate[bot] 464568f29f Update dependency com.google.devtools.ksp to v2.3.4 (#2798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 20:09:01 +00:00
renovate[bot]andFooIbar 213a9d56d2 Update kotlin monorepo to v2.3.0 (#2797)
* Update kotlin monorepo to v2.3.0

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-12-16 11:15:39 +00:00
FooIbar f672462bcd Use WebView version for UA to bypass Cloudflare 2025-12-16 00:29:36 +08:00
renovate[bot] 9a0b0d2c65 Lock file maintenance (#2795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 02:04:38 +00:00
renovate[bot] fcaf4e3182 Update actions/upload-artifact action to v6 (#2792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 20:59:34 +00:00
renovate[bot] ea40fa9b05 Update dessant/lock-threads action to v6 (#2791)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 16:39:18 +00:00
renovate[bot] 2e3436dbe7 Update actions/cache action to v5 (#2790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 04:02:53 +00:00
renovate[bot] bd759dedea Update AGP to v8.13.2 (#2788)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 09:09:24 +00:00
FooIbar bac78ce018 Improve category text contrast 2025-12-05 21:41:29 +08:00
FooIbar dd95d7aaa2 Reapply "Use the upstream PaddingValues.plus"
This reverts commit e8b5fd0425.
2025-12-04 21:13:57 +08:00
renovate[bot] d1bb5b1d87 Cleanup snapshot workaround (#2782)
* Update settings.gradle.kts

* Update libs.versions.toml
2025-12-04 11:22:31 +08:00
renovate[bot] 67a3252b05 Update dependency androidx.compose:compose-bom-alpha to v2025.12.00 (#2781)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 21:02:24 +00:00
renovate[bot] 2df08f022f Update dependency androidx.test.uiautomator:uiautomator to v2.4.0-alpha07 (#2780)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 21:01:00 +00:00
renovate[bot] 65673a0ed9 Update dependency androidx.compose.foundation:foundation to v1.10.0 (#2779)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 18:24:57 +00:00
renovate[bot] aef20e2e04 Update dependency androidx.activity:activity-compose to v1.12.1 (#2778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 18:23:46 +00:00
FooIbar 3bbfc8ac3a Use different icon for finished download items 2025-12-03 11:05:21 +08:00
renovate[bot] 02e85a8c4d Update dependency chrome to v143 (#2776)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 21:57:07 +00:00
FooIbar 8cc531ccec Fix database export 2025-12-02 10:00:48 +08:00
FooIbar 30fc750e17 Show tag content on long click to avoid misclicks 2025-12-02 10:00:32 +08:00
AdiEcho 0ff40d3d38 Add show reading progress function (#2768)
* Add reading progress

* Optimize reading progress state collection and display logic

* Optimize reading progress state collection and add page display to downloads

* Enhance download progress display and settings

* Optimize state collection for read progress, revert arrangement

* Revert arrangement changement
2025-12-02 01:01:25 +08:00
renovate[bot] 9fa80db783 Update dependency libarchive to v3.8.4 (#2773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 15:12:35 +00:00
renovate[bot] 357503b403 Lock file maintenance (#2770)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 01:06:56 +00:00
FooIbar 10f7b2a7a9 KMP EhCookieStore 2025-11-29 15:08:32 +08:00
FooIbar 99696b11db Migrate to SQLite Driver APIs 2025-11-29 15:08:24 +08:00
FooIbar 71291b8800 Fix download state on archive failure 2025-11-27 20:48:52 +08:00
renovate[bot] 3532dbdcd6 Update dependency io.ktor:ktor-client-okhttp to v3.3.3 (#2765)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-26 15:32:05 +00:00
renovate[bot] 589ba8432c Update dependency org.jetbrains.kotlinx:kotlinx-io-core to v0.8.2 (#2764)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 21:09:00 +00:00
renovate[bot] 273196bf5d Update dependency Corrosion to v0.6 (#2763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-23 13:53:02 +00:00
renovate[bot] a5fadeee3c Update dependency org.jetbrains.kotlinx:kotlinx-io-core to v0.8.1 (#2762)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 22:49:16 +00:00
FooIbar 3f784ec1de Refine FFI error handling 2025-11-21 09:18:11 +08:00
renovate[bot] f7a70cdfc3 Update dependency androidx.datastore:datastore-preferences to v1.2.0 (#2760)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 01:02:00 +00:00
renovate[bot] 030100280f Update dependency com.materialkolor:material-kolor to v4.0.5 (#2759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 22:38:28 +00:00
renovate[bot] 0d76ee43fd Update dependency com.google.devtools.ksp to v2.3.3 (#2758)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 22:38:07 +00:00
renovate[bot] 11a215f070 Update actions/checkout action to v6 (#2757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 17:12:09 +00:00
FooIbar a6e61e07fc Update Compose snaoshot (#2756) 2025-11-21 00:00:29 +08:00
renovate[bot] da94d298ce Update dependency androidx.activity:activity-compose to v1.12.0 (#2753)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 02:05:04 +00:00
renovate[bot] 9e74db0f02 Update dependency androidx.compose:compose-bom-alpha to v2025.11.01 (#2754)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 02:04:47 +00:00
renovate[bot] 0d63c2fa3c Update androidx.room to v2.8.4 (#2752)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 21:01:03 +00:00
renovate[bot] bf44fde58e Update androidx.lifecycle to v2.10.0 (#2751)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 21:00:42 +00:00
renovate[bot] 4bc119138f Update moko.resources to v0.25.2 (#2750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 17:57:46 +00:00
renovate[bot] c7f5b86843 Update spotless to v8.1.0 (#2749)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 01:37:36 +00:00
renovate[bot] 2aa815dce1 Update dependency com.squareup.okhttp3:okhttp-bom to v5.3.2 (#2748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 01:36:52 +00:00
FooIbar cf7f18351d Simplify build script for Nettle 2025-11-18 23:45:22 +08:00
FooIbar 5ed27963d7 Block HyperOS 1 & 2 for Cronet 2025-11-18 23:45:11 +08:00
renovate[bot] 85e539f0ba Update Gradle to v9.2.1 (#2745)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 08:17:05 +00:00
renovate[bot] 15ecb0e3f4 Update dependency libarchive to v3.8.3 (#2744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 23:56:04 +00:00
renovate[bot] db5055da6e Update dependency com.squareup.okio:okio to v3.16.4 (#2743)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 23:55:24 +00:00
FooIbar 09e201f3f2 Fix CFLAGS changes not propagating to nettle build 2025-11-17 14:55:07 +08:00
renovate[bot] 6f34083880 Update dependency com.squareup.okhttp3:okhttp-bom to v5.3.1 (#2740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-16 18:10:47 +00:00
FooIbar 4e0c42c955 Use -ffile-prefix-map to improve reproducibility 2025-11-16 21:49:17 +08:00
FooIbar 935b7dec0c Reduce binary size and improve reproducibility 2025-11-16 15:34:49 +08:00
FooIbar d0b0651c7d Revert "Enable trim-paths to improve reproducibility"
This reverts commit e131a416e8.
2025-11-16 15:34:49 +08:00
FooIbar e131a416e8 Enable trim-paths to improve reproducibility 2025-11-15 23:47:01 +08:00
FooIbar dd526d33bd Update download URL for Nettle
The GitLab mirror has been archived.
2025-11-15 18:15:37 +08:00
renovate[bot]andFooIbar 4611a77e04 Update dependency com.pinterest.ktlint:ktlint-cli to v1.8.0 (#2729)
* Update dependency com.pinterest.ktlint:ktlint-cli to v1.8.0

* Fix spotless

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-11-15 08:13:23 +00:00
renovate[bot] 806560cf59 Update dependency rust-nightly to v2025-11-15 (#2733)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 07:01:25 +00:00
renovate[bot] fd3f6eb124 Lock file maintenance (#2732)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 01:03:09 +00:00
renovate[bot] 8e14c6e57b Update dependency com.squareup.okio:okio to v3.16.3 (#2731)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 01:02:04 +00:00
FooIbar 20a4f4ab25 Remove trailing spaces and periods from filenames 2025-11-12 23:14:58 +08:00
renovate[bot] d4c64f6e66 Update dependency com.materialkolor:material-kolor to v4.0.4 (#2726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 01:49:24 +00:00
renovate[bot] cfb2f15a1e Update AGP to v8.13.1 (#2724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 18:53:27 +00:00
renovate[bot] 927a738f46 Update dependency com.google.devtools.ksp to v2.3.2 (#2722)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 23:07:08 +00:00
renovate[bot] 19482dbd77 Update dependency org.jetbrains.compose to v1.9.3 (#2721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 18:20:14 +00:00
FooIbar 43b7fb611a Migrate CMP dependencies to version catalog
Gradle plugin dependency aliases are deprecated.
2025-11-06 18:20:11 +08:00
renovate[bot] c39338efb7 Update dependency androidx.compose:compose-bom-alpha to v2025.11.00 (#2719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 02:23:30 +00:00
renovate[bot] dbd952eebb Update dependency androidx.core:core-splashscreen to v1.2.0 (#2718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 02:23:04 +00:00
renovate[bot] 192039408c Update androidx.lifecycle to v2.10.0-rc01 (#2716)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 21:24:26 +00:00
renovate[bot] e38376d4b0 Update dependency androidx.activity:activity-compose to v1.12.0-rc01 (#2717)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 21:23:25 +00:00
renovate[bot] a4062cf4d1 Update dependency io.ktor:ktor-client-okhttp to v3.3.2 (#2715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 10:15:16 +00:00
FooIbar 3c47d36525 Use snapshot Compose for the popup positioning fix 2025-11-05 18:14:37 +08:00
renovate[bot] 00090944d5 Update dependency com.google.devtools.ksp to v2.3.1 (#2712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 06:50:00 +00:00
renovate[bot] d2cafbb2a3 Update dependency io.arrow-kt:arrow-stack to v2.2.0 (#2710)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 16:57:32 +00:00
renovate[bot] 9847b95582 Lock file maintenance (#2709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 01:54:57 +00:00
renovate[bot] 986412932b Update dependency com.squareup.okhttp3:okhttp-bom to v5.3.0 (#2707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 00:21:38 +00:00
renovate[bot] f13831cb74 Update dependency com.materialkolor:material-kolor to v4.0.3 (#2706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 20:16:27 +00:00
renovate[bot] 5ed1203558 Update Gradle to v9.2.0 (#2705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 00:58:23 +00:00
renovate[bot] acf1fe43bf Update dependency org.jetbrains.compose.material3.adaptive:adaptive to v1.2.0 (#2704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 00:53:24 +00:00
renovate[bot] 4761ef7416 Update dependency org.jetbrains.compose to v1.9.2 (#2703)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 14:05:04 +00:00
renovate[bot] 70aaf38f94 Update xmlutil to v0.91.3 (#2701)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 22:53:18 +00:00
renovate[bot] a92aaf19c9 Update dependency chrome to v142 (#2702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 22:52:56 +00:00
renovate[bot] a5b408d316 Update dependency io.github.petertrr:kotlin-multiplatform-diff to v1.2.0 (#2699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 18:52:48 +00:00
renovate[bot] f96a2e63d1 Update actions/upload-artifact action to v5 (#2698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-24 23:17:26 +00:00
renovate[bot] a0a9da68da Update dependency com.materialkolor:material-kolor to v4.0.2 (#2697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 23:12:52 +00:00
FooIbar a83be94d1f Fix dropdown menu anchor calculation in dialog 2025-10-23 22:31:47 +08:00
FooIbar cc5dfbfa41 Update screenshots 2025-10-23 19:36:27 +08:00
renovate[bot] 57b79b0b06 Update kotlin monorepo to v2.2.21 (#2694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 09:49:38 +00:00
renovate[bot] 569130087f Update dependency androidx.work:work-runtime to v2.11.0 (#2693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 03:36:38 +00:00
renovate[bot] 1a7b36b488 Update dependency androidx.compose:compose-bom-alpha to v2025.10.01 (#2691)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 03:36:18 +00:00
renovate[bot] e8b5fd0425 Revert "Use the upstream PaddingValues.plus"
This reverts commit 656d372612.
2025-10-23 11:25:39 +08:00
renovate[bot] 626d5e7a8c Update dependency androidx.activity:activity-compose to v1.12.0-beta01 (#2690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 22:38:35 +00:00
renovate[bot] 4d94e84c13 Update androidx.room to v2.8.3 (#2689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 19:08:50 +00:00
renovate[bot] 863ec659ff Update androidx.lifecycle to v2.10.0-beta01 (#2688)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 19:07:45 +00:00
renovate[bot] 071fdfc0a3 Update dependency com.google.devtools.ksp to v2.3.0 (#2685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 10:18:07 +00:00
FooIbar a137e9715a Refactor search bar text handling
* Fix search bar text not cleared after searching in popular / toplist
* Fix search bar text not cleared when quick search keyword is empty
2025-10-22 18:05:02 +08:00
renovate[bot] c28d1d34ff Update dependency com.materialkolor:material-kolor to v4 (#2683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 20:49:43 +00:00
FooIbar 1230022d17 Reset FAB state when gallery list changes 2025-10-20 21:11:47 +08:00
FooIbar 9a9f2a8d70 Prepare for next iteration 2025-10-19 21:19:02 +08:00
FooIbar f51da1a948 Prepare for release 1.14.5
Releases / build (push) Has been cancelled
2025-10-19 21:18:11 +08:00
FooIbar c1ac592855 [Reader] Show gallery title & fix status bar color 2025-10-19 12:41:17 +08:00
FooIbar 371103908b Fix flickering loops in reader
Opt out activity recreation for size-based config changes as they are
handled by Compose.
2025-10-16 19:19:40 +08:00
FooIbar a2a029bb28 Fix unnecessary recompositions
`BaseGalleryInfo` is used as part of remember keys.
2025-10-16 19:19:40 +08:00
renovate[bot] 9266fba415 Update dependency libarchive to v3.8.2 (#2678)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-16 00:58:36 +00:00
FooIbar 0963e2f564 Fix reader settings crash with custom orientation 2025-10-15 18:11:29 +08:00
renovate[bot] 72a5906a9b Lock file maintenance (#2676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 02:13:59 +00:00
renovate[bot] 4dbd54c495 Update dependency com.squareup.okio:okio to v3.16.2 (#2675)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 20:06:47 +00:00
renovate[bot] 6219acb15c Update dependency org.jetbrains.compose to v1.9.1 (#2672)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 13:53:25 +00:00
renovate[bot] c4179bef5d Update aboutlibraries to v13.1.0 (#2670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 20:52:02 +00:00
renovate[bot] 117a1eaab8 Fix -Ofast deprecation 2025-10-13 17:31:15 +08:00
FooIbar 78e31e4fb9 Remove indent from compiler flags 2025-10-13 00:14:07 +08:00
FooIbar 3da86fd745 Prepare for next iteration 2025-10-12 21:32:38 +08:00
FooIbar e0c710ff5f Prepare for release 1.14.4
Releases / build (push) Has been cancelled
2025-10-12 21:32:02 +08:00
FooIbar 2946459f1d Prepare changelog for 1.14.4 (#2665)
[no ci]
2025-10-12 21:30:55 +08:00
renovate[bot] 5b5f328e06 Lock file maintenance (#2667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-12 13:22:31 +00:00
FooIbar b496e18e7a Revert "Use to the new panic-immediate-abort cargo feature"
This reverts commit cf10e2ff6d.

Reason for revert: Incompatible with renovate
2025-10-12 21:08:34 +08:00
renovate[bot] 4abb2ab0ce Update compose.destinations to v2.3.0 (#2664)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-12 09:40:33 +00:00
FooIbar 16edaacf37 Temporarily disable backupBeforeUpdate by default 2025-10-12 12:59:35 +08:00
FooIbar b69aea9d14 Update AndroidX WorkManager to 2.11.0-rc01 2025-10-12 12:07:02 +08:00
FooIbar 656d372612 Use the upstream PaddingValues.plus 2025-10-12 12:04:30 +08:00
FooIbar 09a2ebcc61 Workaround crash when 0-sized TextField is focused
It only affects API < 28
https://developer.android.com/about/versions/pie/android-9.0-changes-28#focus
2025-10-11 10:21:44 +08:00
renovate[bot]andFooIbar b74d0e9006 Update dependency com.mikepenz.aboutlibraries.plugin to v13 (#2658)
* Update dependency com.mikepenz.aboutlibraries.plugin to v13

* Fix build

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-10-10 14:10:30 +00:00
renovate[bot] a7c81130cd Update dependency com.squareup.okhttp3:okhttp-bom to v5.2.1 (#2657)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 04:46:04 +00:00
renovate[bot] c0b75cdbe9 Update dependency com.squareup.okio:okio to v3.16.1 (#2656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 00:49:53 +00:00
FooIbar 2c15c74697 Fix visibility calculation with zoomable content 2025-10-09 21:52:39 +08:00
FooIbar ba9308bfbc Underline solid tags when opted in 2025-10-09 21:28:39 +08:00
renovate[bot] 21050b49d4 Update dependency androidx.compose:compose-bom-alpha to v2025.10.00 (#2651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 21:46:22 +00:00
renovate[bot] 842da49b08 Update androidx.lifecycle to v2.10.0-alpha05 (#2649)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 19:28:37 +00:00
renovate[bot] 44fb2b70a5 Update androidx.room to v2.8.2 (#2650)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 19:28:12 +00:00
renovate[bot] 9ef95f56cf Update dependency io.ktor:ktor-client-okhttp to v3.3.1 (#2648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 10:56:43 +00:00
renovate[bot] b5b08404bd Update dependency NDK to v29 (#2647)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 05:04:11 +00:00
renovate[bot] 17abca7f7d Update dependency com.google.devtools.ksp to v2.2.20-2.0.4 (#2646)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 05:02:34 +00:00
renovate[bot] c8d4782b2a Update dependency com.squareup.okhttp3:okhttp-bom to v5.2.0 (#2645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 17:53:41 +00:00
FooIbar e0af610c2c Clean up rust build-std flags 2025-10-06 22:36:17 +08:00
FooIbar ddc645c323 Speed up sorting download list by page count 2025-10-06 09:42:32 +08:00
FooIbar 779dd1f8fd Reduce DownloadManager initialization time 2025-10-06 09:42:32 +08:00
FooIbar cf10e2ff6d Use to the new panic-immediate-abort cargo feature 2025-10-05 19:38:52 +08:00
renovate[bot] 55d1094e55 Update dependency me.saket.telephoto:zoomable to v0.18.0 (#2642)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-04 20:57:52 +00:00
FooIbar 8248ac6e2f Update rust-nightly to 2025-10-03
This reverts commit 085e0938cb.
2025-10-03 12:09:57 +08:00
renovate[bot] 4b2ac8ae5e Update moko.resources to v0.25.1 (#2639)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-02 17:18:38 +00:00
FooIbar 085e0938cb Downgrade rust-nightly to 2025-09-23
2025-09-24 and later have a +25% (141KB) binary size regression

This reverts commit 9a2c0d7121.
2025-10-02 15:40:17 +08:00
renovate[bot]andFooIbar 9a2c0d7121 Update dependency rust-nightly to v2025-10-01 (#2635)
* Update dependency rust-nightly to v2025-10-01

* Fix build

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-10-01 16:11:15 +00:00
renovate[bot] aa8834b941 Update gradle/actions action to v5 (#2636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 13:24:49 +00:00
FooIbar d6b524bc47 Use RTL mode by default for non-webtoon galleries 2025-10-01 21:08:28 +08:00
FooIbar 1ca960995f Use webtoon mode by default for webtoon galleries 2025-10-01 21:08:28 +08:00
renovate[bot] 31be34f551 Update dependency chrome to v141 (#2633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 21:59:26 +00:00
FooIbar e0d1c42391 Extract common model and database code 2025-09-30 12:04:50 +08:00
FooIbar 1b9c3050aa Fix race condition in concurrent append/prepend 2025-09-28 13:31:21 +08:00
FooIbar 5ba0afdd24 Switch tl repository to avoid renovate updating it 2025-09-26 20:16:28 +08:00
FooIbar a8eb4ce47a Extract common utils (#2620)
* Extract common utils

* Fix lint
2025-09-25 17:49:06 +08:00
renovate[bot] feba6dfeb0 Update spotless to v8 (#2626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-25 01:27:59 +00:00
renovate[bot] 061f4a0ed0 Update dependency androidx.work:work-runtime to v2.10.5 (#2625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-25 01:25:06 +00:00
renovate[bot] f7c273a4f3 Update dependency androidx.activity:activity-compose to v1.12.0-alpha09 (#2623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-24 20:56:04 +00:00
renovate[bot] 096dae3cd0 Update dependency androidx.compose:compose-bom-alpha to v2025.09.01 (#2624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-24 20:55:24 +00:00
renovate[bot] eebf51f4a6 Update androidx.lifecycle to v2.10.0-alpha04 (#2621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-24 18:50:38 +00:00
renovate[bot] 000ae02d47 Update androidx.room to v2.8.1 (#2622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-24 18:50:21 +00:00
FooIbar 84c1ee3e88 Clean up CoroutineScope extensions 2025-09-24 13:15:30 +08:00
FooIbar 0fea2076d9 Fix a main thread disk read 2025-09-24 13:15:30 +08:00
FooIbar b7a90b171f Omit default parameters of getGalleryDetailUrl 2025-09-24 13:15:30 +08:00
FooIbar 7a68f331b0 Refactor haptic feedback type selection 2025-09-24 13:15:30 +08:00
renovate[bot] 9e7abd5a85 Update Gradle to v9.1.0 (#2618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 14:43:54 +00:00
FooIbar 9bc3f1137e Fix ghost touch areas for links in comments 2025-09-17 22:28:32 +08:00
FooIbar 0c7e3ac8b4 Fix gallery availability check 2025-09-17 17:16:09 +08:00
renovate[bot] 715f863167 Update dependency org.jetbrains.compose to v1.9.0 (#2613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 15:44:37 +00:00
e062301d42 Disallow overzoom out (#2156)
Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-09-15 15:22:02 +08:00
renovate[bot]andFooIbar 77396558d7 Update dependency me.saket.telephoto:zoomable to v0.17.0 (#2611)
* Update dependency me.saket.telephoto:zoomable to v0.17.0

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-09-15 05:40:17 +00:00
renovate[bot]andFooIbar d11c16c549 [no ci] Migrate renovate config (#2612)
* Migrate config renovate.json

* Keep comment

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-09-15 13:10:22 +08:00
FooIbar 6a90ac40be [no ci] Update lock file biweekly 2025-09-15 11:53:08 +08:00
FooIbar 25cc1d77b1 Stop downloads and notify the user on fatal errors 2025-09-15 11:31:30 +08:00
renovate[bot] fd489d93bb Lock file maintenance (#2610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 02:15:20 +00:00
FooIbar e80487e379 Work around filename length limit on some devices 2025-09-14 11:30:58 +08:00
renovate[bot] 3b4c2981ac Update dependency com.google.devtools.ksp to v2.2.20-2.0.3 (#2606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 03:24:24 +00:00
FooIbar 348d45f2f5 Treat search warnings as errors 2025-09-12 21:16:16 +08:00
renovate[bot] 4db96c7bdb Update dependency io.ktor:ktor-client-okhttp to v3.3.0 (#2603)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 13:03:06 +00:00
renovate[bot]andFooIbar b1617c6996 Update dependency androidx.compose:compose-bom-alpha to v2025.09.00 (#2602)
* Update dependency androidx.compose:compose-bom-alpha to v2025.09.00

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-09-11 05:59:34 +00:00
renovate[bot] c078166e8d Update androidx.room to v2.8.0 (#2601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 00:32:19 +00:00
renovate[bot] 092936eda5 Update dependency androidx.work:work-runtime to v2.10.4 (#2599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 21:51:50 +00:00
renovate[bot] 257a999300 Update dependency com.google.devtools.ksp to v2.2.20-2.0.2 (#2600)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 21:51:08 +00:00
renovate[bot] df6a5040a5 Update dependency androidx.activity:activity-compose to v1.12.0-alpha08 (#2598)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 17:48:40 +00:00
renovate[bot] 3085ca2eeb Update benchmark to v1.4.1 (#2597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 17:48:17 +00:00
renovate[bot] fa4005cf9a Update kotlin monorepo to v2.2.20 (#2596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 10:18:21 +00:00
FooIbar c4cd375c04 Allow disabling page turning with volume keys 2025-09-09 22:04:41 +08:00
FooIbar 19d9bb198f Work around ANR on DownloadManager initialization 2025-09-05 00:29:28 +08:00
LuK1337 7016190ce5 Use LocalContentColor.current for tag text
Fixes text visibility on Android 16 QPR1.
2025-09-04 22:02:21 +08:00
renovate[bot] 8020ffa666 Update dependency com.google.devtools.ksp to v2.2.20-RC2-2.0.2 (#2590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 04:25:38 +00:00
FooIbar bc9914ffa6 Add missing changelog entry
[no ci]
2025-09-03 11:54:41 +08:00
renovate[bot] 4ded4ff470 Update dependency chrome to v140 (#2589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 21:30:42 +00:00
renovate[bot] 7742299651 Update AGP to v8.13.0 (#2587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 17:42:45 +00:00
FooIbar 0b348a59d2 Prepare for next iteration 2025-09-01 20:42:28 +08:00
FooIbar e51d54e346 Prepare changelog for 1.14.3
Releases / build (push) Has been cancelled
2025-09-01 20:42:02 +08:00
renovate[bot] e06b1f0177 Lock file maintenance (#2586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 12:25:48 +00:00
renovate[bot] 178a870608 Update kotlin monorepo to v2.2.20-RC2 (#2585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 12:23:11 +00:00
FooIbar a7d3d7c651 Prepare changelog for 1.14.3 2025-09-01 20:13:28 +08:00
renovate[bot] c13024f1c7 Update AGP to v8.12.2 (#2583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-30 16:14:02 +00:00
FooIbar c53c0ef727 Fix similar galleries by artist tag 2025-08-31 00:02:06 +08:00
FooIbar cd93fe2646 Revert "Work around crash when dismissing filter settings"
This reverts commit afe638fb90.
2025-08-28 11:12:21 +08:00
renovate[bot] 7b506058a4 Update dependency androidx.compose:compose-bom-alpha to v2025.08.01 (#2580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 00:52:26 +00:00
renovate[bot] f658f74f35 Update androidx.lifecycle to v2.10.0-alpha03 (#2578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 20:41:58 +00:00
renovate[bot] eae5efa69d Update dependency androidx.activity:activity-compose to v1.12.0-alpha07 (#2579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 20:41:22 +00:00
FooIbar 4e6051085e Simplify repository content filtering 2025-08-26 20:01:02 +08:00
FooIbar f174ccfc04 Migrate to the new context menu APIs 2025-08-26 10:02:21 +08:00
FooIbar e1252d0554 Support searching download labels with label: 2025-08-25 21:58:18 +08:00
FooIbar afe638fb90 Work around crash when dismissing filter settings 2025-08-25 18:12:39 +08:00
renovate[bot] 17699ce889 Lock file maintenance (#2572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 01:58:40 +00:00
renovate[bot] 4afa06b7b9 Update actions/setup-java action to v5 (#2568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-21 06:39:56 +00:00
FooIbar 2d02815aba Extract common UI components part 1/n 2025-08-20 14:45:18 +08:00
renovate[bot] ae1824c6a2 Update dependency com.google.devtools.ksp to v2.2.20-RC-2.0.2 (#2567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-20 03:40:50 +00:00
renovate[bot] d63cc39478 Update kotlin monorepo to v2.2.20-RC (#2565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 08:38:25 +00:00
FooIbar 51d6775429 Remove unnecessary JNI lib and main thread check
The actual disk I/O will perform in the scope of DataStore.
2025-08-19 12:07:34 +08:00
renovate[bot] 35d92f1386 Update dependency sh.calvin.reorderable:reorderable to v3 (#2562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 22:27:32 +00:00
renovate[bot] ba0ef6dccc Update AGP to v8.12.1 (#2561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 17:34:26 +00:00
renovate[bot] 2a325e6700 Lock file maintenance (#2559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 01:05:47 +00:00
FooIbar 4e7566f06d Fix aboutlibraries.json being out-of-date 2025-08-17 17:06:35 +08:00
FooIbar d95bf4b71d Prepare for next iteration 2025-08-16 22:24:23 +08:00
FooIbar 43ddf6ccc4 Prepare for release 1.14.2
Releases / build (push) Has been cancelled
2025-08-16 22:23:45 +08:00
FooIbar b55da640e1 Prepare changelog for 1.14.2 2025-08-16 22:19:58 +08:00
FooIbar 90534c3c9f Remove confusing connect timeout configuration 2025-08-16 22:19:58 +08:00
FooIbar 88c9e5e902 Support new tag namespace: "location:"
Treat it as "other:".
2025-08-16 17:21:53 +08:00
FooIbar ac41117a78 [no ci] Tweak Rust toolchain update schedule 2025-08-15 17:51:31 +08:00
renovate[bot] 2418c5f625 Update dependency rust-nightly to v2025-08-15 (#2554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 08:33:52 +00:00
FooIbar 52156aa9ff [no ci] Let renovate update NDK 2025-08-15 16:23:17 +08:00
renovate[bot]andFooIbar 6010fb69a4 Update dependency rust-nightly to v2025-08-14 (#2553)
* Update dependency rust-nightly to v2025-08-14

* Fix build

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-08-15 04:59:20 +00:00
FooIbar d5abbb3584 Stop speed tracker immediately if download is done
Subsequent disk I/O will make the reported download speed inaccurate.
2025-08-14 19:25:03 +08:00
FooIbar 8c27504ee2 Update Rust nightly toolchain every ~6 weeks
Follow the stable release cycle.

[no ci]
2025-08-14 15:48:47 +08:00
FooIbar 8a20525da3 Remove obsolete workaround 2025-08-14 11:14:33 +08:00
renovate[bot] c7b8326b52 Update dependency androidx.core:core-ktx to v1.17.0 (#2550)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-14 00:53:52 +00:00
renovate[bot] ee8b80651e Update dependency androidx.compose:compose-bom-alpha to v2025.08.00 (#2549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-14 00:53:37 +00:00
renovate[bot] 589676c52a Update dependency androidx.test.uiautomator:uiautomator to v2.4.0-alpha06 (#2548)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-13 22:54:04 +00:00
FooIbar 83085d5f9a Allow disabling download speed check 2025-08-13 20:37:43 +08:00
FooIbar a1a204d0d5 Refactor settings observation 2025-08-13 00:42:20 +08:00
FooIbar 9a43c36758 KMP DataStorePreferences 2025-08-12 10:40:45 +08:00
renovate[bot] e017efe35f Update actions/checkout action to v5 (#2542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 13:57:50 +00:00
renovate[bot] 1645c17ab7 Lock file maintenance (#2541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 02:33:12 +00:00
FooIbar 075b800932 Exclude CMP resources dependency 2025-08-08 17:19:50 +08:00
FooIbar 56f9138db4 Misc updates 2025-08-08 17:19:50 +08:00
FooIbar 20c1b7acdb Migrate to moko-resources 2025-08-07 10:40:21 +08:00
FooIbar a10022d797 Move plural string resources to their own files 2025-08-07 10:40:21 +08:00
renovate[bot] aafbbbec38 Update dependency chrome to v139 (#2538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 21:43:41 +00:00
FooIbar 028a994070 Hack for type-safe accessors in convention plugins 2025-08-05 22:58:52 +08:00
renovate[bot] 581125db62 Lock file maintenance (#2535)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 03:00:33 +00:00
FooIbar 295770002a Extract common build logic to convention plugins 2025-08-03 10:39:03 +08:00
renovate[bot] 1a66d84bd0 Update dependency gradle to v9 (#2532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 01:45:06 +00:00
renovate[bot] cd3f51b66d Update AGP to v8.12.0 (#2531)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 18:04:05 +00:00
renovate[bot] 6d8ac4c3da Update dependency com.google.devtools.ksp to v2.2.20-Beta2-2.0.2 (#2530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 18:01:43 +00:00
FooIbar 156382eaad Prepare for next iteration 2025-07-31 18:35:19 +08:00
FooIbar 2c155fe599 Prepare for release 1.14.1
Releases / build (push) Has been cancelled
2025-07-31 18:34:50 +08:00
renovate[bot] 323de69ba0 Update dependency androidx.test.ext:junit to v1.3.0 (#2527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 01:52:48 +00:00
renovate[bot] 0cdc6be34f Update dependency androidx.browser:browser to v1.9.0 (#2526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 01:52:18 +00:00
FooIbar 36c1703763 Renew GitHub token and prepare for a point release 2025-07-31 09:43:10 +08:00
renovate[bot] ee3d2f681c Update dependency androidx.work:work-runtime to v2.10.3 (#2525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 23:05:17 +00:00
renovate[bot] 11ce900868 Update dependency androidx.compose:compose-bom-alpha to v2025.07.01 (#2523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 19:59:25 +00:00
renovate[bot] b936dd2533 Update benchmark to v1.4.0 (#2522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 19:58:11 +00:00
renovate[bot] fab8cd0c1f Update dependency com.squareup.okio:okio-bom to v3.16.0 (#2520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 00:35:57 +00:00
renovate[bot] ec3a962ec8 Update dependency com.squareup.logcat:logcat to v0.4 (#2519)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 13:56:37 +00:00
FooIbar 348813109b Clean up context parameters 2025-07-29 21:46:57 +08:00
renovate[bot] 59598094df Update kotlin monorepo to v2.2.20-Beta2 (#2517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 10:18:22 +00:00
renovate[bot] 8b6ae59eda Update dependency io.ktor:ktor-client-okhttp to v3.2.3 (#2516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 10:17:55 +00:00
FooIbar b3d8653882 Speed up dependency downloading 2025-07-29 18:17:42 +08:00
renovate[bot] 719d1ebbb8 Update xmlutil to v0.91.2 (#2514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 00:21:18 +00:00
FooIbar a23f902e9a Revert "[no ci] Allow blank issue"
This reverts commit 24e36b6283.
2025-07-28 20:25:31 +08:00
renovate[bot] 79559635e1 Lock file maintenance (#2512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 03:06:58 +00:00
FooIbar c57a4e4132 Kotlin 2.2.20-Beta1 and remove custom impl 2025-07-26 23:55:25 +08:00
renovate[bot] c6f7d9d869 Update dependency com.squareup.logcat:logcat to v0.3 (#2509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-25 08:47:54 +00:00
renovate[bot] 9fcddd06e4 Update dependency com.materialkolor:material-kolor to v3.0.1 (#2508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 20:54:47 +00:00
FooIbar 3526a2b0e9 [no ci] Add comment about KSP version check 2025-07-24 22:58:06 +08:00
FooIbar 56187aa4dc Prepare for next iteration
(cherry picked from commit 2d02eda9e3)
2025-07-23 23:25:15 +08:00
FooIbar 9e698f382d Release 1.14.0 2025-07-23 22:39:34 +08:00
FooIbar d2583a6d96 Add a workflow to test baseline profile generation 2025-07-23 22:37:44 +08:00
FooIbar 250e4fddd9 Refine build scripts 2025-07-23 22:37:44 +08:00
FooIbar 2f274d969e Fix release 2025-07-23 22:37:44 +08:00
FooIbar 2d02eda9e3 Prepare for next iteration 2025-07-23 17:40:43 +08:00
FooIbar f26a9518e0 Prepare for release 1.14.0 2025-07-23 17:40:02 +08:00
FooIbar 2b92bcf0a9 Prepare changelog for 1.14.0 2025-07-23 17:27:59 +08:00
renovate[bot] 0ce6296318 Update dependency io.coil-kt.coil3:coil-bom to v3.3.0 (#2504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 08:39:02 +00:00
renovate[bot]andFooIbar 2c90ceb5ca Update dependency com.pinterest.ktlint:ktlint-cli to v1.7.1 (#2503)
* Update dependency com.pinterest.ktlint:ktlint-cli to v1.7.1

* Fix spotless

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-07-22 06:23:35 +00:00
renovate[bot] 2fde327195 Update plugin spotless to v7.2.1 (#2502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 01:57:37 +00:00
renovate[bot] 3aee440f67 Lock file maintenance (#2501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 03:07:36 +00:00
renovate[bot] 290c1777b3 Update plugin spotless to v7.2.0 (#2500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-20 21:20:25 +00:00
FooIbar e0c8b64c65 Release URI permission when changing download location 2025-07-21 00:12:14 +08:00
FooIbar 810d339b4e Fix downloading notification stuck when nothing to download 2025-07-21 00:12:14 +08:00
FooIbar 53ce1e6934 Fix crash when clearing notification while app is in background 2025-07-21 00:12:14 +08:00
FooIbar ae4d3b2d65 Use fixed width for archive download button 2025-07-19 21:48:35 +08:00
renovate[bot] 1cdd51de21 Update dependency me.zhanghai.compose.preference:preference to v2.1.0 (#2497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-19 13:22:40 +00:00
FooIbar 9cf4702f69 Migrate to Compose Preference (#2496) 2025-07-19 21:13:29 +08:00
FooIbar cb5455c990 Fix corrupted tag database file not updating 2025-07-17 19:21:05 +08:00
FooIbar db86fef717 Fix history entry not fully dismissed on swipe 2025-07-17 19:21:05 +08:00
FooIbar 8bffeb52a7 Remove explicit dependency on graphics-path 2025-07-17 09:45:10 +08:00
FooIbar 9d2bbd164b Migrate common buttons to M3 Expressive 2025-07-17 09:13:59 +08:00
FooIbar a045766c7a Migrate icon buttons to M3 Expressive 2025-07-17 09:13:59 +08:00
FooIbar 34cff8c8cb Extract NavigationIcon 2025-07-17 09:03:55 +08:00
FooIbar 03ec2af92e Remove redundant PreferenceHeader overload 2025-07-17 09:03:55 +08:00
FooIbar c2a8e27875 Clean up SnackbarHostState usage 2025-07-17 09:03:55 +08:00
FooIbar 3fae5129d8 Revert "Update dependency androidx.compose.material3:material3-android to v1.…"
This reverts commit 17e39f94cc.
2025-07-17 09:03:35 +08:00
renovate[bot] 8311f7baaa Update dependency androidx.compose:compose-bom-alpha to v2025.07.00 (#2491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 23:02:36 +00:00
renovate[bot] 17e39f94cc Update dependency androidx.compose.material3:material3-android to v1.4.0-alpha18 (#2490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 17:54:31 +00:00
renovate[bot] cd1e10c17c Update androidx.lifecycle to v2.9.2 (#2489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 17:54:12 +00:00
renovate[bot] 5defb428ff Update dependency libwebp to v1.6.0 (#2486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 11:50:43 +00:00
FooIbar 79a066e68a Use GitHub repository for renovate to update 2025-07-16 19:42:09 +08:00
FooIbar 3e34bdf7fc Draw scrim behind status bar to improve a11y
Changes toolbar color to the default as surfaceContainerHighest doesn't
look well in light theme.
2025-07-16 10:11:59 +08:00
FooIbar bdf31231f0 Update NDK to r28c 2025-07-15 20:59:32 +08:00
FooIbar 17de611a22 Actually fix the UAF
The archive may be closed before the decoding job finishes.

This reverts commit 4b25fb8363.
2025-07-15 10:09:08 +08:00
renovate[bot] 4744191242 Update dependency io.ktor:ktor-client-okhttp to v3.2.2 (#2480)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 13:15:32 +00:00
renovate[bot] c81be6734a Lock file maintenance (#2478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 02:57:20 +00:00
FooIbar 663fedb81b Improve tag suggestion hit rate 2025-07-13 21:00:53 +08:00
renovate[bot] 200d29dc1e Update Rust crate rxing to 0.8 (#2476)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-12 18:43:45 +00:00
renovate[bot] 41f6948f13 Update AGP to v8.11.1 (#2474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 22:41:37 +00:00
FooIbar 24e36b6283 [no ci] Allow blank issue 2025-07-09 20:29:09 +08:00
renovate[bot] 1b1df14937 Update dependency com.materialkolor:material-kolor to v3 (#2472)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-08 21:04:00 +00:00
FooIbar 895f75a3c5 Fix navigation drawer overlapping side sheet 2025-07-08 21:58:34 +08:00
FooIbar e428a8be57 Sync SwipeToDismissBox with upstream 2025-07-08 21:58:34 +08:00
renovate[bot] a438d2dd88 Update plugin spotless to v7.1.0 (#2470)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-08 03:18:51 +00:00
renovate[bot] f2107edaa3 Update dependency com.squareup.okhttp3:okhttp-bom to v5.1.0 (#2469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 22:53:53 +00:00
renovate[bot] 644f93a46d Update dependency org.jetbrains.kotlinx:kotlinx-datetime to v0.7.1 (#2468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 22:52:51 +00:00
renovate[bot] dc52e0534e Lock file maintenance (#2466)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 00:37:56 +00:00
FooIbar 15e63eb86c Fix text overflow
`maxLines = 1` should always be used with `softWrap = false` or
`overflow = Ellipsis`
2025-07-06 13:25:33 +08:00
FooIbar 842e59e893 Remove deprecated SaveBodyPlugin 2025-07-05 18:51:06 +08:00
FooIbar 2b7d6745ea Simplify native build 2025-07-05 11:27:56 +08:00
renovate[bot] 3c41063a38 Update dependency gradle to v8.14.3 (#2463)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 16:24:28 +00:00
renovate[bot] da8e84acad Update dependency io.ktor:ktor-client-okhttp to v3.2.1 (#2461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 22:34:46 +00:00
renovate[bot] 0b7658eb9b Update dependency com.squareup.okhttp3:okhttp-bom to v5.0.0 (#2460)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 17:33:08 +00:00
renovate[bot]andFooIbar 2d7bda3407 Update dependency androidx.compose:compose-bom-alpha to v2025.06.02 (#2457)
* Update dependency androidx.compose:compose-bom-alpha to v2025.06.02

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-07-03 02:53:34 +00:00
renovate[bot] a7d11d70d4 Update dependency androidx.core:core-splashscreen to v1.2.0-rc01 (#2458)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 22:19:47 +00:00
FooIbar 4b25fb8363 Fix a potential UAF
`dispose` may get called while decoding.
2025-07-03 00:09:12 +08:00
FooIbar b1aca0578c Build a single shared library 2025-07-02 23:58:31 +08:00
renovate[bot] 78d3021616 Update dependency com.squareup.okio:okio-jvm to v3.15.0 (#2454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 03:13:56 +00:00
renovate[bot] 14a7a64af2 Update aboutlibraries to v12.2.4 (#2453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 20:37:45 +00:00
FooIbar 6156b30a36 Remove experimental animated WebP decoder option 2025-06-30 17:09:28 +08:00
renovate[bot]andFooIbar 231afd3e71 Update dependency com.squareup.okhttp3:okhttp-bom to v5.0.0-alpha.17 (#2451)
* Update dependency com.squareup.okhttp3:okhttp-bom to v5.0.0-alpha.17

* Vendor AsyncDns stuff as they're removed in OkHttp

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-06-30 09:06:19 +00:00
renovate[bot] 14803997ba Lock file maintenance (#2452)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 03:20:05 +00:00
FooIbar 9a99597d4e Pause animated WebP when it becomes invisible 2025-06-29 21:01:41 +08:00
renovate[bot] 7079c3814b Update Rust crate quick-xml to 0.38 (#2448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-28 21:18:45 +00:00
renovate[bot] 35a95e2b9b Update dependency com.squareup.okio:okio-jvm to v3.14.0 (#2447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-28 17:39:21 +00:00
FooIbar 99d63c2396 Revert "Update dependency me.saket.telephoto:zoomable to v0.16.0 (#2336)"
This reverts commit fe371f1d66.
2025-06-28 22:41:37 +08:00
FooIbar 62383ed31b Address kotlinx-datetime 0.7.x deprecation 2025-06-28 17:20:24 +08:00
FooIbar a10f9f2ce8 Refine and enable AnimatedWebPDrawable by default (#2442) 2025-06-28 16:10:29 +08:00
renovate[bot] 924d3c4ea3 Update kotlinx.serialization to v1.9.0 (#2443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 16:37:09 +00:00
renovate[bot] be83b53941 Update dependency nettle to nettle_3.10.2_release_20250626 (#2441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 23:18:48 +00:00
renovate[bot] 63657d5286 Update dependency com.google.devtools.ksp to v2.2.0-2.0.2 (#2440)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 23:19:00 +00:00
FooIbar 9bd670cfd3 Revert "Update dependency androidx.compose:compose-bom-alpha to v2025.06.01 (…"
This reverts commit 2753804652.
2025-06-26 01:04:13 +08:00
renovate[bot]andFooIbar 2753804652 Update dependency androidx.compose:compose-bom-alpha to v2025.06.01 (#2434)
* Update dependency androidx.compose:compose-bom-alpha to v2025.06.01

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-06-25 02:41:56 +00:00
renovate[bot] 971c75e754 Update dependency org.jetbrains.kotlinx:kotlinx-datetime to v0.7.0 (#2433) 2025-06-25 02:41:20 +00:00
renovate[bot] bbe29cbd72 Update dependency chrome to v138 (#2436)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 22:32:39 +00:00
renovate[bot] f91deda5ba Update AGP to v8.11.0 (#2435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 20:04:27 +00:00
FooIbar bd11a7fcc2 AnimatedWebPDecoder POC (#2426) 2025-06-24 23:58:52 +08:00
renovate[bot] 6edc8f06f3 Update kotlin monorepo to v2.2.0 (#2432)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 12:38:38 +00:00
renovate[bot] 7e3d170c83 Lock file maintenance (#2431)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 02:06:07 +00:00
renovate[bot] cde3ea0c95 Update dependency androidx.work:work-runtime to v2.10.2 (#2430)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 23:15:37 +00:00
renovate[bot] 5a34fb3443 Update dependency androidx.test.uiautomator:uiautomator to v2.4.0-alpha05 (#2429)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 03:35:54 +00:00
renovate[bot] c000617928 Update dependency androidx.room:room-compiler to v2.7.2 (#2428)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 23:56:49 +00:00
renovate[bot] 01e4747483 Update benchmark to v1.4.0-rc01 (#2427)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 23:56:22 +00:00
FooIbar 77eb761fc3 Fix torrent url 2025-06-18 16:56:56 +08:00
renovate[bot] 64c95c1bc5 Update dependency com.google.devtools.ksp to v2.2.0-RC3-2.0.2 (#2423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 20:02:09 +00:00
renovate[bot] 5e5f9ae4bc Update kotlin monorepo to v2.2.0-RC3 (#2422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 11:39:11 +00:00
FooIbar 015059321a Fix comment vote status parsing 2025-06-16 11:26:08 +08:00
renovate[bot] cc6ae217a4 Lock file maintenance (#2421)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 02:46:24 +00:00
FooIbar a1c730caac Fix PullToRefresh when content doesn't fill screen 2025-06-16 10:35:36 +08:00
FooIbar 365f6936a3 Stop image animation on off-screen pages 2025-06-16 10:35:13 +08:00
renovate[bot] a6f2034377 Update aboutlibraries to v12.2.3 (#2416)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 09:46:30 +00:00
FooIbar 95631ef468 Remove jsoup usage and clean up 2025-06-14 19:21:35 +08:00
renovate[bot] 5dc02c0bd5 Update dependency com.squareup.okio:okio-jvm to v3.13.0 (#2413)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-13 14:34:24 +00:00
FooIbar ea43837e62 Migrate GalleryDetailParser to Rust Part 4/N 2025-06-13 22:25:47 +08:00
FooIbar 4c359e50f8 Migrate GalleryDetailParser to Rust Part 3/N 2025-06-13 22:25:47 +08:00
FooIbar 1feca411b6 Migrate GalleryDetailParser to Rust Part 2/N 2025-06-12 00:33:33 +08:00
FooIbar 4dfcf96897 Clean up rust code
* Make `img` and `parser` public to get rid of dead code warnings
* Remove redundant `no_mangle` and `allow(non_snake_case)` as `jni_fn`
already adds them
2025-06-11 17:40:08 +08:00
renovate[bot] 90568f2216 Lock file maintenance (#2407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 06:31:05 +00:00
FooIbar 4440763f44 Rust 2024 2025-06-11 13:40:05 +08:00
Re*Index. (ot_inc) 5b80fee02e Update Japanese 2025-06-11 09:23:13 +08:00
FooIbar e21a0eca5c Optimize rust binary size (#2403)
* -10.3% vs nightly-2024-08-30 with backtrace
* +2.8% vs nightly-2024-08-30 without backtrace
2025-06-11 09:22:36 +08:00
renovate[bot] 83a1096c08 Update dependency com.google.devtools.ksp to v2.2.0-RC2-2.0.2 (#2405)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 23:23:44 +00:00
renovate[bot] 59f1e7f471 Update aboutlibraries to v12.2.2 (#2404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 23:23:09 +00:00
FooIbar 12374fb573 Migrate GalleryDetailParser to Rust Part 1/N 2025-06-10 11:39:42 +08:00
renovate[bot] 734bffd279 Lock file maintenance (#2400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 00:48:45 +00:00
renovate[bot] 478d6c9035 Update dependency sh.calvin.reorderable:reorderable to v2.5.1 (#2399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 21:44:56 +00:00
renovate[bot] a6048c84bd Update dependency sh.calvin.reorderable:reorderable to v2.5.0 (#2398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 07:00:06 +00:00
renovate[bot] c0ab14e241 Update dependency com.google.devtools.ksp to v2.2.0-RC2-2.0.1 (#2394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 07:59:42 +00:00
renovate[bot] 23332bf4a6 Update dependency com.squareup.logcat:logcat to v0.2.3 (#2395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 07:59:26 +00:00
renovate[bot]andDude so hot a55293448e Convert awaitSelectTime to use TimePickerDialog (#2393)
* Convert awaitSelectTime to use TimePickerDialog

* fixup! Convert awaitSelectTime to use TimePickerDialog

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2025-06-06 15:53:11 +08:00
FooIbar f809d724a0 Reapply "Store pToken in GalleryPreview and refine parser" (#2390)
This reverts commit 5b8d3a2824.

Reason for revert: Reland with fix

Also clear invalid pToken
2025-06-05 22:36:57 +08:00
renovate[bot] 32e2e91013 Update dependency gradle to v8.14.2 (#2392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 14:03:29 +00:00
renovate[bot] 5b8d3a2824 Revert "Store pToken in GalleryPreview and refine parser" (#2390) 2025-06-05 07:17:26 +00:00
renovate[bot] d4b152e7c0 Update dependency androidx.compose:compose-bom-alpha to v2025.06.00 (#2388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 01:58:41 +00:00
renovate[bot] c62c84ae4d Update dependency com.squareup.logcat:logcat to v0.2.2 (#2387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 01:58:06 +00:00
renovate[bot] 9e1869edde Update dependency androidx.test.uiautomator:uiautomator to v2.4.0-alpha04 (#2386)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 21:12:45 +00:00
renovate[bot] 1a164ff8f1 Update dependency androidx.lifecycle:lifecycle-process to v2.9.1 (#2385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 21:12:28 +00:00
renovate[bot] 6b3a4aec16 Update benchmark to v1.4.0-beta02 (#2383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 18:36:38 +00:00
renovate[bot] 150a687f15 Update dependency androidx.appcompat:appcompat to v1.7.1 (#2384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 18:36:08 +00:00
FooIbar fc1d514475 Store pToken in GalleryPreview and refine parser
We no longer save previews in bundle.
2025-06-04 23:04:30 +08:00
renovate[bot] 5528bef09a Update kotlin monorepo to v2.2.0-RC2 (#2382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 14:06:19 +00:00
FooIbar 4013740429 Enable simd feature for tl
Since we use nightly anyway.
2025-06-04 21:57:05 +08:00
FooIbar 000a932ff1 Migrate profile parser to rust 2025-06-03 23:58:52 +08:00
FooIbar 60cd7728e6 Migrate archive parser to rust 2025-06-02 22:52:40 +08:00
renovate[bot] 4f37088e20 Lock file maintenance (#2377)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 00:30:31 +00:00
renovate[bot] d7dc6487d0 Update dependency libarchive to v3.8.1 (#2376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-01 21:41:37 +00:00
renovate[bot] 61c9dcbd1d Update dependency io.github.petertrr:kotlin-multiplatform-diff to v1.1.0 (#2374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-01 13:59:15 +00:00
FooIbar 8f0404242d Remove ConstraintLayout usage 2025-06-01 16:03:29 +08:00
FooIbar 286af0e5bf Do not package OkHttp public suffix list
We handle cookies in ktor.
2025-05-31 15:24:34 +08:00
renovate[bot]andFooIbar 6c49904bf8 Update aboutlibraries to v12.2.0 (#2370)
* Update dependency com.mikepenz.aboutlibraries.plugin to v12.2.0

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-05-31 04:55:01 +00:00
renovate[bot] 509ee08e8e Update dependency com.squareup.okhttp3:okhttp-bom to v5.0.0-alpha.16 (#2369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 18:20:34 +00:00
renovate[bot] fdae6f52cc Update dependency com.squareup.okhttp3:okhttp-bom to v5.0.0-alpha.15 (#2368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 16:04:01 +00:00
renovate[bot] 52ef7cd956 Update AGP to v8.10.1 (#2367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 22:54:12 +00:00
renovate[bot] d8bd81a0b6 Update plugin spotless to v7.0.4 (#2365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 03:06:30 +00:00
renovate[bot] c2e33c7f50 Update dependency com.squareup.okio:okio-jvm to v3.12.0 (#2366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 03:06:07 +00:00
renovate[bot] a9498a1b92 Update dependency chrome to v137 (#2364)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 20:44:45 +00:00
FooIbar f74b8373e3 Fix predictive back crash after launch page change
https://issuetracker.google.com/384186542
2025-05-27 22:34:41 +08:00
FooIbar 809f628bc0 Fix stop all shortcut with no active downloads
The downloading notification would keep showing.
2025-05-26 23:38:46 +08:00
FooIbar 54a2b28219 Auto backup improvements
- Fix backup data size quota exceed
- Backup cookies and preferences on Android 9+ (require E2EE)
2025-05-26 21:20:47 +08:00
FooIbar 0719c53e86 Restore accidently removed @Serializable on Limits 2025-05-26 15:23:29 +08:00
renovate[bot] e9f3deaec0 Lock file maintenance (#2359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-26 02:38:06 +00:00
FooIbar 8207be5940 Reapply "Migrate from Parcelize to kotlinx.serialization" (#2357)
* Reapply "Migrate from Parcelize to kotlinx.serialization" (#2356)

This reverts commit c2e53cc8b9.

* Fix stale states captured by lambdas in ViewModels
2025-05-25 22:27:16 +08:00
FooIbar c2e53cc8b9 Revert "Migrate from Parcelize to kotlinx.serialization" (#2356)
This reverts commit 857b1d9b57.

Reason for revert: https://github.com/FooIbar/EhViewer/issues/2355
2025-05-23 16:21:11 +00:00
FooIbar 251b99f76d Fix snackbar blocking progress dialog dismissal 2025-05-23 23:21:01 +08:00
Dude so hot c4c86104b5 Revert "MetaState trigger refresh implicitly (#2350)"
This reverts commit 277deace47.
Revert reason: `ListUrlBuilder` doesn't have a qualified `equals` semantics

# Conflicts:
#	app/src/main/kotlin/com/hippo/ehviewer/client/data/FavListUrlBuilder.kt
#	app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt
2025-05-23 20:20:47 +08:00
FooIbar 857b1d9b57 Migrate from Parcelize to kotlinx.serialization
Also remove some unnecessary Parcelables.
2025-05-23 18:22:10 +08:00
renovate[bot] 277deace47 MetaState trigger refresh implicitly (#2350) 2025-05-23 17:09:08 +08:00
renovate[bot] 30c659110f Migrate to context parameters 2025-05-23 04:57:48 +00:00
renovate[bot] a8b6adfba2 Update dependency gradle to v8.14.1 (#2349)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 14:27:56 +00:00
FooIbar 1548e16fbe Avoid drawing invisible slider ticks and cleanup (#2346) 2025-05-22 10:03:14 +08:00
FooIbar 0e34baf1bf Enable composition stack traces in debug builds 2025-05-21 20:48:45 +08:00
renovate[bot] 2c461b5ccf Update dependency androidx.datastore:datastore to v1.1.7 (#2315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-21 02:03:56 +00:00
renovate[bot] 2adbeddde3 Update dependency androidx.test.uiautomator:uiautomator to v2.4.0-alpha03 (#2344)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-20 22:31:59 +00:00
renovate[bot] e0a79fcc63 Update dependency androidx.compose:compose-bom-alpha to v2025.05.01 (#2342)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-20 18:04:47 +00:00
renovate[bot] 5e07813a57 Update dependency androidx.biometric:biometric to v1.4.0-alpha04 (#2341)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-20 18:04:15 +00:00
FooIbar 400d478bca Update build scripts for Kotlin 2.2
- Don't generate compatibility bridges and `DefaultImpls` classes
- Remove when guards compiler option as it became stable
- Address deprecation of `kotlinOptions`
2025-05-21 00:58:26 +08:00
renovate[bot]andrenovate[bot] 786ea2ca37 Update dependency libarchive to v3.8.0 (#2338)
* Update dependency libarchive to v3.8.0

* Update 0002-Fix-zip_time-performance.patch

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <99822064+revonateB0T@users.noreply.github.com>
2025-05-20 15:52:52 +00:00
Dude so hot ba169d35a4 Kotlin 2.2.0-RC 2025-05-20 23:22:23 +08:00
FooIbar f2dbb31951 Reader app bars improvements (#2337)
* Flatten reader app bars layout

* Fix reader app bars contract on grey background

* Fix unexpected reader app bars visibility changes
2025-05-20 20:01:24 +08:00
renovate[bot] fe371f1d66 Update dependency me.saket.telephoto:zoomable to v0.16.0 (#2336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-20 07:31:22 +00:00
renovate[bot]andFooIbar 1b8ae87b08 Update dependency com.pinterest.ktlint:ktlint-cli to v1.6.0 (#2335)
* Update dependency com.pinterest.ktlint:ktlint-cli to v1.6.0

* Fix spotless

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-05-20 05:27:44 +00:00
renovate[bot] fece5f64ce Update dependency com.squareup.logcat:logcat to v0.2.1 (#2334)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 21:01:00 +00:00
FooIbar bcedbd85ef Image loading optimizations
- Fix potential memory cache miss
- Reduce `SizeResolver` allocations
- Make data source type more accurate
2025-05-19 21:36:27 +08:00
renovate[bot]andFooIbar dc4ea5e96d Update dependency com.squareup.logcat:logcat to v0.2 (#2331)
* Update dependency com.squareup.logcat:logcat to v0.2

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-05-19 04:02:24 +00:00
renovate[bot] 7e0e01295f Lock file maintenance (#2332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 02:42:29 +00:00
FooIbar 88268a22a9 Migrate to M3 Expressive medium flexible app bar 2025-05-18 23:51:49 +08:00
FooIbar 428a1cb0d2 Fix download progress indicator recomposition 2025-05-18 19:11:23 +08:00
FooIbar b3dda2a1ff Migrate to M3 Expressive docked toolbar 2025-05-17 23:10:17 +08:00
FooIbar 74f9330c38 Migrate to M3 Expressive progress indicators 2025-05-17 23:10:17 +08:00
FooIbar ebf99eda55 Migrate to M3 Expressive loading indicator 2025-05-17 23:10:17 +08:00
FooIbar 7d05e5382a Use LazyLayoutCacheWindow API for prefetching (#2325)
* Use `LazyLayoutCacheWindow` API for prefetching

With the old strategy, prefetched images may be evicted from cache.

* Make decoding process non-cancellable
2025-05-17 23:10:05 +08:00
FooIbar e559520df3 Minor cleanup 2025-05-17 14:39:36 +08:00
renovate[bot] a9b300d357 Update dependency io.arrow-kt:arrow-stack to v2.1.2 (#2326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 14:55:38 +00:00
renovate[bot] f58f930446 Update xmlutil to v0.91.1 (#2324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 20:17:07 +00:00
FooIbar f10bf6d714 Add back desktop site option to bypass Cloudflare (#2323) 2025-05-15 21:49:21 +08:00
FooIbar 6898300c30 Fix quick search import 2025-05-14 21:42:46 +08:00
FooIbar 85bc6fdd28 Show favorite status in grid view 2025-05-14 18:49:13 +08:00
renovate[bot] 4cdf5a8ba6 Update dependency com.google.devtools.ksp to v2.1.21-2.0.1 (#2320)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 23:28:34 +00:00
renovate[bot] 8ca220e98a Update kotlin monorepo to v2.1.21 (#2318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 13:10:29 +00:00
renovate[bot] ec2d37b295 Update dependency io.coil-kt.coil3:coil-bom to v3.2.0 (#2317)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 07:15:26 +00:00
renovate[bot] 3b2118331b Lock file maintenance (#2316)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 02:28:01 +00:00
renovate[bot] ed89e504b0 Update dependency androidx.compose:compose-bom-alpha to v2025.05.00 2025-05-08 08:57:53 +08:00
renovate[bot] 30eec80177 Revert "Update dependency androidx.datastore:datastore to v1.1.6 (#2311)"
This reverts commit 07f730aba6.
2025-05-08 08:39:34 +08:00
renovate[bot] 07f730aba6 Update dependency androidx.datastore:datastore to v1.1.6 (#2311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-07 23:06:32 +00:00
renovate[bot] 3bfd282614 Update dependency androidx.test.uiautomator:uiautomator to v2.4.0-alpha02 (#2312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-07 23:06:13 +00:00
renovate[bot] d031954976 Update androidx.lifecycle to v2.9.0 (#2309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-07 20:03:27 +00:00
renovate[bot] 86f9b9d0f7 Update benchmark to v1.4.0-beta01 (#2310)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-07 20:02:42 +00:00
renovate[bot] 3b1f98d675 Update aboutlibraries to v12.1.2 (#2308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-07 12:32:00 +00:00
renovate[bot] 66e37c8f6b Update AGP to v8.10.0 (#2307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 20:14:29 +00:00
renovate[bot] 3fc6adb2b7 Update aboutlibraries to v12.1.1 (#2306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 20:10:53 +00:00
renovate[bot] 1c971c161f Update dependency io.ktor:ktor-client-okhttp to v3.1.3 (#2305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 12:35:34 +00:00
renovate[bot] 8883d048a7 Lock file maintenance (#2304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 02:27:47 +00:00
renovate[bot] 66dbbb1eee Update compose.destinations to v2.2.0 (#2303)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-03 14:28:14 +00:00
renovate[bot] cb7bbeba99 Update compose.destinations to v2.1.1 (#2302)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-01 15:38:02 +00:00
renovate[bot] 7bf8de139f Update aboutlibraries to v12.1.0 (#2301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-01 07:56:40 +00:00
renovate[bot] b7ffc255f5 Update dependency com.google.devtools.ksp to v2.1.20-2.0.1 (#2300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-01 01:45:22 +00:00
renovate[bot] 06aa4c60c8 Update dependency chrome to v136 (#2299)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 19:49:06 +00:00
renovate[bot] 07e4db9240 Update dependency io.arrow-kt:arrow-stack to v2.1.1 (#2298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 15:06:38 +00:00
renovate[bot] c0144809c6 Update dependency org.jsoup:jsoup to v1.20.1 (#2297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 06:41:59 +00:00
renovate[bot] ffeadc9e44 Update dependency com.google.accompanist:accompanist-drawablepainter to v0.37.3 (#2296)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 12:50:41 +00:00
renovate[bot] a15ed69624 Lock file maintenance (#2295)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 02:03:03 +00:00
renovate[bot] b0fd40444f Update aboutlibraries to v12.1.0-rc04 (#2294)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-27 19:12:27 +00:00
Dude so hot 8cddc0d974 Fix DownloadScreen thumb flickering on select state changed 2025-04-27 16:31:49 +08:00
renovate[bot] 08b76b643d Update aboutlibraries to v12.1.0-rc03 (#2291)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-26 18:52:25 +00:00
renovate[bot] 13c623b788 Update aboutlibraries to v12.1.0-rc02 (#2286) 2025-04-26 13:04:04 +00:00
renovate[bot] b77bd6b3f9 Update dependency gradle to v8.14 (#2290)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-25 12:14:05 +00:00
FooIbar d3dec6981a Update aboutlibraries to v12.1.0-rc01 2025-04-25 20:10:26 +08:00
renovate[bot] 5dd617be69 Update dependency Corrosion to v0.5.2 (#2288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-25 03:03:58 +00:00
FooIbar 1b093469bf Update NDK to r28b 2025-04-24 23:14:18 +08:00
FooIbar 7286c81885 Address deprecations 2025-04-24 23:06:28 +08:00
FooIbar 4ef7707736 Address warnings 2025-04-24 23:06:28 +08:00
renovate[bot] c06a9f915d Update dependency androidx.compose:compose-bom-alpha to v2025.04.01 (#2283)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-24 03:08:28 +00:00
renovate[bot] fe2907974a Update dependency androidx.work:work-runtime to v2.10.1 (#2282)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-24 03:08:09 +00:00
renovate[bot] 3e79617fc9 Update dependency androidx.core:core-splashscreen to v1.2.0-beta02 (#2280)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-23 23:23:19 +00:00
renovate[bot] 52e2fe6fed Update androidx.room to v2.7.1 (#2279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-23 20:12:58 +00:00
renovate[bot] 8e4265a5f8 Update androidx.lifecycle to v2.9.0-rc01 (#2278)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-23 20:12:19 +00:00
renovate[bot] 623de00376 Update AGP to v8.9.2 (#2276)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 19:53:25 +00:00
renovate[bot] 6320900d7c Lock file maintenance (#2274)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 03:50:56 +00:00
FooIbar ab1ff284ca [no ci] Fix renovate config 2025-04-19 00:10:49 +08:00
renovate[bot]andFooIbar 37ad3d0882 Update dependency com.mikepenz.aboutlibraries.plugin to v12.0.0 (#2269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-04-18 16:04:54 +00:00
renovate[bot] c94123da06 Update aboutlibraries to v12.0.0 (#2268)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-18 15:50:02 +00:00
FooIbar 7eef1727a2 Revert "Update aboutlibraries to v12.0.0-b03 (#2264)"
This reverts commit aefca644e8.
2025-04-17 20:37:29 +08:00
FooIbar 6f692db069 Clean up renovate config
KSP2 is no longer a compiler plugin so the Kotlin version doesn't matter
2025-04-17 20:36:09 +08:00
renovate[bot] aefca644e8 Update aboutlibraries to v12.0.0-b03 (#2264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-17 12:29:06 +00:00
FooIbar 99ff71b7e1 Update aboutlibraries to v12.0.0-b03 2025-04-17 20:21:00 +08:00
FooIbar 430ee19dd3 Allow users to disable QUIC support 2025-04-15 21:20:06 +08:00
renovate[bot] d9dbafb51b Lock file maintenance (#2260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 02:57:32 +00:00
FooIbar d78b3a7fa0 Revert "Update dependency androidx.compose:compose-bom-alpha to v2025.04.00 (…"
This reverts commit d753c36195.
2025-04-10 15:36:20 +08:00
renovate[bot] 424dc6dd49 Update dependency com.squareup.okio:okio-jvm to v3.11.0 (#2256)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 07:05:21 +00:00
renovate[bot] 1f4347a316 Update dependency androidx.core:core-ktx to v1.16.0 (#2255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 07:05:04 +00:00
renovate[bot] acd9f2ae1c Update benchmark to v1.4.0-alpha11 (#2253)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 04:02:17 +00:00
renovate[bot] d753c36195 Update dependency androidx.compose:compose-bom-alpha to v2025.04.00 (#2254)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 04:01:51 +00:00
renovate[bot] 7dd0deb931 Update androidx.room to v2.7.0 (#2251)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-09 22:39:22 +00:00
renovate[bot] ba1126db0e Update androidx.lifecycle to v2.9.0-beta01 (#2250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-09 22:38:47 +00:00
renovate[bot] 359a417692 Drop obsolete argument 2025-04-09 13:36:40 +08:00
renovate[bot] 42ac0055c2 Update dependency com.google.devtools.ksp to v2.1.20-2.0.0 (#2248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-09 03:34:53 +00:00
renovate[bot] c0dd03e3f7 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.10.2 (#2247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 12:17:46 +00:00
renovate[bot] 6b1bda629f Update plugin spotless to v7.0.3 (#2246)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 03:35:18 +00:00
renovate[bot] 0b43daf507 Lock file maintenance (#2245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-07 01:50:31 +00:00
renovate[bot] 894038280e Update plugin composeCompilerReportGenerator to v1.4.2 (#2244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-06 13:38:49 +00:00
renovate[bot] 2704ab9afb Update dependency liblzma to v5.8.1 (#2241)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 15:41:55 +00:00
FooIbar 8d604848da Revert "Update dependency androidx.compose:compose-bom-alpha to v2025.03.01 (#2222)"
This reverts commit ef2cfec1a2.

Reason for revert: https://issuetracker.google.com/408118041
2025-04-03 23:41:36 +08:00
renovate[bot] 6f6f123880 Update dependency chrome to v135 (#2239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 22:17:50 +00:00
renovate[bot] 620b037a17 Update xmlutil to v0.91.0 (#2237)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 16:43:56 +00:00
renovate[bot] a653454e7d Update kotlinx.serialization to v1.8.1 (#2236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 16:43:29 +00:00
renovate[bot] 38a8c9eb34 Lock file maintenance (#2235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 03:47:06 +00:00
renovate[bot] 8cdf16a272 Update dependency libarchive to v3.7.9 (#2234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-30 22:28:13 +00:00
renovate[bot] 24333a24ae Lock file maintenance (#2231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-29 14:37:55 +00:00
FooIbar e83c542886 Update androidx.biometric to v1.4.0-alpha03 2025-03-28 19:30:03 +08:00
FooIbar c61824b362 Update xmlutil to 0.91.0-RC1 2025-03-28 17:07:40 +08:00
renovate[bot] 591f918412 Update dependency io.ktor:ktor-client-okhttp to v3.1.2 (#2226)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 12:10:24 +00:00
renovate[bot] ab1335102a Update dependency com.google.devtools.ksp to v2.1.20-1.0.32 (#2225)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 06:49:34 +00:00
renovate[bot] 41cdc33432 Update dependency androidx.datastore:datastore to v1.1.4 (#2224)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 06:49:09 +00:00
renovate[bot] ef2cfec1a2 Update dependency androidx.compose:compose-bom-alpha to v2025.03.01 (#2222)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 02:34:31 +00:00
renovate[bot] 6f2a5d8cdb Update benchmark to v1.4.0-alpha10 (#2221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 02:33:52 +00:00
renovate[bot] c3d52d430a Update androidx.room to v2.7.0-rc03 (#2220)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 23:08:55 +00:00
renovate[bot] 806897adf8 Update androidx.lifecycle to v2.9.0-alpha13 (#2219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 23:08:40 +00:00
renovate[bot] c9b1bc175a Update dependency com.materialkolor:material-kolor to v2.1.1 (#2218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 17:03:09 +00:00
renovate[bot]andFooIbar f4d9d9795a Update dependency liblzma to v5.8.0 (#2216)
* Update dependency liblzma to v5.8.0

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-03-26 11:35:47 +00:00
renovate[bot] 08e6d670de Update dependency com.materialkolor:material-kolor to v2.1.0 (#2217)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 22:12:34 +00:00
renovate[bot] a2848d52fc Update AGP to v8.9.1 (#2214)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 20:43:22 +00:00
FooIbar fa219a674e Address warnings 2025-03-24 21:15:41 +08:00
renovate[bot] 0d0faa3218 Lock file maintenance (#2212)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 04:00:10 +00:00
renovate[bot] 09ab930c56 Revert "Disable shared element transition for DownloadCard"
This reverts commit e5361949df.
2025-03-21 23:58:25 +08:00
renovate[bot] 9b7b411825 Inline PageLoader user (#2209) 2025-03-21 22:03:37 +08:00
renovate[bot] aacde8871e Migrate j.u.c.a atomics usages to k.c.a (#2207) 2025-03-21 12:27:38 +00:00
Dude so hot 1aa1addcf7 Address NamedMutex warning 2025-03-21 11:12:06 +08:00
Dude so hot a305980a78 Address telephoto deprecated API 2025-03-21 11:12:06 +08:00
Dude so hot 0def055e8b Avoid contextual classes for incoming context parameters 2025-03-21 11:12:06 +08:00
Dude so hot 29e9291030 Update benchmark module sdk to 36 2025-03-21 10:41:33 +08:00
renovate[bot] e9015d95e4 Update dependency com.google.devtools.ksp to v2.1.20-1.0.31 (#2203)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 02:27:38 +00:00
renovate[bot] 9cedcc4395 Update Kotlin & KSP to v2.1.20 (#2200)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 12:53:37 +00:00
renovate[bot] 744d8f4262 Update dependency libarchive to v3.7.8 (#2201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 12:53:07 +00:00
renovate[bot] d9efc7aa19 Lock file maintenance (#2199)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 03:12:28 +00:00
FooIbar 53525cffe9 Fix renovate config 2025-03-14 20:01:47 +08:00
renovate[bot] c1269ede63 Update dependency com.pinterest.ktlint:ktlint-cli to v1.5.0 (#2198)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 12:01:38 +00:00
FooIbar 6102a33397 Fix renovate config 2025-03-14 19:54:42 +08:00
FooIbar 192e8ef664 Target SDK 36 2025-03-14 17:41:45 +08:00
renovate[bot] 5778230ee4 Update dependency androidx.compose:compose-bom-alpha to v2025.03.00 (#2196)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 02:21:50 +00:00
renovate[bot] 9e959306b1 Update benchmark to v1.4.0-alpha09 (#2195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 02:21:09 +00:00
renovate[bot] 7a7f4eb458 Update androidx.room to v2.7.0-rc02 (#2193)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 00:22:36 +00:00
renovate[bot] 050545411d Update androidx.lifecycle to v2.9.0-alpha12 (#2192)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 00:22:08 +00:00
FooIbar 28b394d5ea Tweak side sheet item style 2025-03-11 22:39:06 +08:00
renovate[bot] a3761dab35 Lock file maintenance (#2190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 01:46:33 +00:00
FooIbar 79ae51a973 Use KSP2 2025-03-08 14:30:16 +08:00
FooIbar b724286886 Drop obsolete code as HTTP/2 is fully supported (#2185) 2025-03-08 14:23:10 +08:00
renovate[bot] 9c52e789b3 Update Rust crate android_logger to 0.15 (#2188)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 22:52:03 +00:00
renovate[bot] 717a3740c6 Update compose.destinations to v2.1.0 (#2187)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 19:59:31 +00:00
renovate[bot] a8738b9ca9 Update dependency chrome to v134 (#2184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 21:39:34 +00:00
renovate[bot] ea5dba02b1 Update AGP to v8.9.0 (#2183)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 20:03:51 +00:00
renovate[bot] 9fbcb59f32 Update dependency org.jsoup:jsoup to v1.19.1 (#2182)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 07:42:28 +00:00
renovate[bot] 9b312d4a71 Lock file maintenance (#2180)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 03:13:52 +00:00
renovate[bot] 480002e50d Update dependency com.google.devtools.ksp to v2.1.10-1.0.31 (#2179)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 19:30:44 +00:00
renovate[bot] 9cc775f548 Update dependency io.github.petertrr:kotlin-multiplatform-diff to v1 (#2178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 07:14:57 +00:00
renovate[bot] 514e50da74 Update dependency androidx.datastore:datastore to v1.1.3 (#2177)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 07:14:10 +00:00
renovate[bot] 9dea931db0 Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.1 (#2175)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 03:37:24 +00:00
renovate[bot] 12c60715d2 Update dependency androidx.core:core-splashscreen to v1.2.0-beta01 (#2176)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 03:37:11 +00:00
renovate[bot] 23e0c45a52 Update androidx.room to v2.7.0-rc01 (#2173)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 22:34:54 +00:00
renovate[bot] c0c9c834e7 Update dependency androidx.activity:activity-compose to v1.10.1 (#2174)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 22:34:30 +00:00
renovate[bot] 9aaa0e8d36 Update AGP to v8.8.2 (#2171)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 19:16:57 +00:00
renovate[bot] 7bb4daabe2 Update androidx.lifecycle to v2.9.0-alpha11 (#2172)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 19:13:22 +00:00
renovate[bot] b0203874af Update dependency gradle to v8.13 (#2170)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 12:13:03 +00:00
renovate[bot] 2ee757e0b2 Update dependency com.android.tools:desugar_jdk_libs to v2.1.5 (#2169)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 12:08:33 +00:00
renovate[bot] b4db2885aa Update dependency io.ktor:ktor-client-okhttp to v3.1.1 (#2168)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 22:09:30 +00:00
FooIbar 4381237eb3 Use different color for selected items 2025-02-24 17:09:53 +08:00
renovate[bot] d91d333baa Lock file maintenance (#2165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 01:59:26 +00:00
FooIbar 5f6f979bce Comment about rememberSwipeToDismissBoxState usage 2025-02-21 20:32:15 +08:00
FooIbar 881b76fa3e Only add quick search when is not loading or error 2025-02-21 20:32:15 +08:00
renovate[bot] e92dc29896 Update dependency com.google.accompanist:accompanist-drawablepainter to v0.37.2 (#2161)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 02:38:29 +00:00
renovate[bot] 07e64a30e4 Update dependency com.google.accompanist:accompanist-drawablepainter to v0.37.1 (#2159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 06:50:04 +00:00
FooIbar ac8765655f Fix lambdas in side sheet capturing outdated value 2025-02-19 22:13:47 +08:00
renovate[bot] b5c8cfd9f8 Lock file maintenance (#2158)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 03:19:50 +00:00
renovate[bot] 71b11a3708 Update dependency me.saket.telephoto:zoomable to v0.15.1 (#2157)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-16 22:50:56 +00:00
renovate[bot] 8040394150 Update dependency me.saket.telephoto:zoomable to v0.15.0 (#2155)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-16 05:39:17 +00:00
FooIbar a07856a877 Revert "Update aboutlibraries to v11.6.0 (#2109)"
This reverts commit 6e990d3359.
2025-02-14 23:03:17 +08:00
renovate[bot] 6e990d3359 Update aboutlibraries to v11.6.0 (#2109)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 14:53:53 +00:00
renovate[bot] 031cdbf187 Update dependency com.google.devtools.ksp to v2.1.10-1.0.30 (#2151)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 06:30:41 +00:00
renovate[bot] 7198bda02e Update AGP to v8.8.1 (#2150)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 20:08:50 +00:00
renovate[bot] 18df20b675 Update dependency androidx.compose:compose-bom-alpha to v2025.02.00 (#2147)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 00:54:52 +00:00
FooIbar 581e6d2e5c Fix CI channel update 2025-02-13 08:46:00 +08:00
renovate[bot] 91c92f9dac Update benchmark to v1.4.0-alpha08 (#2145)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 21:56:09 +00:00
renovate[bot] 886e84a0c5 Update dependency androidx.paging:paging-compose to v3.3.6 (#2146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 21:55:39 +00:00
renovate[bot] bfeeb955f0 Update androidx.lifecycle to v2.9.0-alpha10 (#2143)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 19:40:18 +00:00
renovate[bot] beebffa5fc Update androidx.room to v2.7.0-beta01 (#2144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 19:39:22 +00:00
FooIbar bf6085bdf9 Migrate to the new semantics-based Autofill APIs 2025-02-12 17:42:53 +08:00
FooIbar db064e788b Fix auto logout when logging in through WebView 2025-02-12 17:42:42 +08:00
renovate[bot] 23be3be21e Upgrade build tools to 35.0.1 2025-02-12 13:23:57 +08:00
renovate[bot] 02ab809c34 NDK r28 (#2140)
* Update settings.gradle.kts

* Update build.gradle.kts
2025-02-12 10:22:07 +08:00
renovate[bot] 4d538714e4 Update dependency io.ktor:ktor-client-okhttp to v3.1.0 (#2139)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 23:42:02 +00:00
FooIbar 43e968864d Fix app updater to bypass GitHub login requirement 2025-02-11 08:31:57 +08:00
renovate[bot] 03f01bb5a0 Update dependency org.jetbrains.kotlinx:kotlinx-datetime to v0.6.2 (#2136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 16:57:44 +00:00
renovate[bot] a7a6f60ebf Lock file maintenance (#2134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 01:13:54 +00:00
FooIbar 4debd0a292 Revert "Migrate to lyricist, Part 1 (#2014)"
This reverts commit 0e608d16
2025-02-08 11:02:50 +08:00
Re*Index. (ot_inc) f0fa010df4 Update Japanese translations (#2130)
* Update strings.xml

* Update strings.xml

* Update ja.md
2025-02-08 10:19:39 +08:00
renovate[bot] 005112043e Update dependency io.coil-kt.coil3:coil-bom to v3.1.0 (#2127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-05 01:15:50 +00:00
renovate[bot] 1d118af0f5 Update dependency chrome to v133 (#2126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-04 18:53:28 +00:00
renovate[bot] fa0bfc256a Lock file maintenance (#2123)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 01:39:44 +00:00
renovate[bot] 602905ef25 Update dependency com.materialkolor:material-kolor to v2.0.2 (#2120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 18:29:39 +00:00
renovate[bot] 1f429e4773 Update compose.destinations to v2.1.0-beta16 (#2119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 18:29:23 +00:00
renovate[bot] 6ce6699355 Update dependency androidx.compose:compose-bom-alpha to v2025.01.01 (#2118)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 01:02:31 +00:00
renovate[bot] 3d46ae8440 Update benchmark to v1.4.0-alpha07 (#2117)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 01:02:13 +00:00
renovate[bot] bbbea1db9c Update androidx.lifecycle to v2.9.0-alpha09 (#2115)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 20:47:54 +00:00
renovate[bot] dc7f353e67 Update androidx.room to v2.7.0-alpha13 (#2116)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 20:46:47 +00:00
renovate[bot] 6ca6f14ad9 Update dependency com.google.devtools.ksp to v2.1.10-1.0.29 (#2114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 02:02:30 +00:00
renovate[bot] 03fd2708cd Update Kotlin & KSP to v2.1.10 (#2113)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-27 16:29:40 +00:00
renovate[bot] 6149497885 Update dependency com.materialkolor:material-kolor to v2.0.1 (#2112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-26 20:28:27 +00:00
FooIbar f810ef03a7 Enable strict resource shrinking mode (#2110) 2025-01-25 14:05:01 +00:00
FooIbar eeef444df7 Revert "Update aboutlibraries to v11.5.0 (#2106)"
This reverts commit 03769e6cf6.
2025-01-25 17:12:02 +08:00
renovate[bot] 03769e6cf6 Update aboutlibraries to v11.5.0 (#2106)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-24 18:07:31 +00:00
renovate[bot] b0bd759035 Update dependency io.arrow-kt:arrow-stack to v2.0.1 (#2105)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-24 18:06:30 +00:00
renovate[bot] eb92b9ca78 Update dependency gradle to v8.12.1 (#2104)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-24 14:13:16 +00:00
renovate[bot] 411ab8db38 Update dependency liblzma to v5.6.4 (#2103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 21:14:22 +00:00
renovate[bot] a679c17c64 Lock file maintenance (#2101)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 01:12:10 +00:00
renovate[bot] 87b7c46eb5 Update dependency sh.calvin.reorderable:reorderable to v2.4.3 (#2099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-19 08:53:29 +00:00
renovate[bot]andDude so hot a8c0a05c28 Make DialogState Screen-Local (#2097)
* Prepare to make DialogState per Screen

* Remove some LocalDialogState usage

* Make DialogState Screen-Local

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2025-01-16 21:08:07 +08:00
Dude so hot 6ac512e749 Address Code Review 2025-01-16 20:22:34 +08:00
Dude so hot be55f3cf90 asyncState takes produce 2025-01-16 20:22:34 +08:00
Dude so hot cbbbc7f211 Rename delegateSnapshotUpdate to asyncState 2025-01-16 20:22:34 +08:00
Dude so hot 623c4c3e39 Remove unused functions 2025-01-16 20:22:34 +08:00
Dude so hot b0ab1f0f2d Make delegateSnapshotUpdate accept two lambda 2025-01-16 20:22:34 +08:00
renovate[bot]andDude so hot 733937ec74 Use androidx SearchBar InputField (#2095)
* Use androidx SearchBar InputField

* Fix

* Fix

* Address CR

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2025-01-16 11:31:30 +00:00
renovate[bot] 14a8818563 Update dependency androidx.compose:compose-bom-alpha to v2025 (#2094)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 01:30:16 +00:00
renovate[bot] 695d00506b Update dependency androidx.activity:activity-compose to v1.10.0 (#2092)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 20:28:43 +00:00
renovate[bot] e1a90a79ec Update dependency androidx.datastore:datastore to v1.1.2 (#2093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 20:28:22 +00:00
renovate[bot] c6cedf7a02 Update dependency chrome to v132 (#2091)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 02:02:49 +00:00
renovate[bot] 9d870a3781 Update plugin spotless to v7.0.2 (#2090)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 22:15:09 +00:00
renovate[bot] e0a33f3013 Lock file maintenance (#2089)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 01:29:24 +00:00
FooIbar e3e95788b4 Clean up build script and address some warnings 2025-01-12 12:18:28 +08:00
FooIbar 0e87595692 Fix gallery comment style by not reusing Spans 2025-01-11 21:35:54 +08:00
FooIbar e5361949df Disable shared element transition for DownloadCard 2025-01-11 16:39:06 +08:00
FooIbar 62290e2812 Fix favorite names & notes in gallery detail cache 2025-01-10 23:44:20 +08:00
renovate[bot] 489b82e8f4 Update AGP to v8.8.0 (#2083)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 22:21:08 +00:00
renovate[bot] 4e8d6e058b Update plugin com.android.settings to v8.8.0 (#2082)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 18:05:45 +00:00
renovate[bot] 535e3abe92 Update dependency com.squareup.okio:okio-jvm to v3.10.2 (#2079)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 21:45:13 +00:00
FooIbar 35fbd4443e Refine tag database updating 2025-01-08 23:41:16 +08:00
renovate[bot] edf667f462 Update plugin spotless to v7.0.1 (#2077)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 02:12:57 +00:00
renovate[bot] a22be909a7 Update dependency com.squareup.okio:okio-jvm to v3.10.1 (#2076)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 02:12:14 +00:00
FooIbar 915f6d0b46 Allow archives to have more pages than galleries (#2073) 2025-01-08 10:05:29 +08:00
FooIbar c23e7af24b Remove obsolete workaround
This reverts commit 8811f92aea.
2025-01-07 22:58:42 +08:00
renovate[bot] 91fdbaff4d Update plugin spotless to v7 (#2070)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 09:44:35 +00:00
renovate[bot] c6f258f01d Update dependency com.squareup.okio:okio-jvm to v3.10.0 (#2069)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 09:44:04 +00:00
FooIbar 8811f92aea Fix build 2025-01-07 17:37:02 +08:00
renovate[bot] 0726505c25 Update kotlinx.serialization to v1.8.0 (#2068)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 17:16:33 +00:00
renovate[bot] 4df1d6cb0a Lock file maintenance (#2067)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 00:09:20 +00:00
renovate[bot] 773b5d4dab Hide comment which score reaches -100 by default (#2065) 2025-01-04 02:49:42 +00:00
renovate[bot] 660fa66df4 Revert "Update aboutlibraries to v11.3.0 (#2063)"
This reverts commit c2992e3a35.
2025-01-04 10:42:52 +08:00
renovate[bot] c2992e3a35 Update aboutlibraries to v11.3.0 (#2063)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 18:35:50 +00:00
Dude so hot d07649bcf8 Cleanup unused code 2025-01-03 12:30:13 +08:00
Dude so hot 5e1baf2104 Recover removed author style 2025-01-03 12:30:13 +08:00
Dude so hot e8701b65c6 Drop HAtHBlacklist
immature implementation
2025-01-03 12:10:42 +08:00
Dude so hot 517d6f6d0b timeoutBySpeed accept OnTimeout handler 2025-01-03 12:10:42 +08:00
renovate[bot]andDude so hot b7c5c15280 Make timeoutSpeed pow of 2 (#2060)
* IntSliderPreference support display transforms

* Make timeoutSpeed pow of 2

* Fix conversion

* Fix conversion

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2025-01-03 11:45:21 +08:00
renovate[bot]andDude so hot 0561a9bcf6 H@H blacklist (#2058)
* Move SpeedTracker to proper place

* SpeedTracker use Instant to avoid value class in generic

* Initial HAtHBlacklist

* H@H blacklist switch

* Allow to trim H@H blacklist

* Don't blacklist if that H@H returned twice

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-12-31 19:46:28 +08:00
Dude so hot 05a14a3610 And balance retry times 2024-12-31 18:15:34 +08:00
Dude so hot b3737448d7 Don't retry at one H@H twice
Now that quota is cheap
2024-12-31 18:15:34 +08:00
renovate[bot]andDude so hot bfa91bf2fb Apply watchdog before send request (#2056)
* Apply watchdog before send request

* Cleanup CronetEngine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-12-31 17:55:39 +08:00
renovate[bot]andDude so hot b7948f7965 Introduce speed based timeout strategy (#2055)
* CronetEngine throw connTimeout properly

* Apply LowSpeedDetector

* Add switch

* Relax

* Refine

* Extract common part

* Apply to AppUpdater

* Fix

* Adjust

* Log conn timeout as well

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-12-31 15:25:00 +08:00
renovate[bot]andDude so hot b0f310b2fe Introduce connection timeout (#2054)
* Introduce connection timeout, Prepare to convert requestTimeout to speed based

* Fix step

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-12-31 13:15:42 +08:00
renovate[bot]andDude so hot ac598fdfab Introduce Windowed SpeedTracker (#2053)
* Remove unreachable cond in `humanReadableByteCount`

* Introduce Windowed SpeedTracker

* Cleanup

* Optimise SpeedTracker for startcase

* Fixup

* Update naming

* Fix

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-12-31 12:11:13 +08:00
renovate[bot] 60e6a04d0d Update dependency nettle to nettle_3.10.1_release_20241230 (#2052)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 19:45:06 +00:00
renovate[bot] c4884022af Lock file maintenance (#2051)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 01:29:22 +00:00
renovate[bot] a70091bf99 Update dependency Corrosion to v0.5.1 (#2050)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-29 17:46:35 +00:00
renovate[bot]andDude so hot ebaf75df95 Update Rust crate rxing to 0.7 (#2047)
* Update Rust crate rxing to 0.7

* Fix build

Signed-off-by: Dude so hot <djohn@fbi.gov>

---------

Signed-off-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-12-29 07:50:16 +00:00
FooIbar 2f044a17e1 Fix crash when download label query takes too long 2024-12-26 17:56:20 +08:00
renovate[bot] 782dafca33 Lock file maintenance (#2040)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 02:02:28 +00:00
FooIbar 7174bbc0bd Clean up build script 2024-12-22 14:59:21 +08:00
renovate[bot] f177045e89 Update compose.destinations to v2.1.0-beta15 (#2038)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-22 04:42:37 +00:00
renovate[bot] e32d66e0aa Update dependency gradle to v8.12 (#2037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 21:06:52 +00:00
renovate[bot] 0237aa31cb Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.10.1 (#2036)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 16:23:04 +00:00
renovate[bot] 18921f8750 Update dependency com.android.tools:desugar_jdk_libs to v2.1.4 (#2035)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 10:57:58 +00:00
renovate[bot] e848360c80 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.10.0 (#2034)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-19 22:33:59 +00:00
FooIbar a075a7cba8 Make SpiderInfo optional 2024-12-19 21:06:49 +08:00
renovate[bot] f98630cff1 Update dependency io.ktor:ktor-client-okhttp to v3.0.3 (#2030)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 23:45:01 +00:00
FooIbar 325e756e4c Fix SpiderInfo serialization 2024-12-16 21:06:05 +08:00
renovate[bot] 556c623e98 Lock file maintenance (#2028)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-16 00:54:09 +00:00
FooIbar de5d74a1f0 Remove more java.io usage 2024-12-13 11:51:48 +08:00
renovate[bot] 41decf85c9 Update dependency com.google.accompanist:accompanist-drawablepainter to v0.37.0 (#2025)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-13 02:34:16 +00:00
renovate[bot] 51fa017b8b Update dependency sh.calvin.reorderable:reorderable to v2.4.2 (#2023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 11:01:49 +00:00
renovate[bot] e78dd6f9dd Update dependency androidx.compose:compose-bom-alpha to v2024.12.01 (#2022)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 01:00:10 +00:00
renovate[bot] 0e8fcdd4d1 Update dependency androidx.paging:paging-compose to v3.3.5 (#2021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 00:59:17 +00:00
renovate[bot] 717fc422ff Update benchmark to v1.4.0-alpha06 (#2019)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 22:05:33 +00:00
renovate[bot] b07c4b7e6c Update dependency androidx.activity:activity-compose to v1.10.0-rc01 (#2020)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 22:05:20 +00:00
renovate[bot] ff2b4cf6e1 Update androidx.room to v2.7.0-alpha12 (#2018)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 19:15:18 +00:00
renovate[bot] a131fa3dd7 Update androidx.lifecycle to v2.9.0-alpha08 (#2017)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 19:15:01 +00:00
FooIbar b3a212c7fd Match platform language resolution behavior
And add non-Compose accessor for Strings.
2024-12-11 00:06:07 +08:00
0e608d169e Migrate to lyricist, Part 1 (#2014)
* Initial convert xml to kotlin i10n res

* Composing take Translations

* Move HistoryScreen to lyricist

* Move FavouritesScreen to lyricist

* Fix naming

* Fixes

* Fixes

* Convert

* Convert

* Revert

* Fix

* Convert

* Convert

* Formats

* Cleanup

* Cleanup

* Cleanup

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Fixes

* Cleanup

* Cleanup

* Address Code Review

* Address Code Review

* Address Code Review

* Update app/src/main/kotlin/com/hippo/ehviewer/ui/i18n/ThTranslations.kt

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

* Update app/src/main/kotlin/com/hippo/ehviewer/ui/i18n/KoTranslations.kt

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

* Address Code Review

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-12-10 12:36:04 +00:00
renovate[bot] c38fa2a70f Drop useK2Uast (#2012)
Enabled by default
2024-12-09 10:46:26 +08:00
renovate[bot] 4c54c4653e Enable one pass resource shrinker 2024-12-09 10:38:29 +08:00
renovate[bot] 5ae5943cc8 Lock file maintenance (#2010)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 02:11:37 +00:00
renovate[bot] 857d367d15 Drop obsolete agp flags 2024-12-09 10:11:25 +08:00
renovate[bot]andDude so hot 8c766115db Refine EhTagDatabase & Cleanup java io usage
* Further kill some java io usage

* Further optimize TagDatabase update logic

* Further optimize TagDatabase

* Move some part from okio to kxio

* Address Code Review

* Fix to not reinit with current if inited

* Convert readLegacySpiderInfo to use kxio

* Refine

* Refine

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-12-08 14:30:56 +00:00
FooIbar 7bd36dd274 Fix comment anchors handling and ripple on click 2024-12-08 12:14:15 +08:00
FooIbar bd246300e0 Revert "Kill Legacy TextView (#1786)"
This reverts commit 62927cb6

Reason for revert: Bad performance
2024-12-08 00:07:21 +08:00
renovate[bot] 31be546e8e Update dependency io.arrow-kt:arrow-stack to v2.0.0 (#2003)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-05 16:21:07 +00:00
FooIbar d57983032e Allow using RGB_565 on API < 26
And increase PageLoader cache size on API 26+
2024-12-06 00:13:50 +08:00
FooIbar 53b3190bbf Make image links in comments clickable 2024-12-04 20:53:00 +08:00
FooIbar d924237b7e Update thumbnail key in database on the fly 2024-12-04 20:42:21 +08:00
da29fc2f25 Handle multiple image format (#1988)
* Polymorphic copy_region

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Polymorphic qr_code

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Polymorphic detect_border

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Implement luminance conversion

* Implement RGB_565 and RGBA_F16 support

* Revert "Workaround for high bit depth images"

This reverts commit 12f46e2e4e.

* Add ptr_as_image

Signed-off-by: Dude so hot <djohn@fbi.gov>

* ptr_as_image_mut

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Refine poly

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Use fake channel

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Cleanup

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Naming

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Cleanup

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Use typealias

Signed-off-by: Dude so hot <djohn@fbi.gov>

* just unwarp

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Cleanup

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Just unwarp

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Address code review

Signed-off-by: Dude so hot <djohn@fbi.gov>

---------

Signed-off-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-12-04 19:30:46 +08:00
renovate[bot] 3bbf195870 Update dependency io.ktor:ktor-client-okhttp to v3.0.2 (#1998)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 19:14:24 +00:00
renovate[bot] 75dac72640 Update dependency org.jsoup:jsoup to v1.18.3 (#1997)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 03:50:20 +00:00
renovate[bot] df9af9c35a Lock file maintenance (#1996)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 00:40:45 +00:00
FooIbar cccc8e829e Don't retry for some unrecoverable errors 2024-12-01 17:46:25 +08:00
FooIbar e0b1b18299 Support reverse physical key controls in reader 2024-12-01 17:46:13 +08:00
FooIbar 02d607817a Limit the scope of MIUI Cronet workaround
`ERROR_OTHER` is too generic and contains `ERR_CONTENT_LENGTH_MISMATCH`
2024-12-01 17:46:01 +08:00
FooIbar 8ae62e21a7 Clean up redundant v1 thumbnails 2024-12-01 17:45:47 +08:00
FooIbar 14a59262af Add missing .heic to supported file extensions 2024-12-01 13:10:10 +08:00
FooIbar 12f46e2e4e Workaround for high bit depth images 2024-12-01 13:09:47 +08:00
renovate[bot] dec2e6b69f Update dependency io.arrow-kt:arrow-stack to v2.0.0-rc.1 (#1987)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-29 11:25:41 +00:00
renovate[bot] e82327a979 Update dependency com.google.devtools.ksp to v2.1.0-1.0.29 (#1986)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 00:18:41 +00:00
renovate[bot] 81fb2ddcd1 Update dependency com.google.devtools.ksp to v2.1.0-1.0.28 (#1985)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-27 23:19:58 +00:00
renovate[bot] a8922cd0b2 Update Kotlin & KSP to v2.1.0 (#1984)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-27 13:31:21 +00:00
renovate[bot] 4f3ea48ec7 Update dependency org.jsoup:jsoup to v1.18.2 (#1982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-27 05:11:59 +00:00
renovate[bot] 150265adf6 Update dependency io.coil-kt.coil3:coil-bom to v3.0.4 (#1981)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 09:35:14 +00:00
renovate[bot]andDude so hot aff627c0cd Implement standby behaviour (#1980)
* Implement standby behaviour

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-26 09:28:27 +00:00
renovate[bot]andDude so hot 2b803d4193 Draggable Dialog Bubble (#1979)
* Draggable Dialog Bubble

* Cleanup

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-26 14:30:04 +08:00
renovate[bot]andDude so hot 464fa7c0c9 Pausable Tagger Dialog (#1978)
* DialogState center & top to allow non `AlertDialog` content

* Initial DialogContent w/o AlertDialog

* Animate DialogContent bubble

* Simplify

* Protect DialogState with MutatorMutex

* Refine animation

* Refine impl

* Refine impl

* Refine impl

* Refine impl

* Refine impl

* Cleanup

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-26 13:41:14 +08:00
FooIbar e9564b1d33 Don't use all caps for uploader names
Uploader names are in UTF-8, `uppercase` may result in unexpected values
 in some locales.
2024-11-25 23:43:54 +08:00
FooIbar c75929789d Show favorite name in HistoryScreen 2024-11-25 20:13:09 +08:00
renovate[bot] 63b7f47f30 Lock file maintenance (#1975)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 02:38:44 +00:00
FooIbar 5650e6ccfe Fix tagger back handler 2024-11-24 19:52:57 +08:00
a0d3706497 Refine TagDatabase Suggestions (#1973)
* Refine rawSuggestSequence

* Lift

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine naming

* Refine naming

* Update app/src/main/kotlin/com/hippo/ehviewer/client/EhTagDatabase.kt

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-11-24 09:27:32 +00:00
FooIbar 0013ef42bd Fix crash when page URL's index is out of bound 2024-11-24 15:03:05 +08:00
Dude so hot 8da5f53856 Just override hue 2024-11-24 14:22:47 +08:00
renovate[bot]andDude so hot d0c150d419 Redesign Vote Status (#1970)
* Redesign Vote Status

* Revert "Redesign Vote Status"

This reverts commit 00295652f8eab9f91c1ef0ec1356e8eff838aaef.

* Redesign Vote Status

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-24 05:57:45 +00:00
f52b828fe1 Refine Tagger Dialog (#1969)
* Refine Tagger Dialog

* Fix limit

* Suggestions use sequence

* Update app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

* Refine

* Refine

* Refine

* Naming

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-11-23 16:29:52 +00:00
8babadc33e Implement Tagger suggestion (#1968)
* Implement Tagger suggestion

* Refine suggestFlow impl

* Refine suggest

* Revert

* Refine display

* Refine

* Refine

* Refine

* Refine

* Update app/src/main/kotlin/com/hippo/ehviewer/client/EhTagDatabase.kt

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

* Refine

* Refine

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-11-23 15:19:56 +00:00
7047de4959 Initial User Friendly Tag Inputer (#1966)
* Initial User Friendly Tag Inputer

* Address code review

* Update translation

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-11-23 10:52:30 +00:00
renovate[bot] 9b5e286b90 Update dependency io.arrow-kt:arrow-stack to v2.0.0-beta.3 (#1967)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-23 09:27:24 +00:00
renovate[bot]andDude so hot 99d98b6227 Support display tag vote status (#1965)
* Support parsing GalleryTag vote status

* Append utf-8 emoji vote status

* Refresh tag list after voting

* Refine to use substringAfter

* Fix implicitNulls

* Refine to use withDraw vote

* Recompose first

* Optimize Snackbar to be cancellable for freq action

* Fix GalleryDetail recompose jump

* Refine VoteStatus display

* Support parse WeakStatus Active

* Append instead

* Add switch

* Refine naming

* Fix fillInfo

* Drop correctly

* Refine

* Refine rememberInVM impl with key

* Be type safe

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-23 07:44:23 +00:00
FooIbar 03a343784d Log Cronet errors before restarting app 2024-11-21 15:01:50 +08:00
Dude so hot 046350c0f9 Bump ArrowKt to 2.0.0-beta.2 2024-11-21 14:22:03 +08:00
renovate[bot] a1f267916e Update dependency gradle to v8.11.1 (#1959)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 21:46:50 +00:00
Dude so hot a4b0c1fe36 TightRope tolerant cancellation for 200ms 2024-11-20 21:49:55 +08:00
renovate[bot] 6aa0878480 Fix animated image (#1956)
* Fix animated image

* Update PagerItem.kt
2024-11-20 09:51:53 +00:00
FooIbar 897a56fa4a Fix rewrite GIF to actually match browser behavior 2024-11-20 14:51:38 +08:00
renovate[bot] 86a1e405b9 Update Kotlin & KSP 2024-11-20 08:18:33 +08:00
renovate[bot] e33fdaf74d Fix crossfade 2024-11-20 02:34:07 +08:00
renovate[bot]andDude so hot a554982f08 Refine MergeInterceptor to not cancel current job when awaiter exists (#1950)
* Refine MergeInterceptor to not cancel current job when awaiter exists

* Refine

* Cleanup abstraction

* Cleanup abstraction

* Refine

* Refine

* Fix MutableSharedFlow skip f when no awaiters

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-19 18:09:27 +00:00
renovate[bot]andDude so hot 553fa9245c Refine Thumb Preload Strategy (#1948)
* PrefetchAround for GalleryList

* Drop Preload Aggressively

* Refine

* Revert "Drop Preload Aggressively"

This reverts commit 0a8d5d083b509fb95c13c7674eca81f2770a92ab.

* Cleanup

* Fix

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-19 14:59:36 +08:00
renovate[bot]andDude so hot f862013c6a Prefetch around v2 preview image (#1947)
* Drop unused field in GalleryDetail

* Let GalleryPreview url prefetchDistance just be pageSize

* Prefetch around GalleryPreview image

* Optimize

* Optimize

* Refine semantics

* Perform decode for +-20 prefetch

* Refine

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-19 05:50:20 +00:00
renovate[bot]andDude so hot 7dd87c1ff6 Workaround to not invalidate shared element modifier chain (#1944)
* Try to fix SET

* Drop check

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-18 21:58:45 +08:00
renovate[bot] 900da46090 Lock file maintenance (#1943)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 01:45:10 +00:00
Dude so hot 7d54b78521 ColorFilterSetting use RollingNumber 2024-11-17 18:59:14 +08:00
Dude so hot ccc7d467c2 Refine RollingNumber to animate negative when fixed length 2024-11-17 18:59:14 +08:00
FooIbar 8b6c358266 Avoid consuming click event if the image is loaded 2024-11-16 18:53:50 +08:00
renovate[bot] 444388e432 Update dependency io.coil-kt.coil3:coil-bom to v3.0.3 2024-11-16 14:28:24 +08:00
FooIbar b4c00d866c Play safe 2024-11-16 14:21:52 +08:00
Dude so hot b9606fcb49 Fix image jumping 2024-11-14 23:18:55 +08:00
Dude so hot ab6dfa0f08 Fix image recycle racing 2024-11-14 23:02:49 +08:00
FooIbar 81ee3c47f5 Fix reading UTF-8 flagged ZIP archives on API < 26 2024-11-14 21:36:03 +08:00
renovate[bot] 7a59f821cf Update dependency me.saket.telephoto:zoomable to v0.14.0 (#1933)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 11:30:50 +00:00
renovate[bot]andDude so hot 6f7276bcdf Revert "Temporarily disable shared element transitions (#1621)" (#1932)
* Revert "Temporarily disable shared element transitions (#1621)"

This reverts commit 42ed51094e.

* Fixes

* Update EhAsyncThumb.kt

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-14 19:24:11 +08:00
renovate[bot]andDude so hot 2cd884bd9d Hack zip_time to fix performance (#1931)
* Hack zip_time to fix performance

* Revert

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-14 06:33:13 +00:00
renovate[bot] 40f4554373 Update dependency androidx.compose:compose-bom-alpha to v2024.11.00 (#1930)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 03:12:22 +00:00
renovate[bot] 76321d3491 Update dependency androidx.activity:activity-compose to v1.10.0-beta01 (#1928)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 22:29:30 +00:00
renovate[bot] c3b5db18a7 Update dependency androidx.paging:paging-compose to v3.3.4 (#1929)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 22:28:56 +00:00
renovate[bot] 71afc90b0e Update androidx.lifecycle to v2.9.0-alpha07 (#1926)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 20:05:49 +00:00
renovate[bot] c03d08b9f4 Update benchmark to v1.4.0-alpha05 (#1927)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 20:05:34 +00:00
renovate[bot] b9d8931f3e Update dependency chrome to v131 (#1925)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-12 19:59:48 +00:00
renovate[bot]andDude so hot ec8bd2fc14 Add rust parser test (#1924)
* Add rust parser test

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Update cargo lock

Signed-off-by: Dude so hot <djohn@fbi.gov>

---------

Signed-off-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-12 08:31:49 +00:00
renovate[bot]andDude so hot dd1bbf1226 Ensure PageLoader not closed until decoder jobs completed (#1923)
* Optimize to use autoCloseScope

* Refine log

* Log

* Remove debug logging

* Refine

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-12 12:15:29 +08:00
renovate[bot] c2c86ff58e Update dependency gradle to v8.11 (#1922)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 17:02:39 +00:00
FooIbar 2d2840c1bb Prepare for next iteration 2024-11-11 21:14:59 +08:00
FooIbar 571969fca9 Prepare for release 1.13.1 2024-11-11 21:14:29 +08:00
FooIbar 16fa52790c Misc cleanups (#1921)
* Clean up resources

* Clean up code
2024-11-11 20:39:45 +08:00
FooIbar c88c252587 Update README (#1920)
[no ci]
2024-11-11 17:19:43 +08:00
FooIbar 253cb03de8 Refine AndroidFileSystem exception handling 2024-11-11 17:19:17 +08:00
renovate[bot] 0b74adc13b Lock file maintenance (#1917)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 01:17:16 +00:00
FooIbar 6fc2328475 Fix false positive download failures on API 28 2024-11-11 00:52:12 +08:00
renovate[bot] e9c4201acc Update dependency io.coil-kt.coil3:coil-bom to v3.0.2 (#1912)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-10 04:10:41 +00:00
FooIbar 9edb52c909 Remove desktop site option 2024-11-10 00:21:53 +08:00
FooIbar f4c7320bb5 Remove search cover feature due to upstream change 2024-11-09 22:54:07 +08:00
FooIbar 17581ca204 Use desktop User-Agent by default
Since WebP resamples have smaller file size.
2024-11-09 20:56:14 +08:00
FooIbar 11edb8ce5d Error out on cover search for V2 galleries 2024-11-09 20:35:09 +08:00
FooIbar 40caa7b1eb Prefer to get page tokens from MPV 2024-11-09 19:44:12 +08:00
FooIbar e3f4c24983 Extend update download request timeout 2024-11-09 13:59:35 +08:00
renovate[bot]andDude so hot dd0482cb26 Allow ExchangeSite on Http 500 (#1905)
* Allow ExchangeSite on Http 500

* Fixup

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-09 13:38:23 +08:00
FooIbar 4052007de8 Implement retry on click for thumbnails 2024-11-09 11:27:10 +08:00
FooIbar 85b03d9d8a Propagate errors from writeComicInfo to callers 2024-11-08 16:06:55 +08:00
renovate[bot] 36c3fd6f5d Update dependency com.google.devtools.ksp to v2.1.0-RC-1.0.27 (#1902)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 07:15:42 +00:00
renovate[bot] d397a5d35e Update dependency com.android.tools:desugar_jdk_libs to v2.1.3 2024-11-07 09:59:04 +00:00
renovate[bot] 43d83ad919 Update dependency io.coil-kt.coil3:coil-bom to v3.0.1 2024-11-07 09:58:36 +00:00
FooIbar e71dd1d576 Improve scrollbar a11y 2024-11-06 23:44:48 +08:00
renovate[bot] 72e28b8e04 Update dependency com.google.devtools.ksp to v2.1.0-RC-1.0.26 2024-11-05 20:05:19 +00:00
renovate[bot] 3432a15c62 Update dependency io.github.pdvrieze.xmlutil:serialization to v0.90.3 2024-11-05 09:46:27 +00:00
renovate[bot]andDude so hot 45a5bae8b2 Group rust ffi code (#1893)
* Group rust ffi code

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Fix

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Fix

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

---------

Signed-off-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-05 09:39:03 +00:00
a566393a89 Optimize to use decode buffer pool (#1890)
* Optimize to use decode buffer poll

* Drop unnecessary logging

* Refine

* Update app/src/main/cpp/archive.c

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-11-05 05:01:15 +00:00
renovate[bot] 5f2f3f9122 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0 2024-11-04 22:53:10 +00:00
renovate[bot] c7fccf7e70 Update Kotlin & KSP to v2.1.0-RC 2024-11-04 20:24:35 +00:00
FooIbar 3d92d061f1 Fix archive size check 2024-11-04 21:15:07 +08:00
Dude so hot f2759675a1 Optimize to not allocate anon mapping when fully zero copy 2024-11-04 21:12:45 +08:00
Dude so hot 2e6ff78662 Fast fix for operator precedence 2024-11-04 20:57:10 +08:00
renovate[bot]andDude so hot 8ca0a32932 Cleanup to not handle memory allocation failure (#1882)
* Cleanup to not handle memory allocation failure

* Handle open failed on first open

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-04 12:06:38 +00:00
FooIbar 72fbcd4e0e Refine Cronet workaround 2024-11-04 18:43:16 +08:00
a5719a9c1d Optimize zero copy case to not iter over entries (#1879)
* Optimie zero copy case to not iter over entries

* Update app/src/main/cpp/archive.c

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-11-04 09:03:41 +00:00
Re*Index. (ot_inc) bad41d5027 Change funds text (#1878)
* Fix Japanese

* Change Funds text
2024-11-04 08:49:11 +00:00
renovate[bot] 3bfb95f190 Lock file maintenance 2024-11-04 00:09:54 +00:00
renovate[bot]andDude so hot f04695feb6 Refresh Avatar Url when load failed (#1873)
* Refresh Avatar Url when load failed

* Check 404

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-03 15:17:50 +00:00
renovate[bot]andDude so hot 94da911f93 Fix DrawerContent ColorScheme
* Fix listItemOnDrawerColor()

* Fix topBarOnDrawerColor

* Refine location

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-11-02 04:51:06 +00:00
Re*Index. (ot_inc) f4771d4ddb Fix Japanese 2024-11-02 11:06:44 +08:00
Re*Index. (ot_inc) 9f7c4adb0f Make Japanese README & Fix Japanese (#1867)
* Update Japanese

* Add Japanese README

* Update ja-jp.md

* Update ja-jp.md

* Update ja-jp.md

* Update ja-jp.md

* Update ja-jp.md

* Update ja-jp.md

* fix text & suffix.

* Update ja.md

* Update ja.md

* Update ja.md

* Update ja.md

* Add Minimum Android Version text
2024-11-01 23:41:58 +08:00
FooIbar 6fb20fc6c1 [no ci] Update README 2024-11-01 21:55:55 +08:00
FooIbar ce8e91aab8 Prepare for next iteration 2024-11-01 21:03:47 +08:00
FooIbar fe4ab6fdab Prepare for release 1.13.0 2024-11-01 21:03:04 +08:00
FooIbar e60b6922c3 Prevent coil from adding cache-control headers
ConnectivityChecker is not reliable when used with VPN on old platforms.
2024-11-01 19:23:14 +08:00
Re*Index. (ot_inc) d3d2c907ad Update & Fix Japanese strings 2024-11-01 17:03:25 +08:00
FooIbar 84306e0b28 Use shallow clone to fetch git dependencies 2024-11-01 12:44:47 +08:00
FooIbar f7c2148d5f Update description of "view original image" 2024-11-01 12:05:40 +08:00
FooIbar 46e5a0e121 Update renovate config 2024-11-01 11:39:34 +08:00
renovate[bot] 2f5b52f12c Update AGP to v8.8.0-alpha09 2024-10-31 22:58:11 +00:00
renovate[bot] 20f9120583 Update plugin com.android.settings to v8.8.0-alpha09 2024-10-31 22:55:48 +00:00
FooIbar e65428cae1 Use SecureTextField for password 2024-10-31 23:51:26 +08:00
Dude so hot e5862e807c Fix to not add padding manually 2024-10-31 23:08:53 +08:00
Dude so hot 911fb3cae0 Update to use bracketCase 2024-10-31 22:17:45 +08:00
d76821f6a6 Unify decode phase to PageLoader (#1848)
* Make PageLoader size RAII

* Unify decode phase to PageLoader

* Fixes

* Fixes

* Fixes

* Refine

* Refine name

* Fixes

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Revert

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Invalidate page cache on retryPage

* Value class

* Cancel bounds takes IntRange

* SpiderQueen awaitReade spread exception

* Reorder

* Fix

* Fix

* Refine

* Split install procress

* Interface default method

* Revert

* Revert

* Fix to clear error state

* Update app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader.kt

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

* Refine

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-10-31 12:30:52 +00:00
renovate[bot] 1d5a680a5b Update dependency androidx.work:work-runtime to v2.10.0 2024-10-31 01:19:56 +00:00
renovate[bot] 2f38e17fd6 Update dependency androidx.core:core-ktx to v1.15.0 2024-10-31 01:19:11 +00:00
renovate[bot] 7de4bea5f7 Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.0 2024-10-31 00:30:00 +00:00
renovate[bot] 35db8baa2a Update dependency androidx.compose:compose-bom-alpha to v2024.10.01 2024-10-31 00:29:50 +00:00
renovate[bot] e774dd3a6c Update benchmark to v1.4.0-alpha04 2024-10-30 21:13:16 +00:00
renovate[bot] f3619c6b43 Update dependency androidx.activity:activity-compose to v1.10.0-alpha03 2024-10-30 21:12:43 +00:00
renovate[bot] b807081f9d Update androidx.room to v2.7.0-alpha11 2024-10-30 19:03:46 +00:00
renovate[bot] 0807d7f1ce Update androidx.lifecycle to v2.9.0-alpha06 2024-10-30 19:03:18 +00:00
FooIbar 8b6894716b Remove fixed ktor workaround 2024-10-30 23:21:00 +08:00
Dude so hot cb733d8e38 Move ktlint to stable 2024-10-30 23:11:19 +08:00
FooIbar d9c7daebea Fix save image filename for external archives 2024-10-30 23:00:34 +08:00
FooIbar bbe8fd6b8f Use .editorconfig to disable ktlint rule (#1844) 2024-10-30 13:58:53 +00:00
Dude so hot 8f8fe513fa Cleanup ctor 2024-10-30 20:30:53 +08:00
Dude so hot 8a4df1f2ba Disable ktlint_standard_class-signature 2024-10-30 20:30:53 +08:00
Dude so hot dacbf5edb3 Fix thumb extension for webp 2024-10-30 19:08:52 +08:00
Dude so hot 4e145f46db Demagic DownloadThumbInterceptor 2024-10-30 19:08:52 +08:00
renovate[bot] 53dc622376 Update dependency io.ktor:ktor-client-okhttp to v3.0.1 2024-10-29 23:37:20 +00:00
renovate[bot] aab8fabb1f Disable SaveBodyPlugin & fix non-redirect Ktor client defaults 2024-10-29 16:21:19 +00:00
FooIbar 86bb240633 Disable Cronet on some incompatible devices 2024-10-29 23:41:40 +08:00
Dude so hot c6b3aec1af Fix build 2024-10-29 23:35:59 +08:00
Dude so hot e6c4b32a47 Use defaultRequest 2024-10-29 23:35:59 +08:00
Dude so hot b71e83047d Install UA for ktor client 2024-10-29 23:35:59 +08:00
Dude so hot 8609336240 Move to use BlackholeDecoder.Factory() 2024-10-29 23:35:59 +08:00
FooIbar ded11a81a5 Refine cache key for v2 gallery preview 2024-10-29 21:17:45 +08:00
renovate[bot]andDude so hot 0c00ea9292 Refactor PageLoader (#1830)
* Cleanup PageLoader

* Refactor ReaderScreen

* Refactor EhPageLoader

* Refine

* Refine

* Refine

* Refine

* Refine

* Don't inline

* Fix recompose

* Merge PageLoader

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-29 08:10:52 +00:00
FooIbar 672b53d8a9 Migrate to settings plugin and update NDK to r27c 2024-10-29 15:27:08 +08:00
FooIbar bc5032b2d0 Fix off-screen fetcher jobs blocking decode queue 2024-10-29 06:56:24 +08:00
renovate[bot] 4cefa439ec Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-rc02 2024-10-28 21:26:43 +00:00
FooIbar 4d2b7b3ad0 Adapt to the upcoming thumbnail changes (#1823) 2024-10-28 22:55:27 +08:00
FooIbar 58f85cc436 Clean up or parameter as it's no longer required 2024-10-28 20:14:52 +08:00
FooIbar 5c50d7622b Unify normal and small thumbnails parsing 2024-10-28 20:14:52 +08:00
renovate[bot] e90b31bf69 Update Rust crate quick-xml to 0.37 2024-10-28 00:48:23 +00:00
renovate[bot] b43d60138b Lock file maintenance 2024-10-28 00:47:49 +00:00
FooIbar 9f4faebfe6 Use ktor plugin for download timeout 2024-10-28 00:00:48 +08:00
FooIbar b6f84d8de6 Group ad utils and fix a main thread IO 2024-10-27 22:58:15 +08:00
FooIbar 230eec7c59 Revert "Block AD in Large preview (#1694)"
This reverts commit 04ee1a63

Reason for revert: Obsoleted by the upcoming changes
2024-10-27 22:58:15 +08:00
renovate[bot]andDude so hot 3b9cfefd5f Fix Small Preview CrossFade not work (#1816)
* Fix Small Preview CrossFade not work

* Abstract

* Update MergeInterceptor.kt

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-27 12:01:53 +00:00
FooIbar c1b701f1f9 Only check file integrity for images served by H@H
URLs returned by non-H@H servers may not contain file hash for resampled
images.
2024-10-27 16:10:47 +08:00
FooIbar 4744caaacc Show search warnings (if any) when no hits found 2024-10-27 15:48:46 +08:00
FooIbar b58912462a Make image limits description unambiguous 2024-10-27 12:36:56 +08:00
FooIbar f3cadf594d Fix crash on devices without WallpaperService 2024-10-27 11:30:37 +08:00
FooIbar a8834ee21c Fix menu click glitch after pager type change 2024-10-26 22:18:52 +08:00
FooIbar 0c59403e49 Add support for arrow key navigation (#1808)
* Add support for arrow key navigation

* Refactor key event handler to use Compose KeyEvent

Also removed volume key configurations.

* Fix focus requester
2024-10-26 12:54:29 +00:00
FooIbar 431a771672 Relax regex for the upcoming WebP adoption 2024-10-26 13:54:16 +08:00
Dude so hot b043b06208 Refine TagNamespace 2024-10-26 10:45:23 +08:00
renovate[bot] abccf713df Update AGP to v8.8.0-alpha08 2024-10-25 19:21:42 +00:00
renovate[bot] 4b83f6f28f Update dependency com.google.devtools.ksp to v2.1.0-Beta2-1.0.26 2024-10-25 19:19:06 +00:00
renovate[bot]andDude so hot e843a547a5 Make GalleryTagGroup immutable & non-empty (#1798)
* Make GalleryTagGroup immutable & non-empty

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-25 15:18:12 +00:00
小白-白 40d9bfa30b Fix thumb parsing when labels are enabled 2024-10-25 22:00:53 +08:00
小白-白 69669f0a50 Fix archive download 2024-10-25 21:04:00 +08:00
Dude so hot 4c20d27d33 Optimize regex with Chatgpt 2024-10-25 18:34:00 +08:00
小白-白 06eb277b34 Fix preview parser 2024-10-25 16:18:52 +08:00
renovate[bot] 86120277c7 Update dependency androidx.work:work-runtime to v2.10.0-rc01 2024-10-24 18:16:09 +00:00
renovate[bot]andDude so hot 62927cb6a8 Kill Legacy TextView (#1786)
* Kill Legacy TextView

* Fix textStyle

* Fix color

* Add breakToTextAndUrl

* Impl breakToTextAndUrl

* Fix OOB

* Update GalleryCommentsScreen.kt

* Fix bug

* Fix color

* Refine regex

* Add undeline as well

* Refine

* Revert

* Don't break if not show image

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-24 23:38:27 +08:00
renovate[bot]andDude so hot 22f31f2ebc Remember pageloader in ViewModel (#1780)
* Remember pageloader in ViewModel

* Fix hang on

* Refine

* Move ***InVM functions to ViewModelState

* Add rememberInVM overload takes no key

* Support asyncInVM & launchInVM with key

* Move UNCHECKED_CAST code

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-23 21:57:28 +08:00
FooIbar c8cfaeecf6 Clean up java.nio APIs usage to avoid desugaring 2024-10-23 18:03:45 +08:00
FooIbar 714d334067 Misc cleanup 2024-10-23 15:20:43 +08:00
FooIbar ca324995b3 Clean up ExceptionUtils with Either.catch 2024-10-23 15:20:43 +08:00
FooIbar f301eab5b1 Fix preview list not recomposing on detail refresh 2024-10-23 11:27:04 +08:00
FooIbar 87ac60a3ab Clean up GalleryListUrlParser/Builder 2024-10-22 22:52:36 +08:00
FooIbar a2f89f07fa Use e-hentai thumbnail server by default 2024-10-22 21:40:21 +08:00
renovate[bot]andDude so hot 57d7d2b798 Kill Switch (#1778)
* Kill Switch

* Refine comment

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-21 13:32:01 +00:00
Dude so hot 6f3bc25b64 Don't apply NonCancellable to H@H large images 2024-10-21 20:54:37 +08:00
Dude so hot 62477dac87 Fix NamedSemaphore naming 2024-10-21 20:54:37 +08:00
Dude so hot 0dfee513f4 Lock concurrency for Normal previews as well 2024-10-21 20:54:37 +08:00
Dude so hot 31f97cb840 Introduce NamedSemaphore 2024-10-21 20:54:37 +08:00
Dude so hot 93e2d36dbb Share semaphore 2024-10-21 20:54:37 +08:00
Dude so hot aed3d08167 Fetch from h1 server is NonCancellable 2024-10-21 20:54:37 +08:00
Dude so hot 9c5f11dd9d Fix step 2024-10-21 20:54:37 +08:00
Dude so hot 1af7aa8a16 Allow configure thumb concurrency temporarily 2024-10-21 20:54:37 +08:00
Dude so hot cf6f793478 Limit thumb fetcher concurrency 2024-10-21 20:54:37 +08:00
Dude so hot 145948f506 Add HttpClientConfig<T>.configureCommon 2024-10-21 20:54:37 +08:00
FooIbar b068492d10 Refine caching configuration in CI 2024-10-21 12:13:03 +08:00
renovate[bot] 8c94d39ae4 Lock file maintenance 2024-10-21 01:43:19 +00:00
Dude so hot c584d72f41 Rotate hue ourselves 2024-10-20 22:31:16 +08:00
Dude so hot cfd248f818 Support WallPaper DynamicColor on O_MR1 above 2024-10-20 22:31:16 +08:00
Dude so hot b9e59ee2f8 Harmonize with Kolor functions 2024-10-20 22:31:16 +08:00
Dude so hot 439899fbdc Use Expressive MotionScheme 2024-10-20 22:31:16 +08:00
Dude so hot 177c1889fa Import material-kolor 2024-10-20 22:31:16 +08:00
FooIbar ebce847724 Update OkHttp and configure async DNS 2024-10-20 19:32:26 +08:00
renovate[bot]andDude so hot c6b6d9a310 Fallback to Okhttp instead (#1771)
* Fallback to Okhttp instead

* Drop unused proguard

* Exclude okhttp3 public suffix

* Refine

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-20 09:21:14 +00:00
Dude so hot 6601a45472 GP divide by 1k 2024-10-19 22:53:37 +08:00
Dude so hot d66fb9a783 RollingNumber support separator 2024-10-19 22:53:37 +08:00
Dude so hot c244369875 Apply RollingNumber to quota 2024-10-19 22:53:37 +08:00
Dude so hot f9c812864e Apply RollingNumber to gp & c 2024-10-19 22:53:37 +08:00
Dude so hot 87d0cdd9e5 Fix to measure RollingNumber with +inf maxHeight 2024-10-19 22:53:37 +08:00
Dude so hot d237a18aae Refine Naming 2024-10-19 21:36:40 +08:00
Dude so hot 0159cf30ae NamedMutex shares MutexPool 2024-10-19 21:36:40 +08:00
Dude so hot 6aba1670ec Refine NamedMutex 2024-10-19 21:36:40 +08:00
Dude so hot 447a31ea00 Restore default MaxConnPerRoute 2024-10-19 21:36:40 +08:00
FooIbar 9e410a61ef Pin Rust toolchain version to 1.81.0
1.82.0 has a severe binary size regression (+36%)
2024-10-19 21:27:34 +08:00
FooIbar 4588b7eaa5 Refine resources packing options 2024-10-19 15:23:24 +08:00
FooIbar aef55c9207 Use Cargo.lock for reproducible builds 2024-10-19 15:23:24 +08:00
renovate[bot] 9a587e9dd2 Update plugin composeCompilerReportGenerator to v1.4.1 2024-10-19 06:40:15 +00:00
renovate[bot] cbe834c71d Update Kotlin & KSP 2024-10-19 08:46:37 +08:00
FooIbar 020495cebf Fix comparison with leading zeros and clean up 2024-10-19 00:31:38 +08:00
FooIbar 54d3cccb67 Fix AnchoredDraggable fling behavior 2024-10-19 00:29:47 +08:00
renovate[bot] b4adf57f39 Update compose.destinations to v2.1.0-beta14 2024-10-18 13:34:21 +00:00
b796dbcde3 Partially Reland "Use Apache5 as fallback HTTP engine" (#1755)
* Use Apache5 as fallback HTTP engine

* Fix R8

* Depolymorphism CronetEngine context

* Conditionally insert conscrypt

* Limit IO thread cnt to 1

* Don't package compose destination as well

* Refine

* Add an option for debugging HTTP engines

---------

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-18 10:18:46 +00:00
FooIbar ba2c69690f Revert "Use Apache5 as fallback HTTP engine (#1752)"
This reverts commit c3b60f1eac.
2024-10-18 16:16:36 +08:00
FooIbarandDude so hot c3b60f1eac Use Apache5 as fallback HTTP engine (#1752)
* Use Apache5 as fallback HTTP engine

* Fix R8

* Depolymorphism CronetEngine context

* Drop Conscrypt, don't enforce anything

* Limit to 1 io thread count

* Fix H2 support below A10

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-18 15:57:42 +08:00
renovate[bot] 2bb387eeaf Update AGP to v8.8.0-alpha07 2024-10-17 22:50:11 +00:00
FooIbar fd67c733aa Always upload R8 reports 2024-10-18 00:17:44 +08:00
renovate[bot]andDude so hot eac7048360 Optimise non-fixed length RollingNumber (#1751)
* Try Optimise non-fixed length RollingNumber

* Apply RollingNumber to SliderPref

* Revert Debug

* RollingNumber handle negative value

* Fix typo

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-17 21:54:20 +08:00
renovate[bot]andDude so hot 34de943c9f Introduce RollingNumberPlaceholder (#1750)
* Introduce RollingNumberPlaceholder, sync page index double end style

* Cleanup

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-17 19:27:41 +08:00
renovate[bot] 302b43c9e8 Update dependency sh.calvin.reorderable:reorderable to v2.4.0 2024-10-17 09:02:20 +00:00
FooIbar 07b3917c4e Best effort for devices without DocumentsUI 2024-10-17 16:55:44 +08:00
FooIbar 1ace248121 Allow resetting to default download location 2024-10-17 16:55:44 +08:00
renovate[bot]andDude so hot 7a96feb049 Rolling number POC, convert page index (#1740)
* Rolling number POC, convert page index

* Refine

* Improve

* Comments

* Refine Comments

* Bigger number is from top

* Prepare to make content moveable

* Make content index reversed

* Use moveableContentOf

* Increase interval

* Fix calculation

* Fix calculation again

* Extract

* Refine

* Use zero-sized placeholder

* Fix width

* Allow RollingNumber have fixed width

* Improve comments

* Make moveableContent initialized lazily

* Reverse direction

* Avoid movableContentOf if width fixed

* Use Arrangement.spacedBy

* Improve animation

* Refine

* Fix comment

* Fix padding

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-17 06:07:49 +00:00
renovate[bot]andrenovate[bot] 5df1d099a1 Update dependency androidx.compose:compose-bom-alpha to v2024.10.00 (#1746)
* Update dependency androidx.compose:compose-bom-alpha to v2024.10.00

* Update Theme.k

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <99822064+revonateB0T@users.noreply.github.com>
2024-10-17 00:30:48 +00:00
renovate[bot] ed2db49cdb Update dependency androidx.core:core-ktx to v1.15.0-rc01 2024-10-17 00:23:24 +00:00
renovate[bot] 7b2c99357e Update androidx.lifecycle to v2.9.0-alpha05 2024-10-17 00:16:37 +00:00
renovate[bot] 4868c40795 Update androidx.room to v2.7.0-alpha10 2024-10-17 00:16:10 +00:00
renovate[bot]andrenovate[bot] 27ff17db50 Update benchmark to v1.4.0-alpha03 (#1743)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <99822064+revonateB0T@users.noreply.github.com>
2024-10-17 00:15:47 +00:00
renovate[bot] c15a52eb2a Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.0-rc01 2024-10-16 23:56:52 +00:00
FooIbar faa54dedb1 Migrate to BTF2
This reverts commit 8a29d46b
2024-10-16 11:14:54 +08:00
renovate[bot] 59bdb4b2f0 Update dependency io.github.pdvrieze.xmlutil:serialization to v0.90.2 2024-10-15 20:23:09 +00:00
renovate[bot] 4990d757ae Update dependency chrome to v130 2024-10-15 20:22:40 +00:00
FooIbar 5f71c3dcb4 Update Compose Material3 to 1.4 2024-10-15 23:58:56 +08:00
Dude so hot 1892a750b7 Address Code Review 2024-10-15 21:15:38 +08:00
Dude so hot 020f853bb1 MergeInterceptor use NamedMutex 2024-10-15 21:15:38 +08:00
Dude so hot 9ccddae60a Introduce Better NamedMutex 2024-10-15 21:15:38 +08:00
Dude so hot b7a2d93f03 Optimize rememberInVM allocation 2024-10-15 19:08:12 +08:00
Dude so hot 2fc8b421b0 Optimize collection access 2024-10-15 19:08:12 +08:00
Dude so hot 180466674f Optimize statesMap to use mutableIntObjectMapOf 2024-10-15 19:08:12 +08:00
Dude so hot ac23a4312d Address CR 2024-10-15 19:08:12 +08:00
Dude so hot aaea83b932 Minor cleanup GalleryDetailScreen 2024-10-15 19:08:12 +08:00
Dude so hot 8425ea0b2f Move SpiderDen file cache to use rwlock 2024-10-15 19:08:12 +08:00
Dude so hot 100a6569b7 Use rwlock 2024-10-15 13:40:02 +08:00
Dude so hot e8c4b9ab78 Ensure PageLoader cache sync 2024-10-15 13:03:18 +08:00
Dude so hot eff5467ccf Cleanup CrashHandler 2024-10-15 13:03:18 +08:00
Dude so hot 769308c9f0 Remove obsolete 2024-10-15 13:03:18 +08:00
Dude so hot e1de57c98c Ensure QrCodeInterceptor cache thread-safe 2024-10-15 13:03:18 +08:00
Dude so hot 9c6a5af87e Ensure FavouriteStatusRouter thread-safe 2024-10-15 13:03:18 +08:00
Dude so hot 9d88577108 Move to SieveCache 2024-10-15 13:03:18 +08:00
renovate[bot]andrenovate[bot] dbebc5956f Update AGP to v8.8.0-alpha06 (#1728)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <99822064+revonateB0T@users.noreply.github.com>
2024-10-15 02:30:35 +00:00
renovate[bot]andDude so hot 52ca5a7eb8 SpotlessAPply (#1729)
Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-10-15 02:19:56 +00:00
FooIbar 78b898dc5c Reduce build noise 2024-10-14 20:48:13 +08:00
Dude so hot 1e81157d5c Update VectorPainterCache to use MutableScatterMap 2024-10-14 13:27:49 +08:00
Dude so hot 5cff3db9b5 Drop unused local variable 2024-10-14 13:27:49 +08:00
Dude so hot 25fe80f252 Drop DismissDialogScope wrapper 2024-10-14 13:27:49 +08:00
Dude so hot 413bb2cd82 No longer need write permission as we copy magnet now 2024-10-14 13:27:49 +08:00
renovate[bot] b867bbc7a6 Lock file maintenance 2024-10-14 03:23:13 +00:00
Dude so hot 3bf6f763aa Optimise DialogState with value class 2024-10-13 20:48:24 +08:00
2c0d621b16 Add avatar (#1720)
* Load Avatar

* Click Avatar to show image limit

* Shared Limit among collection

* Cleanup and configure heartbeat

* Always refresh avatars

* Refine icons

* Avoid heartbeat

* Fix mistake

* No need to SharingStarted.WhileSubscribed

* Address code review

* Simply map error msg

* Convert limitFlow to stateFlow

* Fixes

* Refine retry logic

* Don't retry automatically

* Don't cache error result

* Animate quota changes

* Logout from guest mode via avatar

* No need to navigate explicitly as needSignIn is observed

* Make PostLogin not suspend

* More UX friendly

* Fix progressIndicator not disappear

* Use Either.catch.mapLeft

* Refine

* Add overload for awaitConfirmationOrCancel

* Log error with snackbar

* Refine

* Fix to use collectLatest

* Refine

* Refine

* Address cr

* Refine

* Animated loading

* Refine

* Fix

* Fix

* Refine image limits dialog

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-10-13 19:36:02 +08:00
renovate[bot] e930ebf42f Update dependency libarchive to v3.7.7 2024-10-13 10:41:48 +00:00
Dude so hot 4e3862b1c5 Move ImageSearchScreen 2024-10-12 21:21:14 +08:00
Dude so hot e3ffb9a473 Spilt ImageSearch from GalleryListScreen 2024-10-12 21:21:14 +08:00
FooIbar e485d79bb1 Drop torrent download in favor of magnet links 2024-10-12 15:26:37 +08:00
renovate[bot] 49edbb8185 Update compose.destinations to v2.1.0-beta13 2024-10-10 13:58:06 +00:00
renovate[bot] 32f7407752 Update dependency io.ktor:ktor-client-core to v3.0.0 2024-10-09 17:15:48 +00:00
renovate[bot]andFooIbar aef02d34a5 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-rc01 (#1713)
* Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-rc01

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-10-08 11:24:43 +00:00
renovate[bot] bb2f9bb309 Lock file maintenance 2024-10-07 07:48:01 +00:00
FooIbar 140b6528a6 Fix ktlint snapshot 2024-10-07 15:40:23 +08:00
FooIbar fa3cf650ea Pin Compose Material3 version to 1.3 2024-10-05 16:11:48 +08:00
renovate[bot] cfe2cff74c Update AGP to v8.8.0-alpha05 2024-10-03 22:08:36 +00:00
renovate[bot] 1def52e41a Update dependency androidx.compose:compose-bom-alpha to v2024.09.03 2024-10-03 19:18:58 +00:00
renovate[bot] cc4eab3244 Update dependency io.ktor:ktor-client-core to v3.0.0-rc-2 2024-10-03 09:08:35 +00:00
renovate[bot] 4327e48c1f Update dependency androidx.profileinstaller:profileinstaller to v1.4.1 2024-10-03 00:25:35 +00:00
renovate[bot] c8c077c14b Update dependency androidx.work:work-runtime to v2.10.0-beta01 2024-10-03 00:25:19 +00:00
renovate[bot] f82221552b Update benchmark to v1.4.0-alpha02 2024-10-02 21:08:39 +00:00
renovate[bot] bfe0aad951 Update dependency androidx.core:core-ktx to v1.15.0-beta01 2024-10-02 21:08:05 +00:00
renovate[bot] 2f5d8edb03 Update androidx.lifecycle to v2.9.0-alpha04 2024-10-02 19:09:56 +00:00
renovate[bot] ac0acafff9 Update androidx.room to v2.7.0-alpha09 2024-10-02 19:09:27 +00:00
renovate[bot] 9333150c3b Update dependency liblzma to v5.6.3 2024-10-01 18:41:54 +00:00
renovate[bot] 0c71c6856b Update dependency chrome to v129 2024-10-01 05:59:07 +00:00
FooIbar 6a47a8a1e1 Use renovate to update Chrome version 2024-10-01 13:51:58 +08:00
renovate[bot]andDude so hot 04ee1a6362 Block AD in Large preview (#1694)
* Cleanup with `getOrPut`

* Refine `ArchivePageLoader` naming

* Move `PageLoader`

* Bump ktlint to fix when guards

* Extract `AdsPlaceholder`

* Detect QrCode for LargePreview

* Conditional detect

* Fixes

* Fixes

* Cleanup code

* Fix render

* Fixup hardware bitmap

* Original size to detect

* Log QrCode detect exception

* Don't mutate cached

* Refine ListUrlBuilder

* Move functions

* Enable preview adblocker for debug build only

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-09-30 21:09:56 +08:00
renovate[bot] 9ead29643d Lock file maintenance 2024-09-30 00:22:01 +00:00
FooIbar d8e7bd4ccc Prepare for next iteration 2024-09-28 22:57:59 +08:00
FooIbar dd2949638c Prepare for release 1.12.1 2024-09-28 22:57:17 +08:00
FooIbar f71aa4902b Prepare changelog for 1.12.1 (#1691)
[no ci]
2024-09-28 22:56:20 +08:00
FooIbar af7e411932 Refine GalleryMultiPageViewerPTokenParser 2024-09-28 00:01:21 +08:00
FooIbar c54ac1a4c0 Fix crash when download location is invalid 2024-09-27 19:57:47 +08:00
renovate[bot] 00d6a2e403 Update AGP to v8.8.0-alpha04 2024-09-27 02:08:47 +00:00
FooIbar b67d0938b0 Reapply "Add scroll bar to quick search and download label"
This reverts commit a52ab28534.
2024-09-26 19:12:46 +08:00
FooIbar 8447760cb5 Fix image download queue not respecting FIFO order 2024-09-25 22:58:36 +08:00
FooIbar 24d2072586 Use Downloads as start screen if device is offline (#1675) 2024-09-25 03:54:34 +00:00
renovate[bot] a52ab28534 Revert "Add scroll bar to quick search and download label"
This reverts commit 601713d8d7.
2024-09-25 08:25:04 +08:00
FooIbar 601713d8d7 Add scroll bar to quick search and download label 2024-09-25 07:38:20 +08:00
FooIbar 7bfa5be53c Fix concurrent creation of the same directory 2024-09-24 21:36:42 +08:00
renovate[bot] ae3a66903a Update dependency gradle to v8.10.2 2024-09-24 02:16:59 +00:00
renovate[bot] 5438fd5d9b Update AGP to v8.8.0-alpha03 2024-09-23 21:59:33 +00:00
FooIbar a74ffca03d Fix galleries with both artist and cosplayer tags
Legacy issue of E-Hentai's tag namespacing changes.
2024-09-24 00:38:24 +08:00
renovate[bot] c5a83d567e Update dependency libarchive to v3.7.6 2024-09-23 13:40:44 +00:00
FooIbar 1b1f781dd9 Update renovate config 2024-09-23 21:32:34 +08:00
renovate[bot] 988195e4fc Lock file maintenance 2024-09-23 00:49:24 +00:00
FooIbar 4d4124458d Fix reader prefetch when scroll direction changes 2024-09-22 16:19:24 +08:00
FooIbar 0ae2843676 Actually fix data import on Android V
This reverts commit 26921c8a
2024-09-21 23:10:50 +08:00
FooIbar beaddc9eeb Fix foreign key constraint violation on db import 2024-09-21 23:10:50 +08:00
FooIbar d843b3a14b Try to reduce false positives in ad detection 2024-09-21 20:05:01 +08:00
Dude so hot b9da0eda8b Cleanup 2024-09-20 20:01:49 +08:00
Dude so hot ba75cda036 Move navigation to use compose Rect 2024-09-20 20:01:49 +08:00
Dude so hot 9d59af29c5 Singleton Navigation 2024-09-20 20:01:49 +08:00
FooIbar 660d6e32f1 Cleanup 2024-09-20 19:50:13 +08:00
FooIbar c07c663105 Address deprecation 2024-09-20 19:50:13 +08:00
FooIbar 3673f6f7f9 Rename awaitPermissionOrCancel -> awaitConfirmationOrCancel 2024-09-20 19:50:13 +08:00
renovate[bot]andDude so hot e8120e12d0 ADT Page model (#1655)
* Enable when guards

* ADT Page model

* Refine

* Refine

* Remove type conversion

* Cleanup

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-09-20 16:19:59 +08:00
renovate[bot] f7459886b2 Update kotlinx.serialization to v1.7.3 2024-09-19 20:07:38 +00:00
FooIbar 6fe5319c08 Update Kotlin & KSP to 2.1.0-Beta1 2024-09-19 23:57:48 +08:00
FooIbar 3a42208a36 Merge GalleryPreviewScreen to GalleryDetailScreen (#1649) 2024-09-19 23:39:42 +08:00
FooIbar e4eacce5bf Update Compose BOM 2024-09-19 23:02:04 +08:00
FooIbar d1fdea8d3e Update libarchive to v3.7.5 2024-09-19 22:57:38 +08:00
98e452e39e Fastpath to create regioned hardware bitmap (#1647)
* kotlin side

* Impl

* Refine

* respect ahb align

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Don't copy unnecessary things

* Cleanup

* Recycle

* Revert "Disable `abortOnError` for lint in debug build"

This reverts commit cea06f0c

* Fix marshmallow build

* Fix cmake

* Address cr

* Refine

* Cleanup

* log it

---------

Signed-off-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-09-19 14:25:55 +00:00
FooIbar 02e8fcc534 Fix lint
This reverts commit cea06f0c
2024-09-19 21:54:28 +08:00
renovate[bot]andDude so hot 70852c2725 Crop bitmap before draw (#1646)
* Crop bitmap before draw

* Spilt

* Refine

* Cleanup

* Address code review

* Address code review

* Address code review

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-09-19 07:56:24 +00:00
renovate[bot] ea67d6e078 Update benchmark to v1.4.0-alpha01 2024-09-18 22:15:18 +00:00
renovate[bot] d2aa3b5347 Update androidx.room to v2.7.0-alpha08 2024-09-18 22:14:40 +00:00
renovate[bot] 937792ba91 Update dependency androidx.work:work-runtime to v2.10.0-alpha04 2024-09-18 18:53:24 +00:00
renovate[bot] fcfdaf59f8 Update dependency androidx.lifecycle:lifecycle-process to v2.9.0-alpha03 2024-09-18 17:22:46 +00:00
renovate[bot] 0d4da998a1 Update dependency androidx.profileinstaller:profileinstaller to v1.4.0 2024-09-18 17:21:32 +00:00
FooIbar 0e7a32d9af [Marshmallow] Add ISRG Root X1 as a trusted root 2024-09-18 17:54:41 +08:00
FooIbar 4bb6b0baf6 Switch to rxing for QR code detection 2024-09-16 17:54:14 +08:00
renovate[bot] 0591b3224f Lock file maintenance 2024-09-16 00:10:00 +00:00
renovate[bot]andDude so hot eaf8dd21ff Crop border in Rust (#1631)
* Fix deref_mut_direct_bytebuffer lifetime

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Framework

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Add Infra

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Implement

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Impl

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Fixes

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Implement

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Update profile code

* Remove corrent but slow code that slow for 1000x

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Revert "Remove corrent but slow code that slow for 1000x"

This reverts commit 644d093ddc20f4a129ee304157767707337d149d.

* Fix speed

Signed-off-by: Dude so hot <djohn@fbi.gov>

* refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

* refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

* cleanup type conversion

Signed-off-by: Dude so hot <djohn@fbi.gov>

* cleanup

Signed-off-by: Dude so hot <djohn@fbi.gov>

* refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Remove C cropborder impl

* impl nth to accel

Signed-off-by: Dude so hot <djohn@fbi.gov>

* F**K

Signed-off-by: Dude so hot <djohn@fbi.gov>

---------

Signed-off-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-09-15 22:20:55 +08:00
Dude so hot 9bc6cccf26 buffer share same lifetime with jnienv
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot eb3b53ee5b Format
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot 731af586af Add safety hint for parse_marshal_inplace
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot 3acb70b8e1 Add safety hint for deref_mut_direct_bytebuffer
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot 836b1841e3 Add safety hint for with_bitmap_content
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot d37f4580a9 Rename to avoid clash
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot 70fc964b4d Fold get_bitmap_handle
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot c3c5fbfc79 Extract with_bitmap_content
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot 04103f9280 Extract detect_image_ad
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
Dude so hot 69370efcf5 Modularize QrCode
Signed-off-by: Dude so hot <djohn@fbi.gov>
2024-09-14 20:08:51 +08:00
renovate[bot]andDude so hot f0d3b0c8ae Misc updates (#1629)
* Respect Arch's ndk location

* Don't spam in debug build

* Cleanup

* Revert "Cleanup"

This reverts commit 45b9d76dd363aa1fd3d58b5102b68649028e313a.

* Revert "Respect Arch's ndk location"

This reverts commit 3fd773422d3ae440a6466e2fa161a45018fd8ed7.

* Respect Arch ndk location

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
2024-09-14 10:45:36 +00:00
FooIbar 370c485048 Update NDK to r27b and address deprecation 2024-09-14 17:07:04 +08:00
FooIbar 7e4c910cd7 Fix blocked image placeholder content scale 2024-09-14 16:06:09 +08:00
renovate[bot] e8c1c80ce5 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.9.0 2024-09-13 16:20:18 +00:00
0d55f319c9 Fuckoff External Ads (#1617)
* Add Rust implementation to detect QRCode from bitmap

* Add QRCodeInterceptor Framework

* Let image take hasQRCode info

* Keep ReaderPage index internal

* Move imageCache to use ReaderPage as key

* Make PageLoader size & pages internal

* Refine

* Kill entry if QRCode

* Rewrite request to respect index mapping

* Debug: enable by default

* Fix crash

* Fixup

* Fix identity

* Revert to lruCache to avoid crash

* Fixup

* Clippy check for android only

* Fixup

* Resolve clippy

* Switch

* Apply switch

* Restart pageloader restore pages

* Cleanup

* Cleanup

Signed-off-by: Dude so hot <djohn@fbi.gov>

* let it inference

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Don't detect border for ads

* Make coil interceptor runs in `Dispatchers.Default`

* Revert "Restart pageloader restore pages"

This reverts commit 8fd02fb7d9c677c119df72a767a6079f1899dc31.

* Revert drop page changes

* Use backGround color painter for ads

* Only check ads for tags contains extraneous ads

* Only check ads for head 5 images || tail 10 images

* Move strip ADs switch to AdvancedScreen

* Pick placeholder

* Show Placeholder

* Don't try to extract & decode

Signed-off-by: Dude so hot <djohn@fbi.gov>

* cleanup

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Naming

* Naming

* Naming

* Naming

* For ArchivePageLoader as well

* Hint

* Fix flicker

* Refine

* Naming

* QR -> Qr

* Naming

* QR -> Qr

* Naming

* Refine

* Revert

* Remove header check

* Allow show ads

* Adjust

* Refine

* Revert "Don't try to extract & decode"

This reverts commit f35ef46f6ffc81485cf0f4c646ed79e524c29d32.

Signed-off-by: Dude so hot <djohn@fbi.gov>

* be strict

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Just Extract, don't decode

* Refine

* Refine placeholder

---------

Signed-off-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-09-13 16:10:05 +00:00
FooIbar 42ed51094e Temporarily disable shared element transitions (#1621) 2024-09-13 12:10:26 +08:00
renovate[bot] 84c3fee5da Update dependency sh.calvin.reorderable:reorderable to v2.4.0-beta01 2024-09-13 00:33:59 +00:00
renovate[bot] b3af2c9481 Update AGP to v8.8.0-alpha02 2024-09-12 22:42:21 +00:00
renovate[bot] 33cab186fd Update dependency com.squareup.okio:okio-jvm to v3.9.1 2024-09-12 22:40:33 +00:00
FooIbar e3f52412ee Replace custom user agent with desktop site option 2024-09-12 20:22:54 +08:00
080810c707 Update dependency io.ktor:ktor-client-core to v3.0.0-rc-1 (#1424)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <99822064+revonateB0T@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-09-10 01:22:52 +00:00
renovate[bot] 964e827cbe Update AGP to v8.8.0-alpha01 2024-09-10 01:10:26 +00:00
FooIbar cae02acfc4 Fix cosplayer tag translation in download label 2024-09-10 06:11:25 +08:00
FooIbar 31ce4eac92 Update description for IP-based image limit 2024-09-09 22:13:49 +08:00
renovate[bot] bd5bd1e571 Implement Shared Element Transition Connector Framework (#1614)
undefined
2024-09-09 21:46:07 +08:00
renovate[bot] 2b4a6dc57a Update dependency gradle to v8.10.1 2024-09-09 10:25:31 +00:00
FooIbar 68932731b9 Fix gallery detail losing scroll position 2024-09-09 18:14:48 +08:00
renovate[bot] aab7ba0d53 Lock file maintenance 2024-09-09 01:22:24 +00:00
renovate[bot] 59bf4f2702 Update compose.destinations to v2.1.0-beta12 2024-09-08 22:47:54 +00:00
FooIbar e0e4b739f9 Add key and contentType for gallery detail 2024-09-08 22:04:12 +08:00
FooIbar 21d6b3bbb8 [no ci] Fix renovate config 2024-09-07 18:13:20 +08:00
FooIbar dae5d67515 [no ci] Fix renovate config 2024-09-07 17:59:56 +08:00
FooIbar a6a814bbbb [no ci] Update renovate config 2024-09-07 16:27:23 +08:00
FooIbar 0b0555af9d Use singleton empty WindowInsets 2024-09-07 16:13:54 +08:00
FooIbar 6e0d77a8ab Fix unfinished files being considered downloaded 2024-09-07 16:13:40 +08:00
FooIbar 5c06a69c8a Revert "Update agp to v8.7.0-beta01"
This reverts commit 63ec1aa217.
2024-09-06 14:25:38 +08:00
FooIbar 188a236a90 Fix reader padding and progress indicator theme 2024-09-06 10:00:21 +08:00
renovate[bot] 63ec1aa217 Update agp to v8.7.0-beta01 2024-09-05 21:38:21 +00:00
renovate[bot] 829316d8b9 Update dependency com.google.devtools.ksp to v2.0.20-1.0.25 2024-09-05 21:36:55 +00:00
renovate[bot] 337739fdad Update dependency androidx.profileinstaller:profileinstaller to v1.4.0-rc01 2024-09-05 14:44:41 +00:00
FooIbar f144ac701d Refine BitmapPainter 2024-09-05 22:36:19 +08:00
FooIbar 9c9b8b74cd Fix crash on random button click with empty list 2024-09-05 20:53:00 +08:00
renovate[bot] 154c7f3710 Update dependency com.google.accompanist:accompanist-drawablepainter to v0.36.0 2024-09-05 03:38:28 +00:00
renovate[bot] d3308bc987 Update dependency androidx.activity:activity-compose to v1.10.0-alpha02 2024-09-05 01:56:06 +00:00
renovate[bot] 04d9ee57d7 Update dependency androidx.core:core-splashscreen to v1.2.0-alpha02 2024-09-05 01:45:16 +00:00
FooIbar f3eecf18fb Switch to official Compose alpha BOM
This reverts commit c76440f167.
2024-09-05 09:45:04 +08:00
renovate[bot] 6591495d2d Update dependency androidx.work:work-runtime to v2.10.0-alpha03 2024-09-05 01:43:42 +00:00
renovate[bot] 2497835628 Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.0-beta01 2024-09-04 22:35:38 +00:00
renovate[bot] 8fad9b2abe Update androidx.lifecycle to v2.9.0-alpha02 2024-09-04 22:34:46 +00:00
renovate[bot] fdd0ec9fd2 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.2 2024-09-04 10:52:07 +00:00
FooIbar 1219ff99ff Reset page state on page loader restart 2024-09-03 19:12:01 +08:00
FooIbar c60b14998a Fix crash when drawing > 100 MB bitmaps 2024-09-03 19:03:54 +08:00
renovate[bot] 380554bd19 Lock file maintenance 2024-09-02 01:56:54 +00:00
FooIbar 2b969030da Fix recent download label can't be set to null
This reverts commit 822a461acd.
2024-09-01 20:56:01 +08:00
github-actions[bot] da917de1f4 Update Chrome version for default User-Agent 2024-09-01 13:34:46 +08:00
renovate[bot] 541f9e89f3 Update agp to v8.7.0-alpha09 2024-08-30 15:14:11 +00:00
renovate[bot] f4a987c83f Update agp to v8.7.0-alpha08 2024-08-28 19:45:33 +00:00
renovate[bot] 37474ed6ac Update kotlinx.serialization to v1.7.2 2024-08-28 19:43:12 +00:00
renovate[bot] cb69a168f3 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.1 2024-08-28 13:34:26 +00:00
FooIbar 94366553de Fix torrent count parsing
This partially reverts commit 634269c6a4.
2024-08-27 07:37:30 +08:00
FooIbar 383332bfbb Fix account-based image limit parsing (#1565)
* Fix account-based image limit parsing

* Fix format
2024-08-27 07:37:05 +08:00
renovate[bot] 4bb1a17d39 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.0 2024-08-26 17:43:40 +00:00
renovate[bot] 94d68fe38b Lock file maintenance 2024-08-26 00:56:27 +00:00
renovate[bot] cf6c850d2a Update aboutlibraries to v11.2.3 2024-08-23 11:06:34 +00:00
renovate[bot] c8a3a0fc20 Update dependency sh.calvin.reorderable:reorderable to v2.4.0-alpha02 2024-08-23 01:22:29 +00:00
renovate[bot] 433896b267 Update dependency com.google.devtools.ksp to v2.0.20-1.0.24 2024-08-22 23:11:02 +00:00
renovate[bot] 7e9c1f00ef Update Kotlin & KSP to v2.0.20 2024-08-22 10:56:10 +00:00
renovate[bot] 2b5b0d695a Update dependency me.saket.telephoto:zoomable to v0.13.0 2024-08-22 03:41:35 +00:00
renovate[bot] 2265b0943e Update dependency androidx.core:core-ktx to v1.15.0-alpha02 2024-08-22 01:09:20 +00:00
renovate[bot] 78f12065ab Update dependency dev.chrisbanes.compose:compose-bom to v2024.08.00-alpha02 2024-08-21 22:04:09 +00:00
renovate[bot] f28c45a13a Update dependency androidx.profileinstaller:profileinstaller to v1.4.0-beta01 2024-08-21 22:03:53 +00:00
renovate[bot] e030500917 Update benchmark to v1.3.0 2024-08-21 21:01:47 +00:00
renovate[bot] 9c042cd323 Update androidx.room to v2.7.0-alpha07 2024-08-21 21:01:30 +00:00
FooIbar 44eea7a273 Adapt status bar content color for reader theme 2024-08-21 15:20:25 +08:00
FooIbar 834cfc5e8c Support scrolling with mouse wheel in reader 2024-08-21 15:20:16 +08:00
renovate[bot] 876bb68873 Update dependency org.jetbrains.kotlinx:kotlinx-datetime to v0.6.1 2024-08-20 17:10:58 +00:00
FooIbar 40c1b7ef21 Prepare for next iteration 2024-08-19 21:02:30 +08:00
FooIbar 6191ad74d3 Prepare for release 1.12.0 2024-08-19 21:01:58 +08:00
FooIbar 5aae8f5649 Fix thumbnail resolution setting not working 2024-08-19 17:45:39 +08:00
FooIbar 6886465afb Refine reader
Fix navigation when switching from `WebtoonViewer` to `PagerViewer`.

Hide navigation overlay and reset slider to follow pager on click.
2024-08-19 17:45:26 +08:00
FooIbar 5a46fc4573 Don't recycle visible images 2024-08-19 13:49:36 +08:00
renovate[bot] a08b8e96e5 Lock file maintenance 2024-08-19 01:05:11 +00:00
FooIbar 5aeded7a24 Don't show load state indicator when no hits found 2024-08-18 17:40:48 +08:00
FooIbar 1883b82159 Fix pager unclickability when image is not loaded 2024-08-18 17:40:37 +08:00
FooIbar 302b57d4ac Fix normal preview in exhentai
Remove force e-hentai thumb option due to infeasible.
2024-08-17 00:52:44 +08:00
FooIbar c6b793923e Temporarily disable shared element transitions 2024-08-16 20:20:13 +08:00
FooIbar 058c280fd7 Avoid out-of-screen shared element transitions 2024-08-16 19:45:19 +08:00
renovate[bot] 0fa42d731b Update agp to v8.7.0-alpha07 2024-08-16 00:45:15 +00:00
FooIbar 9b54b57934 Refactor reader prefetch logic (#1523)
* Refactor reader prefetch logic

* Refine
2024-08-16 00:46:44 +08:00
FooIbar d3ab3ba61f [no ci] Update workflow 2024-08-15 18:29:41 +08:00
renovate[bot] 921f570151 Update dependency gradle to v8.10 2024-08-14 12:46:28 +00:00
FooIbar 03ab435401 Fix preload in reader (#1517)
* Fix preload in reader

* Reduce preload page count for ArchivePageLoader
2024-08-14 12:58:06 +08:00
renovate[bot] b73704ede1 Update dependency com.google.devtools.ksp to v2.0.20-RC2-1.0.24 2024-08-14 01:37:30 +00:00
renovate[bot] 97b2181ff7 Update agp to v8.7.0-alpha06 2024-08-13 19:11:16 +00:00
renovate[bot] ef5e3f7c51 Update Kotlin & KSP to v2.0.20-RC2 2024-08-13 13:41:34 +00:00
FooIbar 8a29d46b38 Revert^3 "Update Compose snapshot and migrate to BTF2 (#1474)"
This reverts commit 22b5a175f4.
2024-08-13 18:54:38 +08:00
FooIbar 75d8ff4b64 Fix ComicInfo.xml for galleries with temp tags 2024-08-13 17:46:01 +08:00
FooIbar 33258ea554 Fix read progress insert crash when exiting reader 2024-08-13 17:40:21 +08:00
renovate[bot] a723b4ecd3 Lock file maintenance 2024-08-12 00:15:22 +00:00
renovate[bot] 78a237fb0e Update agp to v8.7.0-alpha05 2024-08-09 02:18:54 +00:00
renovate[bot] 8fe60c0163 Update dependency dev.chrisbanes.compose:compose-bom to v2024.08.00-alpha01 2024-08-08 06:19:26 +00:00
renovate[bot] ffe47b2ff2 Update dependency androidx.lifecycle:lifecycle-runtime-compose to v2.9.0-alpha01 2024-08-08 03:14:46 +00:00
renovate[bot] e05fffb41c Update dependency androidx.biometric:biometric-ktx to v1.4.0-alpha02 2024-08-08 03:14:00 +00:00
renovate[bot] 1fb5de2527 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha10 2024-08-08 00:40:20 +00:00
renovate[bot] 46790a1fbe Update dependency androidx.profileinstaller:profileinstaller to v1.4.0-alpha02 2024-08-08 00:39:56 +00:00
renovate[bot] af1316f06b Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.0-alpha14 2024-08-07 22:11:39 +00:00
renovate[bot] 24564cac8d Update dependency androidx.paging:paging-compose to v3.3.2 2024-08-07 22:11:14 +00:00
renovate[bot] c4868f137b Update benchmark to v1.3.0-rc01 2024-08-07 20:38:52 +00:00
renovate[bot] 3ccfa0dd5b Update androidx.room to v2.7.0-alpha06 2024-08-07 20:38:26 +00:00
FooIbar fded0fd08d Fix GitHub authorization and clean up code 2024-08-07 18:37:51 +08:00
renovate[bot] b1afe9bcd1 Update agp to v8.7.0-alpha04 2024-08-06 22:54:54 +00:00
FooIbar 22b5a175f4 Reapply "Update Compose snapshot and migrate to BTF2 (#1474)"
This reverts commit 94b138f254.
2024-08-06 23:01:20 +08:00
FooIbar ee9e0df12e [no ci] Fix release workflow 2024-08-06 22:03:30 +08:00
FooIbar dd525baf88 Use encoded token to avoid detection & revocation (#1492) 2024-08-06 21:33:46 +08:00
FooIbar 9180603ead Fix a division by zero error in ScrollbarAdapter 2024-08-06 21:30:12 +08:00
renovate[bot] 66fe19369e Lock file maintenance 2024-08-05 00:38:20 +00:00
renovate[bot] b9db025f57 Update gradle/actions action to v4 2024-08-04 00:54:28 +00:00
FooIbar 44f6eca3aa Fix main thread IO (really) and float comparison 2024-08-03 14:55:05 +08:00
FooIbar 1ff1caf421 Fix list item container color
Also compile debug build with SDK 35 as sources for SDK 35 have been
available.
2024-08-03 08:42:33 +08:00
github-actions[bot] ec1355c0e7 Update Chrome version for default User-Agent 2024-08-01 11:21:29 +08:00
FooIbar d23a453f71 Don't let navigation overlay consume click event
So it won't take two actions to dismiss overlay & app bar.
2024-08-01 07:25:47 +08:00
FooIbar 94b138f254 Revert "Update Compose snapshot and migrate to BTF2 (#1474)"
This reverts commit abcef9a86e.
2024-08-01 03:06:53 +08:00
FooIbar c281daa4ea Update NDK to r27 2024-08-01 01:42:33 +08:00
FooIbar 4f9eba8c63 Update Kotlin and remove workaround 2024-08-01 01:08:41 +08:00
FooIbar a2547f1e8d Update page range filter limit 2024-07-31 18:54:51 +08:00
FooIbar abcef9a86e Update Compose snapshot and migrate to BTF2 (#1474)
* Update Compose snapshot and remove workaround

* Sync SearchBarInputField with upstream

* Migrate to BTF2 text fields

* Fix search page range error text container color

* Update snapshot to fix a SieveCache crash

https://r.android.com/3199791
2024-07-31 17:40:09 +08:00
FooIbar 7d6a9b10e4 Fix crash when opening archives simultaneously 2024-07-30 16:27:16 +08:00
renovate[bot] 9322b80790 Update dependency com.google.devtools.ksp to v2.0.0-1.0.24 2024-07-30 00:51:18 +00:00
FooIbar e12897bd13 Fix layout issue with comments that contain images 2024-07-29 21:59:40 +08:00
FooIbar 28279cc671 Fix page transitions when reading with volume keys 2024-07-29 18:19:43 +08:00
FooIbar 2531fa289b Update dependency sh.calvin.reorderable:reorderable to v2.4.0-alpha01 2024-07-29 10:47:13 +08:00
renovate[bot] 9673163e8f Lock file maintenance 2024-07-29 00:55:33 +00:00
renovate[bot] 3f7091f880 Update dependency sh.calvin.reorderable:reorderable to v2.3.0 2024-07-28 20:01:11 +00:00
FooIbar 5df75fd534 Don't save baseline profile in sources (#1462)
* Don't save baseline profile in sources

* Fixup
2024-07-28 17:25:36 +00:00
FooIbarandGG Bond 62061e54ee Add shared element transitions (#1049)
* Add shared element transitions

* Fixup CodeStyle

* Add a overload for `Modifier.sharedBounds`

* Add TransitionsVisibilityScope to hold multiple transition

* advance AnimatedVisibilityScope to TransitionsVisibilityScope

* composing use advance

* Correctly remember

* Convert to use `Set`

* share SharedContentState

* Refine

* Cleanup

* Cleanup

* Update snapshot

* Temporarily disable navigation transition

* Fix build

* Make `TransitionsVisibilityScope` a value class

* Naming

* Use `implicit` instead

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-07-28 14:11:47 +00:00
github-actions[bot] 436c07fb6b Update baseline profile 2024-07-28 21:34:06 +08:00
FooIbar a16f873f7d Fix crash when switching reading mode 2024-07-28 21:15:09 +08:00
FooIbar 98fdcf0588 Fix double free on closing failed-to-open archives 2024-07-28 20:48:56 +08:00
renovate[bot] 539ad1af30 Fix read encrypted archive 2024-07-28 12:05:04 +00:00
FooIbar 4370606977 [Reader] Show progress indicator & fix background 2024-07-28 19:45:18 +08:00
FooIbar 1ecdc07655 Graduate Compose Reader to stable 2024-07-28 17:29:50 +08:00
FooIbar 01a1113a07 [New Reader] Avoid initial scroll on reader start 2024-07-28 17:14:07 +08:00
renovate[bot] fd6c2977b0 Update plugin composeCompilerReportGenerator to v1.4.0 2024-07-28 08:50:51 +00:00
FooIbar 02e36e7021 [New Reader] Implement open archive intent 2024-07-28 14:00:23 +08:00
FooIbar 7f8e4aa003 [New Reader] Hide menu on scroll 2024-07-28 14:00:23 +08:00
FooIbar 8ce16bc918 [New Reader] Implement navigation overlay (#1445)
* [New Reader] Implement navigation overlay

* Fix typo

* Refine

* Reuse navigation regions
2024-07-26 23:45:41 +08:00
renovate[bot] fbe35a0fa0 Update agp to v8.7.0-alpha03 2024-07-25 23:43:05 +00:00
renovate[bot] 931f1ec514 Update dependency dev.chrisbanes.compose:compose-bom to v2024.07.00-alpha02 2024-07-25 17:31:31 +00:00
renovate[bot] 7c4a59e9a4 Update compose.destinations to v2.1.0-beta11 2024-07-25 13:33:39 +00:00
FooIbar c1c6f25763 [New Reader] Refine landscape zoom (#1442) 2024-07-25 18:53:16 +08:00
renovate[bot] e7f515c70f Update dependency androidx.paging:paging-compose to v3.3.1 2024-07-24 19:48:04 +00:00
renovate[bot] 64fecbfa52 Update dependency androidx.lifecycle:lifecycle-process to v2.8.4 2024-07-24 17:18:31 +00:00
renovate[bot] c6c181a9fa Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha09 2024-07-24 22:05:22 +08:00
FooIbar 53a1bd9959 [New Reader] Implement landscape zoom 2024-07-24 20:36:43 +08:00
FooIbar 7b1f94cf5a [New Reader] Implement custom image scale types 2024-07-24 09:02:45 +08:00
renovate[bot] 4b8ecbf6a7 Update agp to v8.7.0-alpha02 2024-07-22 21:19:13 +00:00
renovate[bot] 0e0b0519e7 Lock file maintenance 2024-07-22 02:12:22 +00:00
FooIbar 7fb3b7c785 Tweak baseline profile generation schedule
[no ci]
2024-07-21 15:00:14 +08:00
renovate[bot]andFooIbar e18ebb1feb Update compose.destinations to v2.1.0-beta10 (#1435)
* Update compose.destinations to v2.1.0-beta10

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-07-21 06:30:57 +00:00
FooIbar f76dcfc97b [New Reader] Misc improvements 2024-07-21 08:30:32 +08:00
renovate[bot] c0c630bd9f Update dependency me.saket.telephoto:zoomable to v0.12.1 2024-07-20 09:00:29 +00:00
FooIbar 04df7da5dc Remove obsolete workaround 2024-07-17 17:38:01 +08:00
FooIbar d156638a79 Fix comment screen refresh indicator padding 2024-07-17 17:33:44 +08:00
FooIbar c78fe0b72c [New Reader] Implement touch navigation 2024-07-17 17:32:08 +08:00
renovate[bot] 799c37ea71 Update dependency sh.calvin.reorderable:reorderable to v2.2.0 2024-07-17 05:42:34 +00:00
renovate[bot] 9559ca571b Update agp 2024-07-15 19:40:24 +00:00
renovate[bot] 2f5c1e11c6 Lock file maintenance 2024-07-15 01:07:35 +00:00
renovate[bot] 734c73fa4d Update dependency me.saket.telephoto:zoomable to v0.12.0 2024-07-13 19:51:02 +00:00
renovate[bot] c09173d62c Update dependency com.google.devtools.ksp to v2.0.0-1.0.23 2024-07-11 21:35:55 +00:00
renovate[bot] 533f50ed08 Update dependency gradle to v8.9 2024-07-11 18:03:51 +00:00
Dude so hot b4422fe910 Drop MAP_UNINITIALIZED
This is not available on modern MMU devices
2024-07-11 21:31:34 +08:00
Dude so hot 22e58a034f Fix memleak while do archive 2024-07-11 21:12:31 +08:00
FooIbar 784856d341 Revert "Update agp to v8.6.0-beta01"
This reverts commit b982705b2f.
2024-07-11 08:45:00 +08:00
renovate[bot] f82832ca36 Update dependency dev.chrisbanes.compose:compose-bom to v2024.07.00-alpha01 2024-07-11 00:15:30 +00:00
renovate[bot] 6a70aa792e Update benchmark to v1.3.0-beta02 2024-07-10 19:10:50 +00:00
renovate[bot] a0016dbf85 Update androidx.room to v2.7.0-alpha05 2024-07-10 19:10:30 +00:00
renovate[bot] b982705b2f Update agp to v8.6.0-beta01 2024-07-10 17:01:40 +00:00
FooIbar 4c36398bed Prepare for next iteration 2024-07-10 22:13:25 +08:00
FooIbar 7e1b3abcf6 Prepare for release 1.11.7 2024-07-10 22:12:56 +08:00
FooIbar 97bb65aa35 Fix search bar title flickering 2024-07-10 22:11:46 +08:00
FooIbar 98c1a377ca Fix main thread disk IO 2024-07-10 20:25:52 +08:00
FooIbar 2d2ce3281e Fix Path -> Uri conversion for paths contain '%'
This reverts commit ab2e4a8e2d.
2024-07-10 19:44:13 +08:00
renovate[bot] a28d3ba840 Update dependency org.jsoup:jsoup to v1.18.1 2024-07-10 08:41:55 +00:00
FooIbar ab2e4a8e2d Fix Path -> Uri conversion
No need to do it for '?' as that's not allowed in filename.
2024-07-10 12:59:29 +08:00
FooIbar 67ddba87b5 Fix workflow config
[no ci]
2024-07-09 22:29:28 +08:00
FooIbar 8164847fcb Prepare for next iteration 2024-07-09 22:03:40 +08:00
FooIbar 85b538bbd3 Prepare for release 1.11.6 2024-07-09 22:03:04 +08:00
FooIbar d00d700a4b Improve Path/Uri conversion 2024-07-09 21:51:41 +08:00
github-actions[bot] ae66318843 Update baseline profile 2024-07-09 19:15:53 +08:00
FooIbar 1b9aa6a762 Retrieve favorite notes when modifying favorites 2024-07-09 18:54:57 +08:00
renovate[bot]andFooIbar 306ce83de4 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha08 (#1397)
* Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha08

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-07-09 07:01:49 +00:00
FooIbar 89f9735ab9 Update Rust edition to 2021 2024-07-09 11:07:10 +08:00
FooIbar 843e1cc6a0 Fix MediaStore DATE_ADDED/DATE_MODIFIED
They are in seconds not milliseconds as per the documentation.
2024-07-09 10:55:34 +08:00
FooIbar 1a53d0d207 Fix download notification not showing 2024-07-09 10:18:22 +08:00
renovate[bot] 45c02d4fd6 Update Rust crate quick-xml to 0.36 2024-07-08 20:06:06 +00:00
renovate[bot] 862dee0845 Lock file maintenance 2024-07-08 01:53:46 +00:00
FooIbar 809bbfeba7 Refine clean redundancy dialog 2024-07-08 00:12:23 +08:00
FooIbar 959c740175 AndroidFileSystem POC (#1383)
* AndroidFileSystem POC

* Cache image file paths

* Refine
2024-07-07 07:13:49 +00:00
FooIbar a718510c89 Implicitly add language filter tag
This partially reverts commit 673c1837e9.
2024-07-06 15:02:55 +08:00
FooIbar f7c2fd8af3 Renew GitHub token 2024-07-05 21:12:46 +08:00
FooIbar c76440f167 Address deprecation 2024-07-05 20:57:47 +08:00
FooIbar c3bd959ee5 Update Compose snapshot (#1381)
* Update Compose snapshot

https://issuetracker.google.com/344633766

* Fix search bar text field padding
2024-07-04 13:34:27 +00:00
FooIbar 0a25643b87 Fix native crash on archive open failures 2024-07-04 20:24:37 +08:00
FooIbar c805ca8aff Revert "Reuse the root download location UniFile object"
Reason for revert: Caused OOM.
2024-07-03 22:34:18 +08:00
FooIbar 699a1ab801 Refactor DownloadService to use flows 2024-07-03 19:59:12 +08:00
FooIbar ee091aee0f Rescan download directory when restoring downloads (#1372) 2024-07-02 05:50:20 +00:00
FooIbar 673c1837e9 Fix language filter being applied to quick search 2024-07-02 13:44:01 +08:00
FooIbar 3998913c17 Fix existing download dirname not being used (#1371)
Tweak clean redundancy logic and add a confirm dialog.
2024-07-02 13:41:31 +08:00
renovate[bot] 1e83d7128e Update androidx.lifecycle to v2.8.3 2024-07-01 17:29:06 +00:00
FooIbar 355289a576 Avoid relying on implementation details (#1357)
* Avoid relying on implementation details

* Refine

* Fixup

* Extract common logic of RawFile & TreeDocumentFile

* Reuse the root download location UniFile object
2024-07-01 20:00:13 +08:00
FooIbar ba3a41f0ee Limit search bar text to single line 2024-07-01 17:17:38 +08:00
github-actions[bot] fc6d0bd6ef Update Chrome version for default User-Agent 2024-07-01 14:26:38 +08:00
FooIbar 15ef0ae362 Exclude scrollbar area from the system gesture 2024-07-01 14:26:15 +08:00
renovate[bot] a598f8562d Lock file maintenance 2024-07-01 01:28:33 +00:00
github-actions[bot] 2860fed5ab Update baseline profile 2024-06-30 11:07:14 +08:00
FooIbar f247544439 Migrate to CMP's implementation of scrollbar 2024-06-30 11:06:12 +08:00
renovate[bot] fdc3d94d5c Update Rust crate quick-xml to 0.35 2024-06-29 20:08:16 +00:00
renovate[bot] afc2ff7478 Update dependency io.github.petertrr:kotlin-multiplatform-diff to v0.7.0 2024-06-29 15:38:04 +00:00
renovate[bot] 83072d4eee Update aboutlibraries to v11.2.2 2024-06-29 12:13:33 +00:00
renovate[bot] 7e7be8d40a Update agp to v8.6.0-alpha08 2024-06-27 21:42:03 +00:00
FooIbar 90d7555333 [no ci] Fix benchmark build type configuration 2024-06-27 14:48:55 +08:00
github-actions[bot] ba268cfe6d Update baseline profile 2024-06-27 12:37:32 +08:00
renovate[bot]andFooIbar 0a7a9beb84 Update dependency androidx.test.uiautomator:uiautomator to v2.4.0-alpha01 (#1354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-06-27 04:09:18 +00:00
renovate[bot] b18b89db29 Update dependency androidx.test.ext:junit to v1.2.1 2024-06-27 03:54:12 +00:00
FooIbar cb2d99bdc1 Add automated baseline profile generation (#1349)
* Add automated baseline profile generation

* Create benchmark build type

And remove the `enabledRules` argument as it will be injected by
baseline profile gradle plugin.
2024-06-27 11:46:02 +08:00
renovate[bot]andFooIbar 4dd6d68163 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha07 (#1351)
* Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha07

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-06-27 03:44:45 +00:00
renovate[bot] 28ecc9d479 Update dependency dev.chrisbanes.compose:compose-bom to v2024.06.00-alpha01 2024-06-26 23:16:46 +00:00
renovate[bot] c917117db3 Update kotlinx.serialization to v1.7.1 2024-06-25 17:19:22 +00:00
FooIbar 5c0d65dd60 Fix crop borders not working 2024-06-25 15:17:21 +08:00
FooIbar dfc59aa643 Target API 35 as it has reached platform stability
Still use 34 SDK in debug build as sources for 35 are not available yet.
2024-06-25 15:17:09 +08:00
renovate[bot] 6ab77e541f Update dependency com.android.application to v8.6.0-alpha07 2024-06-25 10:01:48 +08:00
renovate[bot] 6224f8f4ab Update Rust crate quick-xml to 0.34 2024-06-24 21:20:26 +00:00
renovate[bot] 3d269c4220 Lock file maintenance 2024-06-24 00:31:53 +00:00
FooIbar 176a802e96 Don't use Unicode ellipsis character for filename
It will be replaced by three dots.
2024-06-23 09:04:18 +08:00
renovate[bot] 417a06e140 Update dependency io.github.fornewid:material-motion-compose-core to v2.0.1 2024-06-22 21:19:47 +00:00
FooIbar 5c4f034532 Update Compose snapshot and vendor Reorderable
As `animateItemPlacement` has been removed.
2024-06-22 12:57:13 +08:00
FooIbar 1d50855f52 Workaround for displaying extremely long images (#1327)
* Workaround for displaying extremely long images

* Make hardware bitmap threshold configurable
2024-06-22 12:56:46 +08:00
renovate[bot] 0280450797 Update Rust crate quick-xml to 0.33 2024-06-21 16:58:19 +00:00
FooIbar d0c907c7ff Remove unused jitpack repo 2024-06-21 12:17:49 +08:00
FooIbar 1302e17c01 Migrate to ViewPager2 (#1335)
* Migrate to ViewPager2

* Remove code for unimplemented automatic background
2024-06-21 12:07:27 +08:00
FooIbar 33f83b7325 Switch PhotoView to active fork 2024-06-20 15:05:02 +08:00
FooIbar d2c49f5022 Upload all R8 reports 2024-06-19 12:26:32 +08:00
FooIbar 084883a308 Decouple CronetEngine 2024-06-19 12:14:20 +08:00
FooIbar 67a73da720 Revert "Update dependency com.android.application to v8.6.0-alpha06"
This reverts commit f2447fd122.
2024-06-19 01:21:01 +08:00
renovate[bot] f2447fd122 Update dependency com.android.application to v8.6.0-alpha06 2024-06-18 16:33:56 +00:00
FooIbar 8ddcb003e0 Move lint check to the check job (#1330)
* K2 lint

* Move lint check to the check job

* Fix CI
2024-06-18 16:25:38 +00:00
FooIbar 0bff011f72 Address deprecation 2024-06-18 18:15:29 +08:00
FooIbar f41d1d7306 Skip patching if the patch is already applied 2024-06-18 00:12:33 +08:00
renovate[bot] a30aa23b67 Update xmlutil to v0.90.1 2024-06-17 23:39:02 +08:00
FooIbar 583d30eb5f Update nettle to v3.10 2024-06-17 18:01:31 +08:00
FooIbar c53b0986a9 Use link options to exclude libarchive symbols 2024-06-17 18:00:56 +08:00
renovate[bot] 85703c8c47 Lock file maintenance 2024-06-17 07:57:38 +00:00
renovate[bot] 025ba0bf5b Lock file maintenance 2024-06-17 01:19:24 +00:00
renovate[bot] ac2def22d2 Update Rust crate android_logger to 0.14 2024-06-16 09:59:31 +00:00
FooIbar 895cfc7bc6 Minor refactor 2024-06-16 17:48:50 +08:00
FooIbar 49eb9d5077 Fix gid qualifier not working with language filter 2024-06-16 17:48:50 +08:00
FooIbar 5a3c165ff1 Update ktlint to 1.3.0 2024-06-16 17:48:50 +08:00
FooIbar 3946428df6 Migrate to the std implementation of OnceCell 2024-06-16 17:46:40 +08:00
FooIbar 471d9784d6 Use different schedule to avoid rate limits 2024-06-15 13:41:21 +08:00
FooIbar 0ba0afc0d0 Downgrade Compose to the last known good build 2024-06-14 20:44:18 +08:00
FooIbar 5049b791fb Fix FAB recomposing on item selected 2024-06-14 20:43:46 +08:00
renovate[bot] 9a3715c516 Update androidx.lifecycle to v2.8.2 2024-06-12 23:23:08 +00:00
renovate[bot] 4ee276f636 Update androidx.room to v2.7.0-alpha04 2024-06-12 20:27:55 +00:00
renovate[bot] d528b2d5d0 Update dependency io.github.petertrr:kotlin-multiplatform-diff to v0.6.0 2024-06-12 01:40:34 +00:00
FooIbar b3ee69d193 Clean up EhCookieStore 2024-06-11 14:21:51 +08:00
FooIbar f882b78ff6 Update NDK to r27 RC 1 2024-06-11 14:20:18 +08:00
renovate[bot] 7e58101a3f Update Rust crate quick-xml to 0.32 2024-06-10 15:25:59 +00:00
FooIbar 5450eb9b2b Revert "Update Compose snapshot"
This reverts commit 13cf42437f.
2024-06-10 13:04:40 +08:00
renovate[bot] 69695e3c99 Lock file maintenance 2024-06-10 01:02:53 +00:00
FooIbar 5b15059c04 Switch back to C implementation of sha1
Now that libnettle is built with hardware acceleration instructions
2024-06-09 23:11:57 +08:00
FooIbar f50d69cd60 Build libnettle with assembly code (#1292)
* Build libnettle with assembler code

* Add .gitattributes for shell script

* Patch nettle for CPU feature detection on Android

* Don't build libnettle at configuration time

* Fix build

* Refine
2024-06-09 13:49:36 +08:00
renovate[bot] df6f783a69 Update dependency com.android.application to v8.6.0-alpha05 2024-06-08 10:58:55 +00:00
Dude so hot f0291d94f9 Tell function return types to workaround compiler bug 2024-06-07 17:11:04 +08:00
Dude so hot 593f24b86f Refactor awaitInputTextWithCheckBox 2024-06-07 17:11:04 +08:00
Dude so hot 5bce898f99 Move goto dialog invalidator to use ensure 2024-06-07 17:11:04 +08:00
Dude so hot 2b444c262d Move archive dialog invalidator to use ensure 2024-06-07 17:11:04 +08:00
Dude so hot e40a7c75d9 Move page range invalidator to use ensure 2024-06-07 17:11:04 +08:00
Dude so hot 4a7c057260 Expr-ize 2024-06-07 17:11:04 +08:00
Dude so hot 229a00cd53 Let Dialog.awaitInputText use Raise to improve semantics 2024-06-07 17:11:04 +08:00
Dude so hot d2d0d880f1 Let Dialog.awaitResult use Raise to improve semantics 2024-06-07 17:11:04 +08:00
Dude so hot c5e17c1d5b Retry when FabLayoutState sync interrupted
Double click primary fab on select mode should "collapse to recompose secondary fabs" then immediately expand
2024-06-07 15:58:37 +08:00
Dude so hot 33ff2ebe06 Import arrow resilience 2024-06-07 15:58:37 +08:00
Dude so hot 5c13e188de Secondary fab recompose force fablayout collapse
Fix bug where fastly click primary fab twice on select mode, select state is cleared but secondary fabs not recomposed
2024-06-07 15:58:37 +08:00
FooIbar 60b03dcd0e Fix integrity check for non-H@H resampled images
Their URLs contain hashes for both original and resampled images.
2024-06-07 15:13:40 +08:00
renovate[bot] 4604a25f58 Update dependency io.github.fornewid:material-motion-compose-core to v2 2024-06-07 01:26:56 +00:00
FooIbar 99029b8b04 Prepare for next iteration 2024-06-06 22:07:52 +08:00
FooIbar 70f46442c6 Prepare for release 1.11.6-RC2 2024-06-06 22:07:23 +08:00
FooIbar 13cf42437f Update Compose snapshot
https://issuetracker.google.com/343750859
2024-06-06 21:54:22 +08:00
renovate[bot] 1e29f6dcb4 Update kotlinx.serialization to v1.7.0 2024-06-06 13:52:43 +00:00
renovate[bot] f25981792f Update dependency com.google.devtools.ksp to v2.0.0-1.0.22 2024-06-06 00:09:30 +00:00
FooIbar 934c8be3e4 Anti-tamper support 2024-06-04 15:13:09 +08:00
renovate[bot] b9994eacbc Lock file maintenance 2024-06-03 01:53:41 +00:00
FooIbar 704ab0ee7e Prepare for next iteration 2024-06-02 23:38:33 +08:00
FooIbar 6d1d737f9f Prepare for release 1.11.6-RC1 2024-06-02 23:38:02 +08:00
FooIbar bc75156232 Simplify ReadableTime.getFilenamableTime 2024-06-02 22:51:58 +08:00
FooIbar 2101ea7a36 Update mime types based on Android's mime.types 2024-06-02 22:22:42 +08:00
FooIbar 26921c8ae1 Fix data export/import on Android V
The mime type mapping for sqlite files was added in
https://r.android.com/2988915.
2024-06-02 22:22:42 +08:00
FooIbar be1eecf3b5 Use official coroutines API instead of manual cast 2024-06-02 19:49:21 +08:00
FooIbar 2d4f154c9b Prevent async failure from cancelling parent scope 2024-06-02 19:49:21 +08:00
renovate[bot] d82cbe51ba Update dependency io.github.fornewid:material-motion-compose-core to v1.2.1 2024-06-02 03:13:01 +00:00
FooIbar d13ffa93ba Workaround for navigation bars padding on API < 28 2024-06-01 22:34:30 +08:00
FooIbar 2cce55953f Fix quick search and download label reordering
This is a regression introduced by 6277d7d.
2024-06-01 22:15:44 +08:00
FooIbar 07d100fcc0 Show navigation drawer only on top-level screens (#1266)
* Show navigation drawer only on top-level screens

* Remove redundant `rememberUpdatedState`
2024-06-01 18:59:16 +08:00
FooIbar cc04371181 Fix navigation drawer overlapping side sheet 2024-06-01 13:08:49 +08:00
github-actions[bot] ee50c88765 Update Chrome version for default User-Agent 2024-06-01 09:02:43 +08:00
renovate[bot] efc5b55ea7 Update dependency gradle to v8.8 2024-05-31 23:42:12 +00:00
FooIbar 94af66156a Replace invalid/consecutive whitespaces in query 2024-06-01 01:52:03 +08:00
FooIbar 4861359f9e Make secondary FAB offset density-aware 2024-06-01 01:51:40 +08:00
renovate[bot] 5af167dab4 Update compose.destinations to v2.1.0-beta09 2024-05-31 14:19:32 +00:00
renovate[bot] db1f0bc6d7 Update dependency com.android.application to v8.6.0-alpha04 2024-05-30 23:33:55 +00:00
renovate[bot] b94a843ec5 Update aboutlibraries to v11.2.1 2024-05-30 08:27:09 +00:00
FooIbar b27d214f24 Use Non-H as default category for guest mode
Don't modify the URL builder.
2024-05-30 10:57:26 +08:00
FooIbar a81eb9d17b Fix default value 2024-05-30 10:57:26 +08:00
FooIbar ec9b85f30f Refactor to not get sign-in state from cookies 2024-05-30 10:57:26 +08:00
FooIbar 8ac2f33949 Update liblzma to v5.6.2 2024-05-30 09:32:18 +08:00
FooIbar d84198ab28 Move Compose out of snapshot 2024-05-30 09:22:20 +08:00
renovate[bot] 34d339d9f1 Update compose.destinations to v2.1.0-beta08 2024-05-30 00:26:53 +00:00
renovate[bot] bcfefc7c76 Update dependency dev.chrisbanes.compose:compose-bom to v2024.05.00-alpha03 2024-05-29 21:16:26 +00:00
renovate[bot] 62539dc48e Update dependency androidx.appcompat:appcompat to v1.7.0 2024-05-29 21:15:22 +00:00
renovate[bot] 453ab11395 Update androidx.lifecycle to v2.8.1 2024-05-29 18:36:58 +00:00
renovate[bot] b2246b980a Update androidx.room to v2.7.0-alpha03 2024-05-29 18:36:07 +00:00
FooIbar 822a461acd Use All as default recent download label 2024-05-28 22:46:18 +08:00
renovate[bot] 25e5c0ab8e Lock file maintenance 2024-05-27 01:53:10 +00:00
FooIbar 2a55db95f4 Workaround for ReaderPageSheet clipped on API < 28 2024-05-26 17:17:50 +08:00
FooIbar a144866182 Use commit time instead for reproducible builds 2024-05-25 16:56:17 +08:00
FooIbar be7a04a4cb Move kotlin options to kotlin block 2024-05-24 22:18:18 +08:00
renovate[bot] fd1b861dab Update dependency com.android.application to v8.6.0-alpha03 2024-05-24 01:09:17 +00:00
FooIbar 957ebd35aa Update Compose snapshot
https://issuetracker.google.com/333584635
2024-05-23 16:31:14 +08:00
FooIbar 29d8b11771 Fix quota exceeded exceptions being swallowed 2024-05-22 18:37:28 +08:00
renovate[bot] c87059e9d7 Update Kotlin & KSP 2024-05-22 09:51:29 +08:00
renovate[bot] 4babe2eec4 Update aboutlibraries to v11.2.0 2024-05-21 23:45:18 +00:00
FooIbar 48fdaaca0c Fix race condition of ComicInfo.xml modification 2024-05-21 19:54:41 +08:00
FooIbar f0ee88b4d6 [no ci] Fix renovate config 2024-05-21 17:32:37 +08:00
FooIbar 9210780b6b Fix renovate false positive 2024-05-21 17:26:30 +08:00
FooIbar b479e28543 Initial support for importing downloaded archives 2024-05-21 14:20:57 +08:00
FooIbar f93b98fc5e Fix user input not being used when rating gallery 2024-05-20 11:51:29 +08:00
renovate[bot] 4721d1011b Lock file maintenance 2024-05-20 00:43:28 +00:00
FooIbar 4da4443e67 [no ci] Remove unused res 2024-05-20 00:25:13 +08:00
FooIbar e5343f4360 Don't package 32-bit x86 libs into universal apk 2024-05-20 00:24:05 +08:00
FooIbar 86323220dc Prepare for next iteration 2024-05-19 23:06:35 +08:00
FooIbar ada8f3af75 Prepare for release 1.11.5 2024-05-19 23:06:00 +08:00
FooIbar fb940d3982 Group downloads by artist if no labels are defined 2024-05-19 23:02:29 +08:00
FooIbar 8e8729c25f Introduce AppConfig.isSnapshot 2024-05-19 23:01:25 +08:00
FooIbar ffdd063ca1 Revert "Update Compose snapshot"
This reverts commit e6bdaf1172.

Reason for revert: https://issuetracker.google.com/341320533
2024-05-19 20:44:51 +08:00
FooIbar dfd5c6c70e Remove unnecessary synchronization
We use thread-safe methods now.
2024-05-19 20:32:08 +08:00
FooIbar 3068aab681 Use CI update channel by default for snapshots (#1223)
* Use CI update channel by default for snapshots

And use build time as initial update time.

* Fix build time
2024-05-19 20:25:59 +08:00
renovate[bot] e402587f8f Update plugin composeCompilerReportGenerator to v1.3.1 2024-05-19 10:52:18 +00:00
FooIbar 00b1be879d Fix app language out of sync with system settings (#1221)
* Fix app language out of sync with system settings

* Manually update current language

Recomposition may not happen when current language is the same as system
 default.
2024-05-19 10:43:24 +00:00
ɴᴇᴋᴏ e63bd49a4c Update TW Translation (#1081)
* Add new TW Translation

* Fix translation

* change translate

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* change to '圖庫'

* 更新 strings.xml

* space

* fix

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml
2024-05-19 15:41:10 +08:00
renovate[bot] 8131170eb1 Update compose.destinations to v2.1.0-beta07 2024-05-18 20:13:22 +00:00
FooIbar dc1abd3806 Migrate to Compose Material3 Adaptive (#1219)
* Migrate to Compose Material3 Adaptive

* Fix R8
2024-05-18 20:56:03 +08:00
FooIbar 468fbd50da Fix tap zone overlay not following inversion 2024-05-18 18:53:26 +08:00
FooIbar f2011f9b6f Fix reader tap zone inversion not working 2024-05-18 18:53:26 +08:00
FooIbar 1e53a9b389 Make sure favorites don't have the same timestamp 2024-05-18 10:46:46 +08:00
renovate[bot] dc2504b67f Update dependency com.android.application to v8.6.0-alpha02 2024-05-17 23:06:40 +00:00
FooIbar 2ae3e3b64c Reland "Use autogenerated localeConfig"
Reland as https://issuetracker.google.com/294109810 has been fixed.
2024-05-17 23:50:48 +08:00
FooIbar e6bdaf1172 Update Compose snapshot
https://issuetracker.google.com/339188523
2024-05-17 14:34:53 +08:00
FooIbar 36a66d9199 Revert "Workaround for renovate false positive"
This reverts commit fb6fbb8df9.
2024-05-17 14:23:21 +08:00
FooIbar fb6fbb8df9 Workaround for renovate false positive 2024-05-17 12:17:12 +08:00
renovate[bot] ef8449dc02 Update compose.destinations to v2.1.0-beta06 2024-05-16 16:06:28 +00:00
FooIbar b59f8e9ade Use Non-H category as homepage in guest mode 2024-05-16 01:46:14 +08:00
FooIbar a49506f4a0 Update Compose snapshot to fix item clipping
https://issuetracker.google.com/340201510
2024-05-15 21:26:07 +08:00
FooIbar 4ce3d25eed Remove unused code 2024-05-15 17:28:42 +08:00
FooIbar 81b166bee1 Unpin dependencies 2024-05-15 10:58:53 +08:00
renovate[bot] 37a587859a Update dependency dev.chrisbanes.compose:compose-bom to v2024.05.00-alpha02 2024-05-15 10:47:04 +08:00
renovate[bot] b7544630bb Update dependency com.android.application to v8.6.0-alpha01 2024-05-15 02:43:51 +00:00
renovate[bot] 05c7a0fa39 Update dependency androidx.paging:paging-compose to v3.3.0 2024-05-15 02:43:16 +00:00
renovate[bot] eee7655f80 Update dependency org.jetbrains.kotlinx:kotlinx-datetime to v0.6.0 2024-05-15 02:42:54 +00:00
renovate[bot] 9390813c48 Update dependency androidx.lifecycle:lifecycle-runtime-compose to v2.8.0 2024-05-15 02:42:37 +00:00
renovate[bot] d032fbdd67 Update dependency androidx.annotation:annotation to v1.8.0 2024-05-14 23:15:41 +00:00
renovate[bot] adcd73a590 Update dependency androidx.appcompat:appcompat to v1.7.0-rc01 2024-05-14 23:15:00 +00:00
renovate[bot] 87b405bf1f Update androidx.room to v2.7.0-alpha02 2024-05-14 17:41:39 +00:00
renovate[bot] d8306e2c80 Update androidx.lifecycle to v2.8.0 2024-05-14 17:41:20 +00:00
FooIbar c03fe9b51e Address java.util.regex.Pattern usage 2024-05-15 01:31:54 +08:00
FooIbar 9099014d28 Remove reference of "Sign in via cookies" 2024-05-15 01:31:54 +08:00
FooIbar d7b3872486 Support autofill in SignInScreen 2024-05-15 01:31:54 +08:00
FooIbar 4ae2af0bef Adjust SignInScreen layout 2024-05-15 01:31:54 +08:00
FooIbar c9bcd46b77 Revert "Drop basic login as captcha is mandatory now"
This reverts commit e07aab4434.
2024-05-15 01:31:54 +08:00
FooIbar e531653634 Workaround for archive list crash when scrolling
It has fixed size and we don't actually expect its items to be reused.
2024-05-14 13:59:24 +08:00
renovate[bot]andFooIbar a29e4c72b1 Update Kotlin & KSP (#1171)
* Update Kotlin & KSP

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-05-14 12:14:54 +08:00
FooIbar 9e1f7ca051 Fix crash in favorites caused by duplicate items 2024-05-13 22:42:39 +08:00
renovate[bot] fbbc347411 Update dependency sh.calvin.reorderable:reorderable to v2.1.1 2024-05-13 04:11:24 +00:00
renovate[bot] ea364536a4 Lock file maintenance 2024-05-13 01:32:13 +00:00
github-actions[bot] 575fc9e94c Update Chrome version for default User-Agent 2024-05-12 14:53:50 +08:00
FooIbar 703c63125a [no ci] Don't push to main branch directly 2024-05-12 14:42:53 +08:00
FooIbar b205b5e120 Keep default User-Agent updated 2024-05-12 11:44:28 +08:00
FooIbar e27f36d2c8 Don't swallow exceptions when importing database 2024-05-12 11:39:29 +08:00
FooIbar d582eddc03 Update Corrosion to v0.5 2024-05-12 02:08:29 +08:00
FooIbar 3b83d8744c Revert "Update dependency com.android.application to v8.5.0-beta01"
This reverts commit 7c39b95025.
2024-05-11 13:18:20 +08:00
renovate[bot] 68a5217b1c Lock file maintenance 2024-05-11 04:39:15 +00:00
FooIbar c01082b822 Revert pointless bumps and check in Cargo.lock 2024-05-11 12:26:25 +08:00
FooIbar bb95c86320 Update Compose snapshot 2024-05-11 11:56:41 +08:00
FooIbar d398926aeb Simplify rating widget implementation 2024-05-11 03:23:40 +08:00
FooIbar 7b23efb0ce Fix rememberMutableStateInDataStore persistence 2024-05-11 03:22:22 +08:00
FooIbar 7818ff197c Fix R8 2024-05-11 03:21:37 +08:00
FooIbar 95bfc4f8c5 Introduce AnchoredHorizontalDraggable to prevent unintentional drags 2024-05-11 03:21:37 +08:00
FooIbar c750016034 Revert "Revert "Drop experimental touch slop factor setting" (#1162)"
This reverts commit a4619547f1.
2024-05-11 03:21:37 +08:00
FooIbar 6298e501cd Save minimum rating search option in preference (#1163) 2024-05-11 03:18:01 +08:00
renovate[bot] 7c39b95025 Update dependency com.android.application to v8.5.0-beta01 2024-05-10 18:46:00 +00:00
renovate[bot] 02c1e630d1 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.8.1 2024-05-10 11:36:12 +00:00
GG Bond 8f3b0cc536 Refine naming 2024-05-10 19:27:45 +08:00
GG Bond d0c5ef8e8a Refine naming 2024-05-10 19:27:45 +08:00
GG Bond d628024637 Refine 2024-05-10 19:27:45 +08:00
GG Bond fe1249630b Convert FavDialogTheta to use rememberInDataStore 2024-05-10 19:27:45 +08:00
GG Bond cdbe3cf728 Drop unused avatar pref 2024-05-10 19:27:45 +08:00
GG Bond f5e36bc0cf Initialize dataStateFlow on app start 2024-05-10 19:27:45 +08:00
GG Bond 4f9bfe13cb Move SearchCategory to use rememberInDataStore 2024-05-10 19:27:45 +08:00
GG Bond 959da21b9d Introduce rememberInDataStore 2024-05-10 19:27:45 +08:00
renovate[bot]andGG Bond fae2b1f416 Cleanup rememberInVM family (#1166)
* Update DataStore to 1.1.1 for future `rememberInDataStore` impl

* Cleanup `rememberInVM` family

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-05-10 06:15:18 +00:00
GG Bond a054633d0a Update DataStore to 1.1.1 for future rememberInDataStore impl 2024-05-10 13:49:36 +08:00
FooIbar a4619547f1 Revert "Drop experimental touch slop factor setting" (#1162)
* Revert "Drop experimental touch slop factor setting"

This reverts commit d5d8a828ef.

Reason for revert: After some more testing I think sadly we still need it.

* Read touch slop factor as state
2024-05-10 13:08:32 +08:00
FooIbar 5d53febb15 Show gallery pages in thumbnail mode (#1164) 2024-05-10 04:41:42 +00:00
FooIbar 3da1587924 Use HapticFeedback in ChapterNavigator as well 2024-05-10 12:22:09 +08:00
FooIbar 971d0307fe Inline DragHandle as it's redundant now 2024-05-10 12:22:09 +08:00
FooIbar 705d3afb78 Refactor haptic feedback on reordering 2024-05-10 12:22:09 +08:00
renovate[bot] bc34b4b9c1 Update dependency sh.calvin.reorderable:reorderable to v2.1.0 2024-05-09 09:59:57 +00:00
FooIbar a2ac01fcdc Revert "Update dependency sh.calvin.reorderable:reorderable to v2.0.4"
This reverts commit 4cea8d0090.

Reason for revert: The item being dragged becomes invisible.
2024-05-09 14:44:10 +08:00
FooIbar 645ebddd4f Fix crash when saving unloaded images 2024-05-09 14:23:11 +08:00
FooIbar d5d8a828ef Drop experimental touch slop factor setting 2024-05-09 12:18:59 +08:00
FooIbar 96d4193d2d Fix system bars insets in fullscreen on API < 30 2024-05-09 12:14:59 +08:00
FooIbar 18e4afdff9 Update Compose snapshot 2024-05-09 12:13:28 +08:00
FooIbar 19a702ffc4 Migrate to main artifact of AndroidX WorkManager
`work-runtime-ktx` is empty since 2.9.0
2024-05-08 17:55:39 +08:00
renovate[bot] cfd44c256f Update compose.destinations to v2.1.0-beta05 2024-05-07 23:13:30 +00:00
renovate[bot] 4cea8d0090 Update dependency sh.calvin.reorderable:reorderable to v2.0.4 2024-05-07 19:49:57 +00:00
FooIbar 30e166b29c Fix DownloadService state not cleared 2024-05-07 16:34:23 +08:00
FooIbar c50f3d8c5b Fix tap control area shifting after zooming out 2024-05-07 15:37:38 +08:00
FooIbar d9249eeedb Fix tap control area shifting in multi-window mode 2024-05-07 15:37:38 +08:00
FooIbar 51699cafe6 Constrain minimum width of reader bottom bar menus 2024-05-07 15:37:17 +08:00
FooIbar ce97976bdd Refactor bottom bar icon dropdown menus in reader
Avoid hard-coded width and offset.
2024-05-07 15:37:17 +08:00
renovate[bot]andFooIbar 7b8a12e3eb Update dependency sh.calvin.reorderable:reorderable to v2.0.3 (#1143)
* Update dependency sh.calvin.reorderable:reorderable to v2.0.3

* Address deprecation

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-05-07 03:49:33 +00:00
FooIbar 7276c37c54 Don't constrain menu's width 2024-05-07 01:17:55 +08:00
FooIbar 4ec7de611c Introduce DropdownFilterChip for SearchFilter 2024-05-07 01:17:55 +08:00
FooIbar 0295c40b02 Implement fullscreen mode in new reader 2024-05-06 19:53:32 +08:00
FooIbar 035fa44523 Correctly handle display cutout (#1139) 2024-05-06 14:34:22 +08:00
renovate[bot] 60c6618db1 Update compose.destinations to v2.1.0-beta04 2024-05-05 23:53:49 +00:00
FooIbar f35643b2b6 Correctly handle window insets in reader
Now it's actual edge-to-edge.
2024-05-06 07:46:15 +08:00
FooIbar b25661c436 Revert "Clarify message for no current torrents"
This reverts commit 49d4ae07

Reason for revert: We list outdated torrents as well after torrent
dialog overhaul.
2024-05-05 18:05:40 +08:00
FooIbar cea06f0c7e Disable abortOnError for lint in debug build
[no ci]
2024-05-05 17:16:16 +08:00
FooIbar 7de6a821d9 Drop 32-bit x86 support 2024-05-05 11:17:26 +08:00
FooIbar 13be4e4eb1 Remove Navigation Compose dependency
It's transitively provided by Compose Destinations, we should not
directly depend on it in case they are incompatible.
2024-05-05 07:58:46 +08:00
renovate[bot] 691e7c3ba7 Update compose.destinations to v2.1.0-beta03 2024-05-04 18:08:26 +00:00
FooIbar 31aea1cdbf Fix duplicate key in search suggestions 2024-05-05 01:59:49 +08:00
FooIbar 97d5642427 Run download item restoration in parallel 2024-05-04 18:52:05 +08:00
FooIbar c68272c1a4 Fix download item restoration not bailing on error 2024-05-04 17:59:34 +08:00
renovate[bot] a69dc994b3 Update dependency sh.calvin.reorderable:reorderable to v2.0.1 2024-05-04 09:27:19 +00:00
renovate[bot] d79a71c764 Update dependency dev.chrisbanes.compose:compose-bom to v2024.05.00-alpha01 2024-05-04 08:31:58 +00:00
renovate[bot] 35183de477 Update dependency androidx.navigation:navigation-compose to v2.8.0-alpha08 2024-05-04 16:31:41 +08:00
FooIbar 21b9fe3cb2 Update to snapshot Compose and remove workaround (#1050)
* Update to snapshot Compose

* Fix dep

* Revert "More workaround"

This reverts commit 9013778f58.

* Revert "Workaround for lazy layout lagging (#1042)"

This reverts commit b83e0fd0

* Revert "Workaround for comment list lagging at the bottom"

This reverts commit 45be957df5.

* Fix minOffset for blur radius calculation

Also use `onSizeChanged` instead of `onGloballyPositioned` as
measurement happens before placement.
2024-05-04 16:22:01 +08:00
FooIbar 5762b0bfc3 Disallow screen capture on identity cookies dialog 2024-05-04 11:30:34 +08:00
renovate[bot] 3473e91747 Update dependency com.android.application to v8.5.0-alpha08 2024-05-03 23:07:00 +00:00
renovate[bot]andFooIbar 6277d7d41d Update dependency sh.calvin.reorderable:reorderable to v2 (#1120)
* Update dependency sh.calvin.reorderable:reorderable to v2

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-05-03 09:36:56 +00:00
renovate[bot] f22da2b771 Revert androidx navigation update 2024-05-03 17:07:55 +08:00
FooIbar 3466bbd64a Disable grouping for AndroidX dependencies 2024-05-03 16:33:25 +08:00
FooIbar 49fc87621c Revert "Limit workflow concurrency"
This reverts commit e81092d42c.
2024-05-03 16:33:25 +08:00
GG Bond 7e558f4478 DialogState.bgWork doesn't show dialog if work doesn't suspend 2024-05-03 15:29:13 +08:00
FooIbar 307624ca9a Copy invalid filename handling logic from AOSP 2024-05-03 15:29:00 +08:00
FooIbar 40e66b919a Move ArchiveList and TorrentList to separate files (#1113)
* Move ArchiveList and TorrentList to separate files

GalleryDetailScreen is too long

* Refactor with deferred archive and torrent results
2024-05-03 07:27:57 +08:00
FooIbar cf2e0aadd5 [no ci] Fix renovate config 2024-05-02 13:56:25 +08:00
FooIbar 1b523f6a78 [no ci] Tweak renovate config 2024-05-02 13:48:50 +08:00
renovate[bot] 6a9f6bb645 Drop obsolete flag
Room generate kotlin by default now.
2024-05-02 07:17:16 +08:00
renovate[bot] 45602b05c6 Update aboutlibraries to v11.1.4 2024-05-01 19:09:42 +00:00
renovate[bot] 8dc842e125 Update AndroidX & Jetpack Compose 2024-05-01 19:09:18 +00:00
renovate[bot] 9dfc3a49fe Update Rust crate tl to 0.7.8 2024-05-01 17:10:59 +00:00
renovate[bot] 97d4a61140 Update Rust crate serde_cbor to 0.11.2 2024-05-01 17:10:41 +00:00
renovate[bot] 8b596e2274 Update Rust crate sha1 to 0.10.6 2024-05-01 17:10:15 +00:00
renovate[bot] 123b86830b Update Rust crate serde to 1.0.200 2024-05-01 16:40:54 +00:00
renovate[bot] e11f5615a1 Update Rust crate regex-lite to 0.1.5 2024-05-01 14:16:12 +00:00
FooIbar 31fb2ed2b1 Address renovate warning
[no ci]
2024-05-01 22:15:59 +08:00
renovate[bot] 9c12d11c85 Update Rust crate serde to 1.0.199 2024-05-01 14:14:59 +00:00
renovate[bot] aea0e885c3 Update Rust crate log to 0.4.21 2024-05-01 12:43:51 +00:00
renovate[bot] f34ff71145 Update Rust crate jni to 0.21.1 2024-05-01 12:43:29 +00:00
FooIbar 820c16e86b Tweak image limits dialog 2024-05-01 19:39:24 +08:00
renovate[bot] a335348b1a Update Rust crate android_logger to 0.13.3 2024-05-01 10:41:05 +00:00
renovate[bot] 7f134e1995 Update Rust crate anyhow to 1.0.82 2024-05-01 10:40:43 +00:00
FooIbar 9e13c1c3db Revert "Update Kotlin & KSP (#1084)"
This reverts commit 9ff74e789c.

Reason for revert: crash
2024-05-01 01:29:14 +08:00
FooIbar 61161cef0f Torrent dialog overhaul 2024-05-01 01:06:57 +08:00
renovate[bot]andFooIbar 9ff74e789c Update Kotlin & KSP (#1084)
* Update Kotlin & KSP

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-04-30 12:48:06 +00:00
FooIbar 45183df640 Avoid packing/unpacking and use pre-allocated list 2024-04-30 17:13:59 +08:00
FooIbar 22f11994f5 Remove obsolete workaround 2024-04-30 12:46:46 +08:00
renovate[bot] bc7aa832b7 Update dependency com.android.application to v8.5.0-alpha07 2024-04-29 21:37:45 +00:00
FooIbar b32f78ea9f Archive dialog overhaul 2024-04-29 23:49:40 +08:00
FooIbar 78002dac3a Use proper placeholder instead of 0 in image limit 2024-04-29 16:29:46 +08:00
renovate[bot]andGG Bond aca70354e7 Refactor FabBuilder interface & refine suspend dialog naming (#1082)
* `FabLayoutValue` is `Stable`

* FabBuilder accepts a autoClose

* Refactor control flow to pattern matching expression and avoid exception

* Close fab on DatePicker selected

* Refine naming

* Introduce `SnapshotStateMap<K, V>.takeAndClear()`

* PutAll action use `autoClose = false`

* Handle exceptions

* Cleanup

* Handle potential e

* Refine to use autoClose

* FabLayout should use parent Screen CoroutineScope

* Use implicit context

* Introduce `runSwallowingWithUI`

* log exception as well

* Refine naming

* Cleanup with `runSwallowingWithUI`

* Refine naming again

* Fixup

* Remove redundant logcat

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-04-29 04:20:37 +00:00
FooIbar d7a3471b9b Fix side drawer anchor updates during initial open 2024-04-28 23:43:55 +08:00
FooIbar e81092d42c Limit workflow concurrency
[no ci]
2024-04-28 18:51:11 +08:00
renovate[bot] d06df3c819 Update dependency me.saket.telephoto:zoomable to v0.11.2 2024-04-28 10:17:26 +00:00
FooIbar 050bfea8f2 Parse favorite categories from user config page 2024-04-28 18:09:17 +08:00
renovate[bot] 7b84c74abf Update dependency me.saket.telephoto:zoomable to v0.11.1 2024-04-28 06:46:40 +00:00
FooIbar 009c49cd89 Don't fail silently if can't install update 2024-04-28 14:37:31 +08:00
renovate[bot] ca023f73f4 Update dependency me.saket.telephoto:zoomable to v0.11.0 2024-04-28 05:53:51 +00:00
renovate[bot]andFooIbar 456dfd2aac Update compose.destinations to v2.1.0-beta02 (#1073)
* Update compose.destinations to v2.1.0-beta02

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-04-28 05:45:03 +00:00
renovate[bot] 0c0291127c Revert "Drop Xjvm-default=all"
This reverts commit df1efa7604.
2024-04-27 16:59:41 +08:00
FooIbar fe0c74de23 MimeTypeMap is case-sensitive prior to Android O
https://android-review.googlesource.com/251120
2024-04-27 16:54:06 +08:00
renovate[bot] df1efa7604 Drop Xjvm-default=all
We are not a library intended for Javaers nor do we have any Java code to interop so this is meaningless and safely to drop
2024-04-27 03:41:54 +08:00
FooIbar 73122fc517 Workaround for SearchBar reopen in non-touch mode 2024-04-27 03:34:27 +08:00
renovate[bot] b506ba277c Drop -Xlambdas=indy
https://kotlinlang.org/docs/whatsnew-eap.html#generate-lambda-functions-using-invokedynamic
2024-04-26 23:00:41 +08:00
dasoopsandFooIbar ea3aed8fae Reload metadata (#1058)
* Reload metadata

* Remove pages check and Refactor

* Check metadata stable

* Run in parallel

* Refactor postedTime

* Remove concurrency limit

* Update translation

* Update translate

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

* `parseDate` return timestamps

* Remove unused import

* Show Snackbar after the update

* Revert EndLine

* use localContext

* Extend the range of `runSuspendCatching`

---------

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-04-26 21:05:52 +08:00
FooIbar 481bb8e7d4 Update libarchive to v3.7.4 2024-04-26 19:01:03 +08:00
FooIbar 0ed8c40dbf Improve a11y by limiting grid item's aspect ratio 2024-04-26 18:08:32 +08:00
FooIbar d267d7c3b0 [no ci] Change links in README to use https 2024-04-26 17:48:27 +08:00
FooIbar a04206f600 Refine UniFile and evict lookup cache on delete
Also add comments on functions that rely on implementation details.
2024-04-26 15:46:47 +08:00
FooIbar 8d64a6f1b2 Update ndk and support 16 KB page sizes (#1057)
* Update ndk and support 16 KB page sizes

* Fix CI

* Update graphics-path and pass linker flag to rustc
2024-04-25 14:37:01 +00:00
dasoops b3f614e150 Sort artists by gallery count (#1059)
* Sort artists by number of galleries

* Sorted in DB query
2024-04-25 11:23:11 +00:00
renovate[bot]andGG Bond 988fae56fd Add fake implicit syntax for migration (#1056)
* Add fake `implicit` syntax

* Refine

* Refine

* Refine

* Refine

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-04-25 00:17:33 +08:00
FooIbar fa439fc62d Switch back to GitHub xz repo
It should be faster.
2024-04-24 22:05:35 +08:00
GG Bond c58b2d6156 Cleanup realPadding calculation 2024-04-24 19:40:30 +08:00
GG Bond 62c56b7918 Add missing lazy item key 2024-04-24 19:40:30 +08:00
GG Bond 73882ab55d Cleanup DownloadsScreen to use implicit context 2024-04-24 19:17:34 +08:00
490ea2b521 Add Option to Select Downloads Grouping Mode (#998)
* Add Option to Select Downloads Grouping Mode

* hide Drag and Edit Button when Mode is Artist

* Revert "hide Drag and Edit Button when Mode is Artist"

some changes are not filtered when pushed

This reverts commit 319812e156f61c32250d908ed5ed22720ea0c6b7.

* hide Drag and Edit Button when Mode is Artist

* declared is typo

* use ComicInfo.penciller

* remove BaseGalleryInfo.artist

* Correct invalidate key

* reImport kotlin.math.roundToInt, it was incorrectly deleted

* use movableContentOf

* optimize imports

* use remember not rememberInVM

* Get DownloadInfo.artists from Database or `ComicInfo.xml`

* refactor

* unknown artist group

* redundant parameter

* reformat code

* SearchBar title and hint

* Translation

* Fix when initially loaded, the count of DownloadLabel is zero.

* lazy invalid

* use remember not rememberInVM

* refactor

* Fix LazyColumn not updated

* refactor

* Hide some Button in Artist Mode

* Revert

* Fix update labelList not updated UI

* Revert reFormat

* use trailing lambda

* Revert rememberUpdatedState

* Translate

* use buildMap

* Move ARTIST to DOWNLOAD_ARTIST

* Revert multi-line for chained Modifier

* Generate DownloadInfo.artistInfoList after writeComicInfo and restoreDownload

* Fixed crash after download gallery

* Use a composite key for DOWNLOAD_ARTISTS

* Make artists non-null

* Refactor

* Only update artist info when writing comic info

* Separate list for pending artist info

* Remove unused

* Put download artist when import database

* Use a composite key of GID and ARTIST

* Refactor

* Cache labelList

* named lambda parameters

* Refactor to avoid `mutex`

* follow up on `Make artists non-null`

* ArtistList order by ARTIST

* Lift EhTagDatabase check

* Translate artist in download title

* Refactor

* Refactor readMetadataFromLocal

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>

* Do not update the DB when the `artists` is empty

* Remove unused

---------

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
Co-authored-by: GG Bond <whatever@tagkong.com>
2024-04-24 11:01:53 +00:00
GG Bond 7116871652 Avoid implement a CancellableContinuation
Let compiler do CPS transform
2024-04-24 18:43:21 +08:00
FooIbar cca374c774 Refactor UniFile.fromUri for tree uri
This reverts b39e244 ("Fix `UniFile.fromUri` for children of tree uri")
and 3978abe ("Fix UniFile.fromUri() for 2 path segments tree uri").
2024-04-24 17:48:41 +08:00
FooIbar 3e0dbf92d0 Fix search bar offset not being reset 2024-04-22 12:49:03 +08:00
renovate[bot] 2747ae674c Update dependency sh.calvin.reorderable:reorderable to v1.5.2 2024-04-21 20:20:31 +00:00
FooIbar e85ec5aaba Code cleanup 2024-04-21 23:42:55 +08:00
FooIbar d4894e066b Prepare for next iteration 2024-04-21 22:47:04 +08:00
FooIbar 71a4e84d7c Prepare for release 1.11.4 2024-04-21 22:45:54 +08:00
FooIbar d6c36146ba Fix losing reading progress on reading mode change 2024-04-21 22:35:22 +08:00
FooIbar 9013778f58 More workaround 2024-04-21 18:05:25 +08:00
renovate[bot] 1af03dd145 Update dependency sh.calvin.reorderable:reorderable to v1.5.1 2024-04-20 16:46:53 +00:00
FooIbar b83e0fd092 Workaround for lazy layout lagging (#1042) 2024-04-20 16:37:41 +00:00
GG Bond 1f8d786d05 Simplify to use Modifier.offset 2024-04-20 18:31:22 +08:00
GG Bond 5d87a4ada0 Never have a clipped shadow 2024-04-20 18:31:22 +08:00
GG Bond 4d4fb1e398 Eliminate unnecessary Column 2024-04-20 17:10:33 +08:00
GG Bond 05eea54f06 Fix primaryFab appear anim 2024-04-20 17:10:33 +08:00
GG Bond a00d9ef765 Mitigate shadow being clipped 2024-04-20 17:10:33 +08:00
GG Bond aa6ddac9c5 Avoid unnecessary zIndex 2024-04-20 17:10:33 +08:00
GG Bond 9a6f17c745 Image Search Card doesn't need to be scrollable now 2024-04-20 17:10:33 +08:00
GG Bond 980977aaf3 Ensure Image Search Card's shadow not being clipped 2024-04-20 17:10:33 +08:00
GG Bond 3ed0afaf91 Restore an alpha usage can be safely applied 2024-04-20 17:10:33 +08:00
GG Bond 250adb4b33 Fix shadow being clipped 2024-04-20 17:10:33 +08:00
GG Bond 0baf09cb53 Codestyle fix for DownloadCard 2024-04-20 17:10:33 +08:00
FooIbar d04a914b9b Tweak gallery card alignment and ensure min height 2024-04-19 19:08:21 +08:00
renovate[bot] 59775cb27c Update dependency com.android.application to v8.5.0-alpha06 2024-04-18 22:06:24 +00:00
GG Bond fb76cd7b1b Move compose out of snapshot 2024-04-18 11:25:52 +08:00
renovate[bot] 52fb5306b6 Update dependency dev.chrisbanes.compose:compose-bom to v2024.04.00-alpha02 2024-04-18 01:03:11 +00:00
renovate[bot] 74f7d95718 Update AndroidX & Jetpack Compose 2024-04-17 18:12:27 +00:00
renovate[bot] bcc1b78c7c Update compose snapshot 2024-04-18 00:00:56 +08:00
FooIbar 3978abea87 Fix UniFile.fromUri() for 2 path segments tree uri 2024-04-17 17:38:51 +08:00
FooIbar 556b726349 Update to follow the latest material slider specs
Tick size is 4 dp now.
2024-04-16 10:49:10 +08:00
FooIbar e0f16f31e6 Automatically backup data before updating 2024-04-16 00:08:30 +08:00
FooIbar b39e24467c Fix UniFile.fromUri for children of tree uri 2024-04-15 23:50:54 +08:00
FooIbar 65613f07ba Optimize DownloadCard interaction in select mode 2024-04-15 21:53:37 +08:00
FooIbar 666a7e4d77 Allow disable list item animations on API 29+ (#1024)
* Allow disable list item animations on API 29+

* Update list item animations setting description
2024-04-15 14:54:13 +08:00
renovate[bot] cf08bfdd9b Update dependency me.saket.telephoto:zoomable to v0.10.0 2024-04-14 21:58:40 +00:00
renovate[bot]andFooIbar 1f47d56c55 Update Kotlin, KSP and Compose Compiler (#1009)
* Update Kotlin, KSP and Compose Compiler

* Migrate to new compose compiler gradle plugin

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-04-14 14:38:22 +00:00
FooIbar 1a7dde848f Don't treat warnings as errors on debug build 2024-04-14 17:56:55 +08:00
GG Bond c8207308a8 Update predictive back animations to use interpolation curve of (0.1, 0.1, 0, 1) 2024-04-14 17:38:03 +08:00
renovate[bot] f23020cc6f Update dependency sh.calvin.reorderable:reorderable to v1.5.0 2024-04-12 18:46:12 +00:00
renovate[bot] 555e487f1e Update dependency com.android.application to v8.5.0-alpha05 2024-04-11 18:20:21 +00:00
renovate[bot] 107bb0b15d Update aboutlibraries to v11.1.3 2024-04-09 19:14:59 +00:00
FooIbar 87a2de393e Tweak select item dialog style 2024-04-10 03:04:49 +08:00
renovate[bot] a7e4412454 Update dependency com.mikepenz.aboutlibraries.plugin to v11.1.2 2024-04-09 14:42:54 +00:00
FooIbar 7dce4cfa8e Update Compose snapshot 2024-04-09 22:12:49 +08:00
FooIbar 2b30aca47c Update R8 and remove workaround 2024-04-09 21:44:57 +08:00
FooIbar 0073d2862b Fixed crash on deleting a label after renaming it 2024-04-09 18:39:25 +08:00
FooIbar b182c97185 Fix titleless select item dialog top padding 2024-04-09 10:46:44 +08:00
renovate[bot] a32119bada Update dependency com.android.application to v8.5.0-alpha04 2024-04-08 19:37:35 +00:00
FooIbar a928cfd066 Update libarchive to v3.7.3 2024-04-09 00:08:01 +08:00
FooIbar 8c7a371336 Highlight selected item in select item dialog 2024-04-07 23:32:47 +08:00
GG Bond 6f61eb4946 Update outdated comments 2024-04-07 21:21:37 +08:00
GG Bond 1096f05344 Move navigation out of snapshot 2024-04-07 21:21:37 +08:00
GG Bond 26f96d3e38 Move to telephoto 0.10.0-SNAPSHOT 2024-04-07 21:21:37 +08:00
FooIbar 4a02189a59 Rearrange code to move plurals resources together 2024-04-07 17:13:35 +08:00
FooIbar 7a5bab41f6 Add option to disable list item animations (#997)
It may crash on some devices.
2024-04-07 13:57:27 +08:00
FooIbar 84c32fb28d Prepare for next iteration 2024-04-06 17:10:28 +08:00
FooIbar a3cb6d2782 Prepare for release 1.11.3 2024-04-06 17:09:38 +08:00
FooIbar 45be957df5 Workaround for comment list lagging at the bottom 2024-04-06 15:25:26 +08:00
FooIbar 8e45014e3f Align load comments button and progress indicator 2024-04-06 15:24:54 +08:00
FooIbar d84e948352 Add padding for progress indicator in DetailScreen 2024-04-06 15:24:54 +08:00
FooIbar 68bd835038 Fallback to transferTo if sendfile failed 2024-04-06 10:52:25 +08:00
FooIbar c297bebea1 Fix resource leak when decode job is canceled 2024-04-05 20:29:04 +08:00
FooIbar 6c0a823216 Fix typo (#991)
* Fix typo

* Fix lint
2024-04-05 06:57:34 +00:00
renovate[bot]andFooIbar 067a701bdc Update KSP to v2.0.0-Beta5-1.0.20 (#989)
* Update Kotlin, KSP and Compose Compiler

* Fix compose compiler version

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-04-05 05:03:58 +00:00
GG Bond c3fb008c92 Introduce HttpStatement.executeSafely 2024-04-05 13:03:39 +08:00
GG Bond bc4321da2e Cleanup CronetEngine 2024-04-05 13:03:39 +08:00
FooIbar 8dec040803 Fix FileNotFoundException on saving download thumb 2024-04-05 00:04:02 +08:00
FooIbar 29e4e77c44 Open file descriptor in "wt" mode for overwriting 2024-04-05 00:04:02 +08:00
FooIbar 11429ef7fe Log image loading errors in release build 2024-04-05 00:04:02 +08:00
FooIbar f5fcd96a1e Fix DownloadService stopped during onFinish call 2024-04-04 19:17:35 +08:00
renovate[bot]andFooIbar 19120038c5 Update AndroidX & Jetpack Compose (#985)
* Update AndroidX & Jetpack Compose

* Fix nullability

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-04-04 06:19:08 +00:00
FooIbar 9021f28ead Allow update to unstable AndroidX libraries
Most of them are already transitively updated.
2024-04-04 13:52:50 +08:00
renovate[bot] 62934f72e3 Update dependency dev.chrisbanes.compose:compose-bom to v2024.04.00-alpha01 2024-04-03 23:36:15 +00:00
renovate[bot] 6c534e1049 Update dependency androidx.paging:paging-compose to v3.3.0-beta01 2024-04-03 19:00:38 +00:00
renovate[bot] f80cd90792 Update dependency androidx.core:core-splashscreen to v1.1.0-rc01 2024-04-03 19:00:00 +00:00
renovate[bot] 3999c0c114 Update dependency io.arrow-kt:arrow-stack to v1.2.4 2024-04-03 13:21:01 +00:00
FooIbar 59399453c4 Fix IP ban error message and parse error logging (#979) 2024-04-03 08:56:40 +00:00
renovate[bot] 694bb19e2a Update dependency sh.calvin.reorderable:reorderable to v1.4.0 2024-04-03 06:25:14 +00:00
FooIbar 9928ecd4d9 Refactor database export 2024-04-03 11:54:06 +08:00
renovate[bot] 2c5293af6e Update dependency com.android.application to v8.5.0-alpha03 2024-04-02 13:39:05 +00:00
FooIbar 9569ca774d Fix unable to refresh images due to resource leak 2024-04-02 00:38:38 +08:00
FooIbar 3a04460926 Abort if no image entries found in archive 2024-04-01 18:18:19 +08:00
FooIbar acb4fb6166 Don't run decode job if fetch job failed
Fix fetch error being suppressed by decode job result.
2024-03-31 20:39:27 +08:00
renovate[bot]andGG Bond 7d137bb016 Fast move to compose destinations v2.1 (#965)
* Fast move to compose destinations v2.1

* Remove default parameters

* Cleanup

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-31 16:07:06 +08:00
FooIbar 8c2ed37b72 Update build script due to xz Github repo shutdown 2024-03-31 01:31:22 +08:00
FooIbar 749b230250 Fix download label not being draggable 2024-03-30 10:19:08 +08:00
renovate[bot]andGG Bond 23531cb6e0 Upstream SearchBar (#960)
* Upstream SearchBar

* Fix SearchBar label

* Fix SearchBar padding

* Use BoxWithConstraints

* Refine

* Refine

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-29 10:01:42 +00:00
FooIbar 0ac11dbe0b Fix SpiderQueen not respecting FIFO order
And optimize preloading strategy.
2024-03-29 17:50:53 +08:00
FooIbar 9b0e017542 Use Compose UI implementation of parseAsHtml 2024-03-28 15:48:28 +08:00
FooIbar f2f5fcacf9 Use upstream navigation drawer 2024-03-28 15:48:16 +08:00
FooIbar 52ded265f3 Update Compose to snapshot and remove workaround 2024-03-28 08:41:01 +08:00
FooIbar e77f905c4a Revert "Downgrade to stable AGP"
This reverts commit 2d14d34d53.

Reason for revert: Workaround added
2024-03-28 08:39:15 +08:00
FooIbar 2d14d34d53 Downgrade to stable AGP 2024-03-27 13:26:34 +08:00
renovate[bot] 78facda18a Crossinline (#952) 2024-03-26 22:32:42 +08:00
renovate[bot]andrenovate[bot] 1fe1b43015 Update Kotlin, KSP and Compose Compiler (#950)
* Update Kotlin, KSP and Compose Compiler

* Update libs.versions.toml

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <99822064+revonateB0T@users.noreply.github.com>
2024-03-26 14:19:00 +00:00
FooIbar 63164dc70a Scale wide images (banners) to fit target width
And don't scale tall images (webtoons).
2024-03-26 15:50:45 +08:00
renovate[bot] 0d39092a7f Update dependency me.saket.telephoto:zoomable to v0.9.0 2024-03-26 00:34:29 +00:00
renovate[bot] c50db8a327 Update plugin composeCompilerReportGenerator to v1.2.0 2024-03-25 11:58:03 +00:00
renovate[bot] 7e107cc5e2 Update dependency com.mikepenz.aboutlibraries.plugin to v11.1.1 2024-03-23 15:15:45 +00:00
renovate[bot] 260949e3a7 Update dependency gradle to v8.7 2024-03-22 20:28:33 +00:00
renovate[bot] d9ec108721 Update dependency com.android.application to v8.5.0-alpha02 2024-03-22 20:25:51 +00:00
FooIbar 1e97407936 Fix recycled item's height being 0 in webtoon mode
Refactor reader page holder and miscellaneous cleanups
2024-03-22 22:10:00 +08:00
FooIbar 73afe05a18 Manually migrate from schema 17 to 18 for non-null 2024-03-22 20:59:45 +08:00
FooIbar 23475de932 Fix layout inspector not working 2024-03-21 20:31:10 +08:00
FooIbar 6fb2172c2c Drop obsolete snapshot 2024-03-21 19:02:34 +08:00
renovate[bot] 9226b71119 Update dependency dev.chrisbanes.compose:compose-bom to v2024.03.00-alpha02 2024-03-21 10:42:27 +00:00
FooIbar 58e38b58bf Fix renovate configuration
[no ci]
2024-03-21 18:18:29 +08:00
renovate[bot] db0d110627 Update dependency androidx.paging:paging-compose to v3.3.0-alpha05 2024-03-21 10:17:09 +00:00
FooIbar 08812bf7ff Tweak renovate configuration
[no ci]
2024-03-21 18:09:13 +08:00
renovate[bot] daf7f8ffca Update dependency com.android.application to v8.5.0-alpha01 2024-03-21 09:52:03 +00:00
FooIbar 74564fa07f Tweak renovate configuration
[no ci]
2024-03-21 17:41:35 +08:00
FooIbar fc51d3fc81 Allow update to unstable AGP and Compose BOM
[no ci]
2024-03-21 17:32:50 +08:00
renovate[bot]andFooIbar c5d5b8abd2 Update dependency androidx.navigation:navigation-compose to v2.8.0-alpha05 (#929)
* Update dependency androidx.navigation:navigation-compose to v2.8.0-alpha05

* Fix deprecated

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-03-21 17:31:54 +08:00
FooIbar 193badccb8 Fix crash when reading galleries from gallery list 2024-03-21 16:52:17 +08:00
FooIbar 2653d0568a Fix false positive update notifications 2024-03-21 16:31:48 +08:00
FooIbar adf5a65371 Group Kotlin, KSP and Compose Compiler
[no ci]
2024-03-21 15:54:08 +08:00
FooIbar 5b59dabc66 Fix generating simple language from title 2024-03-21 15:19:46 +08:00
GG Bond f7259c5b40 Individual zoomableState per pager item 2024-03-17 14:56:44 +08:00
FooIbar 6f9fceaef8 Prepare for next iteration 2024-03-17 14:45:19 +08:00
FooIbar 6a64ff51ed Prepare for release 1.11.2 2024-03-17 14:44:16 +08:00
FooIbar ea9d9cfba7 Prepare changelog for 1.11.2 2024-03-17 14:41:43 +08:00
FooIbar 75c6561896 Fix app updater getting wrong download link 2024-03-16 20:13:42 +08:00
FooIbar 8f8db82e9d Fix disowned gallery uploader parsing (#918)
* Fix disowned gallery uploader parsing

* Run rustfmt

* Refine

* Run rustfmt
2024-03-16 17:48:10 +08:00
FooIbar a114a8ac72 Minor tweak to some settings' visibility 2024-03-16 11:10:08 +08:00
FooIbar 2c6216057f Revert "Merge default and marshmallow flavors"
This reverts commit 5676de24da.

Reason for revert: Desugaring causes regression on API 26+
2024-03-15 19:55:59 +08:00
FooIbar c5cbcf7418 Reintroduce confirm dialog for swipe-to-dismiss (#914)
Observe the state changes instead of using `confirmValueChange`, which
will be called twice.
2024-03-15 18:40:51 +08:00
FooIbar 5746e3bb57 Pin compose foundation version to 1.7.0-alpha03 2024-03-14 16:05:18 +08:00
FooIbar fd3c87be22 Make reader bottom sheet scrollable and fix insets 2024-03-14 16:05:08 +08:00
FooIbar 866fff3e45 Fix favorites modification blocked by snackbar 2024-03-14 16:04:55 +08:00
FooIbar 06df7befb6 Fix tag namespace for cloud favorites search 2024-03-13 21:38:16 +08:00
FooIbar 8021f86473 Address java.util.regex.Pattern usage 2024-03-13 21:34:04 +08:00
renovate[bot]andGG Bond c9919a8b39 Cleanup CronetEngine (#901)
* Cleanup CronetEngine with UploadDataProviders

* Remove WriteChannelContent as no user now

* Cleanup

* Cleanup

* Remove unnecessary invokeOnCancellation

* Refine

* Refine

* Format

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-13 14:40:37 +08:00
renovate[bot] 386015a9ec Update dependency com.squareup.okio:okio-jvm to v3.9.0 2024-03-13 04:00:26 +00:00
FooIbar cbce98b34e Prepare for next iteration 2024-03-12 22:47:24 +08:00
FooIbar f69521fc1a Prepare for release 1.11.1 2024-03-12 22:45:48 +08:00
FooIbar 01b4054368 Prepare changelog for 1.11.1 2024-03-12 22:34:27 +08:00
FooIbar e07aab4434 Drop basic login as captcha is mandatory now 2024-03-12 21:27:01 +08:00
FooIbar 18caa87512 Implement group by download label 2024-03-12 21:26:09 +08:00
FooIbar 5676de24da Merge default and marshmallow flavors 2024-03-12 12:05:12 +08:00
FooIbar 57bbb1fe38 Embed Cronet and drop OkHttp (#895)
* Embed Cronet and drop OkHttp

* Fix updater
2024-03-11 12:22:39 +00:00
FooIbar dcbfbc2e5f Fix concurrent modification 2024-03-11 15:04:56 +08:00
FooIbar e1f473d301 Convert remaining icons to ImageVector 2024-03-11 12:43:52 +08:00
renovate[bot] a33256d29f Update dependency sh.calvin.reorderable:reorderable to v1.3.3 2024-03-10 11:12:12 +00:00
FooIbar 6a81877b09 Prepare for next iteration 2024-03-09 19:17:18 +08:00
FooIbar 6cd7b85b94 Prepare for release 1.11.0 2024-03-09 19:16:25 +08:00
FooIbar df97a9c4b9 Prepare changelog for 1.11.0 (#881)
no ci
2024-03-09 19:10:37 +08:00
FooIbar 5cb91e9b45 Update liblzma to v5.6.1 2024-03-09 19:03:37 +08:00
FooIbar 94794cb0db Address code review comments 2024-03-09 17:16:42 +08:00
FooIbar a16c021000 Fix temporary download directory cleanup 2024-03-09 16:22:57 +08:00
FooIbar 49d4ae078f Clarify message for no current torrents 2024-03-09 14:52:12 +08:00
FooIbar eb45068df3 Increase bringCursorIntoView workaround delay 2024-03-09 14:19:00 +08:00
renovate[bot] 1aa750d676 Update softprops/action-gh-release action to v2 2024-03-08 21:35:28 +00:00
FooIbar af85a68e3d Fix duplicate subscription on page state 2024-03-08 15:45:54 +08:00
FooIbar a8fc4003a3 Workaround for crash on delete text button click 2024-03-08 09:24:10 +08:00
renovate[bot] 675df44745 Update dependency com.google.devtools.ksp to v2.0.0-Beta4-1.0.19 2024-03-08 00:33:53 +00:00
renovate[bot] 7e271d7e8e Update dependency com.android.application to v8.4.0-alpha13 2024-03-07 22:07:29 +00:00
FooIbar acb2c720af Fix torrent parsing 2024-03-07 23:06:22 +08:00
GG Bond 5d0c2e3f05 Animated dim with SettingsPager 2024-03-07 22:48:33 +08:00
GG Bond 3d5e3620dd Restore dep behaviour in ReaderModeSetting 2024-03-07 22:48:33 +08:00
GG Bond 43aefeedae Restore dep behaviour of fullscreen & cutoutShort 2024-03-07 22:48:33 +08:00
GG Bond c2cbcc009d No more credits to MDC-Android 2024-03-07 21:08:55 +08:00
GG Bond 2aa494d9b7 Get rid of MDC-Android 2024-03-07 21:08:55 +08:00
GG Bond 8f3a11f374 Calculate Reader statusBarColor with Compose 2024-03-07 21:08:55 +08:00
GG Bond 76298f15e4 Rewrite harmonizeWithRole with ColorUtils 2024-03-07 21:08:55 +08:00
GG Bond 58839e0def Fix Error Text Color 2024-03-07 21:08:55 +08:00
GG Bond 6fe62db5e4 Remove ReaderButton, prepare to drop MDC 2024-03-07 21:08:55 +08:00
renovate[bot]andGG Bond 9b9f25d405 Move to Compose Reader Settings Now (#871)
* Move to Compose Reader Settings Now

* Cleanup res

* Cleanup

* Fix trans

* Fix

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-07 17:43:41 +08:00
renovate[bot]andGG Bond e47d7378ec Add remaining settings layout (#870)
* Implement ReadModeSetting

* Fix textstyle

* Fix pref color

* Fix

* Refine

* Refine style

* Remaining Pref

* Inline

* Refine

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-07 08:49:13 +00:00
renovate[bot]andGG Bond 51b385408d Implement remaining of ColorFilterSetting (#867)
Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-07 13:14:58 +08:00
renovate[bot] 8e7cced8d2 Update dependencies 2024-03-07 13:12:18 +08:00
renovate[bot] affeb3e106 Update dependency androidx.browser:browser to v1.8.0 2024-03-06 21:05:15 +00:00
FooIbar a2377857e9 Fix page state update on cache entry removed 2024-03-06 22:53:41 +08:00
FooIbar 7714ff8938 Make sure slider value has single source of truth 2024-03-06 21:55:43 +08:00
FooIbar 352853b88c Implement VolumeKeysHandler 2024-03-06 20:44:49 +08:00
5idereal 23d282e354 Update strings.xml 2024-03-06 19:46:00 +08:00
FooIbar b93c12de89 Fix navigating on non-main thread 2024-03-06 16:30:37 +08:00
FooIbar e0e5a155aa Initial support for local tag search
BREAKING CHANGE: Upgrade database
2024-03-06 16:29:37 +08:00
renovate[bot]andGG Bond e1f34a7b1b Implement ColorFilterSetting (#859)
* Implement ColorFilterSetting

* Initial ColorFilterSetting

* Cleanup

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-05 17:20:09 +00:00
renovate[bot] 298e80eb9d Update compose.destinations to v1.10.2 2024-03-05 16:28:14 +00:00
renovate[bot]andGG Bond 3ecfd83f1e Remaining GeneralSettings Layout (#857)
* PlaceHolder

* Implement

* ConfigureKeepScreenOn

* Implement

* Remaining general layout

* Remove zIndex

* Add ReaderContentOverlay

* Stub for ColorFilterSetting

* Implement ColorFilterSetting

* Introduce SliderChoice

* Refine naming

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-05 16:19:21 +00:00
renovate[bot]andGG Bond 1dcf4e7f46 Implement some reader settings (#856)
* Initial SpinnerChoice

* Apply bgColor

* Introduce SwitchChoice

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-05 18:32:36 +08:00
FooIbar f1a856cb45 Don't fetch metadata if source is GalleryDetail 2024-03-05 18:32:25 +08:00
renovate[bot]andGG Bond af32d7eea7 Initial Setting Pager (#854)
* Update Compose Snapshot

* Initial SettingsPager

* Cleanup

* Fix inactive color

* Sync container color with BSheet

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-05 13:32:48 +08:00
FooIbar 47be2754e5 Refactor tag namespace to use value class 2024-03-05 10:45:51 +08:00
FooIbar 39b16b6650 Fix webtoon last visible item position calculation 2024-03-04 22:13:07 +08:00
FooIbar 0a809a564c Fix general setting update on reading mode changes
Another bug introduced in DataStore migration.
2024-03-04 19:49:32 +08:00
FooIbar 07e24013c8 Workaround for prepending before the first item 2024-03-04 19:48:55 +08:00
FooIbar 8e8394985d Don't crop borders for webtoons
Webtoons are typically split across multiple pages, they require
consistent width to maintain alignment.
2024-03-04 16:56:23 +08:00
FooIbar e54b33aaaa Fix simple language generation for ComicInfo
Regression introduced in commit 4142f288
2024-03-04 15:01:16 +08:00
GG Bond e6a22d8ef4 Reader Setting PlaceHolder 2024-03-04 12:21:02 +08:00
GG Bond ae25018574 ReaderScreen saveToImage 2024-03-04 12:21:02 +08:00
GG Bond 28160608ec ReaderScreen saveImage 2024-03-04 12:21:02 +08:00
GG Bond 38651eba1d Optimize GalleryListScreen 2024-03-04 00:42:36 +08:00
GG Bond cc53fa5f1c Optimize 2024-03-04 00:42:36 +08:00
FooIbar 70f8181752 Support FTS for history and local favorites search
BREAKING CHANGE: Upgrade database
2024-03-03 23:24:08 +08:00
GG Bond 9e506f6258 Update empty hint in HistoryScreen 2024-03-03 23:23:08 +08:00
GG Bond 1304c728cf Cleanup GalleryCommentsScreen 2024-03-03 23:09:00 +08:00
GG Bond 80e11c54e7 Composing FavouritesScreen 2024-03-03 23:09:00 +08:00
GG Bond ff5d58b101 Update CopyLeft years 2024-03-03 23:09:00 +08:00
GG Bond bf550d99bc Remove credits to OkHttp as we are not directly built on it 2024-03-03 23:09:00 +08:00
FooIbar 7738b7bc29 Implement title keyword search in history (#840)
* Implement title keyword search in history

* Don't use Dispatchers.IO
2024-03-03 14:51:30 +00:00
FooIbar 01f51b1000 Decouple search FAB from SearchBarScreen 2024-03-03 18:44:40 +08:00
FooIbar 16dfacb285 Workaround incorrect fling velocity on zoom end 2024-03-03 00:42:46 +08:00
GG Bond a253ddf7e4 composing with MainActivity 2024-03-02 21:57:55 +08:00
GG Bond ffb488482c composing GalleryDetailScreen 2024-03-02 21:57:55 +08:00
GG Bond 0e29754ce9 composing GalleryCommentsScreen 2024-03-02 21:57:55 +08:00
GG Bond ee97720e1f composing GalleryPreviewScreen 2024-03-02 21:22:57 +08:00
GG Bond 29d4e6370c composing GalleryPreviewScreen 2024-03-02 21:22:57 +08:00
GG Bond 9040cb54d4 composing with CoroutineScope 2024-03-02 21:22:57 +08:00
GG Bond f082766f06 composing HistoryScreen 2024-03-02 21:22:57 +08:00
GG Bond e2372891cb composing ReaderScreen 2024-03-02 21:22:57 +08:00
GG Bond 9dc5b5c539 Introducing composing magic, pack necessary environment CompositionLocal 2024-03-02 21:22:57 +08:00
GG Bond 886e025ac8 Implement copy 2024-03-02 21:22:57 +08:00
GG Bond feb13cbd79 Fix sheet anim 2024-03-02 21:22:57 +08:00
GG Bond 66baaad86f Implement share image 2024-03-02 21:22:57 +08:00
FooIbar 4142f28822 Fix simple language generation for favorites 2024-03-02 19:40:30 +08:00
renovate[bot] c578b16e27 Update dependency org.jetbrains.kotlinx:kotlinx-datetime to v0.6.0-RC.2 2024-03-02 10:06:04 +00:00
FooIbar 32887acecd Migrate to kotlinx datetime [2/2] 2024-03-02 17:58:37 +08:00
FooIbar 5cf5cc5e34 Update page state on image extraction failure 2024-03-02 16:11:42 +08:00
GG Bond 4dc0f7e02e CropBorder use Compose IntRect 2024-03-02 16:01:08 +08:00
renovate[bot]andGG Bond 6e9c6e6269 Updates for ReaderScreen (#827)
* Retry pages

* Restore loading behaviour

* Refactor GalleryPager

* GalleryPager accepts onSelectPage

* Slot to do page select actions

* Move zoomableState in

* Refactor using EdgeApplier

* Cleanup unneeded

* Workaround Crash

* Don't scale non-image

* Reset page state when recycled

* Have pageloader restart when cropBorder changes

* Make PagerItem top-level

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-02 15:43:53 +08:00
renovate[bot]andGG Bond a22880a9b1 Fix archive memleak (#828)
* Don't lock anon pages when decode

* Log madvise fault

* Simplify

* Don't readahead unconditionally

* Refine

* Fixes

* Cleanup

* Fix sparse archive case

* Fixes

* Prefault anon pages for new kernel

* Fixes

* Fixes

* Fixes

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-03-02 14:48:35 +08:00
FooIbar 7357724392 Address GlobalScope usage and minor cleanup 2024-03-01 16:05:17 +08:00
renovate[bot] fb1b08e7e8 Update dependency com.android.application to v8.4.0-alpha12 2024-02-29 23:43:32 +00:00
renovate[bot] 1405b99ab8 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha06 2024-02-29 23:41:13 +00:00
FooIbar f7aec69dae Avoid SAF overhead when save-as-cbz is enabled 2024-02-29 22:15:43 +08:00
renovate[bot] f15a0fc4bb Update dependency io.arrow-kt:arrow-stack to v1.2.3 2024-02-29 10:21:56 +00:00
FooIbar 6330fc3bdc Revert "Build with Android 15 preview SDK"
This reverts commit 7d2e0f7f

Reason for revert: Android SDK sources for VanillaIceCream are not
available, making it hard to debug.
2024-02-29 16:10:00 +08:00
FooIbar 01e3f3e9b7 Sort only entries in external archive files 2024-02-29 16:07:34 +08:00
FooIbar e0c614cf71 Cache display name and mime type for document file 2024-02-29 16:06:42 +08:00
renovate[bot] b34764d4c2 Update dependency com.google.devtools.ksp to v2.0.0-Beta4-1.0.18 2024-02-29 04:44:46 +00:00
FooIbar f46f84e533 Fix forbidden filename characters in gallery title 2024-02-29 12:36:40 +08:00
renovate[bot] ca0eaf2053 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha05 2024-02-29 03:05:54 +00:00
renovate[bot] 25f283ee9c Update snapshot 2024-02-29 03:45:13 +08:00
FooIbar 8ee23ab520 Fix zero-copy branch condition for an edge case 2024-02-29 01:45:10 +08:00
renovate[bot]andrenovate[bot] 6964baf3f6 Update aboutlibraries to v11 (major) (#810)
* Update aboutlibraries to v11

* Update LicenseScreen.kt

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <99822064+revonateB0T@users.noreply.github.com>
2024-02-28 12:52:07 +00:00
FooIbar 01aa7af175 Minor fixes for new reader 2024-02-28 18:28:19 +08:00
renovate[bot]andGG Bond 5982bd2d4b Zero-copy read for 0-compression archives (#809)
* Zero-copy read for 0-compression archives

* Refine

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-28 09:04:17 +00:00
FooIbar d0fbb3539f Initial save downloaded gallery as cbz archive (#808)
* Initial save downloaded gallery as cbz archive

* Make save as cbz archive opt-in
2024-02-28 08:06:08 +00:00
GG Bond 284fb4242b Compose Reader Start Page 2024-02-28 15:52:53 +08:00
GG Bond beeb4bdbd4 Crop border for Compose ReaderScreen 2024-02-28 15:52:53 +08:00
renovate[bot]andGG Bond 76a52903e5 Polymorphic pager (#804)
* Ctrl AppbarVisible

* PageIndicatorText

* Polymorphic pager

* Fixes

* Sync state for pager as well

* Refine

* Navbar Padding

* Refine

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-28 04:19:29 +00:00
Vladimir Dostoyevskiy a19b8782a8 Update compose snapshot 2024-02-28 08:58:51 +08:00
Vladimir DostoyevskiyandGG Bond 31f66fd05b Reader updates (#800)
* Refine

* Bars and sliders

* Zoomable

* Refine

* Refine Slider & LazyList state sync

* Refine

* Cleanup

* Fixes

* Fix initial state

* Fixes

* Fixes

* Fixup initial state

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-27 23:14:36 +08:00
renovate[bot] bc3c4e969c Update dependency io.arrow-kt:arrow-stack to v1.2.2 2024-02-27 14:52:00 +00:00
FooIbar 4231402e54 Coroutinize DownloadManager 2024-02-27 22:44:12 +08:00
Vladimir DostoyevskiyandGG Bond ff4f76dfa5 Initial Compose ReaderScreen (#799)
* Initial Compose ReaderScreen

* Refine

* Naming

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-27 09:44:03 +00:00
FooIbar 2ff9df4c07 Show gallery pages in history 2024-02-27 17:29:35 +08:00
GG Bond 7cbc641178 Drop outdated comment 2024-02-27 13:58:33 +08:00
GG Bond 08d5621b59 Update snapshot for non-gesture systemback transition 2024-02-27 13:58:33 +08:00
GG Bond d39c2fdfa2 Update workaround 2024-02-27 13:58:33 +08:00
GG Bond 27e02ad642 Don't dup fd when calculate sha1
Signed-off-by: GG Bond <whatever@tagkong.com>
2024-02-27 12:50:32 +08:00
FooIbar 11e9faef7c Fix skip H@H key parsing
It may contain alphabets.
2024-02-26 20:55:48 +08:00
FooIbar cbb8971dd1 Implement sort downloads by page count 2024-02-26 18:04:45 +08:00
FooIbar 1077f4d8cf Show pages instead of language in downloads' badge (#793)
It is expected that most downloads will be in the same language.

BREAKING CHANGE: Upgrade db, add `PAGES` column to `GALLERIES` table
2024-02-26 17:54:21 +08:00
FooIbar 594f9412e3 Ignore leading zeros when sorting archive entries 2024-02-26 12:44:48 +08:00
Vladimir DostoyevskiyandGG Bond 7674f6a0f7 Refactor Image with coil Interceptor (#791)
* CropBorderInterceptor

* TooLargeBitmapInterceptor

* Refine

* Take Foolbar's code

* May fail if the image is too large

* Fixup

* Fixup

* Refine

* Refine

* Swap CropBorderInterceptor & HardwareBitmapInterceptor

* Refine

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-26 03:49:40 +00:00
Vladimir DostoyevskiyandGG Bond aea6fbf4ab Anyhow for parser (#788)
* Use anyhow for parser

Signed-off-by: GG Bond <whatever@tagkong.com>

* Refine

Signed-off-by: GG Bond <whatever@tagkong.com>

---------

Signed-off-by: GG Bond <whatever@tagkong.com>
Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-25 23:58:33 +08:00
GG Bond 962f9bfff5 Fuck off silly error handling
Signed-off-by: GG Bond <whatever@tagkong.com>
2024-02-25 23:04:35 +08:00
GG Bond 60532e2622 Move sha1 to mod hash, use anyhow
Signed-off-by: GG Bond <whatever@tagkong.com>
2024-02-25 23:04:35 +08:00
FooIbar 5c7a834342 Treat download as finished if archive file exists 2024-02-25 21:24:08 +08:00
FooIbar ca940080a9 Move checked category filter cards to first 2024-02-25 19:39:16 +08:00
Vladimir Dostoyevskiy 2273dd541a Enable simd (#783)
* Enable simd

* Update CMakeLists.txt

* Update CMakeLists.txt

* Update CMakeLists.txt

* Update CMakeLists.txt
2024-02-25 07:34:17 +00:00
Vladimir DostoyevskiyandGG Bond 422062226d Move to rust sha1 impl (#782)
* Move to rust sha1 impl

* Use onstack buffer

Signed-off-by: GG Bond <whatever@tagkong.com>

---------

Signed-off-by: GG Bond <whatever@tagkong.com>
Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-25 14:07:32 +08:00
FooIbar 6a6da6f059 Move sha1 digest calculation to native function (#781)
`kotlinx-io` has removed hash functions API.
2024-02-25 00:15:34 +08:00
GG Bond 4cbb0eca4f launchInVM 2024-02-24 22:29:41 +08:00
GG Bond ebb3eb1ab3 Simplify EhEngine.getPreviewList 2024-02-24 22:29:41 +08:00
GG Bond ebf645acaf Refine naming 2024-02-24 22:29:41 +08:00
GG Bond ec787ebde2 Aggressively fetch in viewModelScope 2024-02-24 22:29:41 +08:00
GG Bond 4f190cb0ef Cleanup GalleryPreviewScreen 2024-02-24 22:29:41 +08:00
GG Bond 2a36868d11 CR for GalleryInfo 2024-02-24 22:29:41 +08:00
GG Bond 176d8be565 Throwable.displayString() 2024-02-24 22:29:41 +08:00
GG Bond 7b581e45e1 CodeReview for LoadStateIndicator 2024-02-24 22:29:41 +08:00
GG Bond 4888e29f79 CodeReview for EhAsyncThumb 2024-02-24 22:29:41 +08:00
GG Bond e5f6d1b1aa CodeReview for EhAsyncThumb 2024-02-24 22:29:41 +08:00
GG Bond d544bbfbeb CodeReview for GalleryPreview 2024-02-24 22:29:41 +08:00
FooIbar ed20d6022b Update liblzma to v5.6.0 2024-02-24 19:26:32 +08:00
FooIbar 4191e2091c Handle invalid UTF-8 characters in response body (#778) 2024-02-24 18:30:40 +08:00
FooIbar 1eeb5a51f6 Introduce DownloadsFilterState (#769)
* Fix default download list not updating on label delete

* Fix unexpected behavior after renaming label
2024-02-24 12:51:11 +08:00
FooIbar dd0776758f Refactor confirm remove download dialog 2024-02-24 01:42:01 +08:00
FooIbar cf3ccf6155 Update snapshot
* Remove obsolete workaround
2024-02-23 22:40:19 +08:00
GG Bond fa6e4acad6 Flatten closure
Signed-off-by: GG Bond <whatever@tagkong.com>
2024-02-23 11:13:58 +08:00
GG Bond db33a72a8e Update snapshot 2024-02-23 11:13:58 +08:00
renovate[bot] 0f8a0549b1 Update dependency com.android.application to v8.4.0-alpha11 2024-02-23 02:02:43 +00:00
renovate[bot] 26ff30af6c Update dependency sh.calvin.reorderable:reorderable to v1.3.2 2024-02-22 01:20:38 +00:00
GG Bond c2fa32f430 Workaround SlotTable modification race 2024-02-21 20:09:36 +08:00
GG Bond 2734de0588 Fix upstream RememberObserver forget order changes 2024-02-21 20:09:36 +08:00
GG Bond aa219e4b71 Fix for compose snapshot 2024-02-21 20:09:36 +08:00
GG Bond 645656b4cd Update compose snapshot 2024-02-21 20:09:36 +08:00
GG Bond ef16bdeb75 Relax GalleryList recomposition 2024-02-21 14:17:37 +08:00
FooIbar 84299b4db7 Workaround for image search (#768)
* Workaround for image search

* Cleanup
2024-02-20 15:22:51 +00:00
FooIbar 953e216dde Implement sort modes and random walk for downloads (#767)
* Implement sort modes and random walk for downloads

* Use `PrefDelegate<T>.asMutableState` instead

BREAKING CHANGE: Upgrade database.
2024-02-20 11:50:37 +00:00
FooIbar c57fd0a923 Reset progress indicator on retry 2024-02-19 17:31:20 +08:00
FooIbar f9cb3d769f Skip H@H on force retry (#762)
* Skip H@H on force retry

* Refine
2024-02-19 08:11:57 +00:00
GG Bond dd62571943 Apply padding for SearchBarScreen Fab 2024-02-19 15:55:41 +08:00
GG Bond 9edd33d88d Apply padding for GalleryCommentsScreen 2024-02-19 15:55:41 +08:00
GG Bond b2fded76d5 Extract a snackBarPadding Modifier 2024-02-19 15:55:41 +08:00
GG Bond 1761493c8e Apply padding to FabLayout 2024-02-19 15:29:33 +08:00
GG Bond 6aa6ee2810 LocalSnackbarFabPadding 2024-02-19 15:29:33 +08:00
GG Bond 84728a1490 Enable context receivers 2024-02-19 15:29:33 +08:00
GG Bond a1568467f0 Cleanup showTip usage 2024-02-19 15:29:33 +08:00
FooIbar 32e2a03c31 Make GalleryInfo.token non-null, upgrade db 2024-02-19 15:24:08 +08:00
GG Bond d4609593e2 Remove showTip usage in GalleryDetailScreen 2024-02-19 12:57:10 +08:00
GG Bond a2af7437c0 Remove showTip usage in GalleryCommentsScreen 2024-02-19 12:57:10 +08:00
FooIbar ab5af22fc3 Re-enable gradle configuration cache 2024-02-18 15:14:35 +08:00
FooIbar 39a159e5ca Optimize CI caching policy 2024-02-18 15:14:35 +08:00
FooIbar ec8a54906c Optimize CI caching policy 2024-02-18 11:59:27 +08:00
FooIbar 73c0ce02ea Separate build tasks to accelerate CI 2024-02-18 08:27:03 +08:00
FooIbar 7d2e0f7f7e Build with Android 15 preview SDK (#749)
* Build with Android 15 preview SDK

* Fix CI

* Don't set application as test-only
2024-02-17 19:05:12 +08:00
FooIbar 7515a049c2 Address cmake warning: "Policy CMP0077 is not set" 2024-02-17 15:03:28 +08:00
GG Bond a317d5303e Enable compiler nonSkippingGroupOptimization 2024-02-17 13:36:07 +08:00
Vladimir DostoyevskiyandGG Bond c4045ded70 Upstream Navigation (#744)
* Official Navigation

* Update snapshot

* Move navigation to snapshot

* Drop dead switch

* Bisect

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-17 12:16:05 +08:00
FooIbar 3cb1b1eee4 Migrate to Corrosion, rust-android-gradle is dead (#742)
* Migrate to Corrosion, rust-android-gradle is dead

This also allows us to enable gradle configuration cache.

* Disable gradle configuration cache
2024-02-17 08:04:28 +08:00
renovate[bot] 5423af46c5 Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json-okio to v1.6.3 2024-02-16 19:03:24 +00:00
FooIbar a2883840e0 Fix thumbnails not updating in grid view (#741)
* Fix 7735237 ("Remember ImageRequest")

* Refine
2024-02-16 11:21:11 +00:00
GG Bond 14810b4c2c Grab some RestrictedApi to make lint happy 2024-02-16 12:25:42 +08:00
GG Bond f5a9a41790 Introduce Result<T>.foldToLoadResult 2024-02-16 12:25:42 +08:00
Vladimir Dostoyevskiy 500d00d214 Update kotlin & compose compiler 2024-02-16 10:00:17 +08:00
Vladimir Dostoyevskiy 49810f8fd1 Update compose snapshot 2024-02-16 09:47:45 +08:00
renovate[bot] 6ad3ab228f Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.8.0 2024-02-15 16:36:08 +00:00
renovate[bot] 3a29ce291b Update compose.destinations to v1.10.1 2024-02-15 16:35:33 +00:00
Vladimir DostoyevskiyandGG Bond 1e28a4c7ab Introduce delegateSnapshotUpdate dsl (#735)
* Introduce rememberSnapshotFlowAsState dsl

* Refine naming

* Cleanup

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-16 00:11:35 +08:00
FooIbar 645467d20f Implement random walk in search results 2024-02-15 18:20:42 +08:00
Vladimir Dostoyevskiy b2a5fe9f52 Update NDK 2024-02-15 09:23:38 +08:00
Vladimir Dostoyevskiy 2ea3c03360 Update compose snapshot (#731)
* Update compose snapshot

* Update
2024-02-14 04:15:24 +00:00
FooIbar 0c147967d2 Attempt to parse received keyword as URL first 2024-02-13 23:45:30 +08:00
FooIbar de1fab2595 Fix current page in goto dialog on ToplistScreen 2024-02-13 23:11:26 +08:00
FooIbar b86eebf0a7 Refactor Context.decodeBitmap to non-blocking 2024-02-13 22:07:36 +08:00
FooIbar 07345f90e6 Fix exported archive extension on older platform 2024-02-13 02:09:54 +08:00
FooIbar 5fe520e575 Cleanup Log usage and make logging configurable 2024-02-13 00:23:10 +08:00
FooIbar a9dd705ec5 Don't trim gallery detail cache, Resolve #710
It will only free about 1 MB memory
2024-02-12 23:42:01 +08:00
FooIbar e36520faad Replace printStackTrace with proper priority log (#722)
* Replace `printStackTrace` with proper priority log

* Fix
2024-02-12 14:40:45 +00:00
Vladimir DostoyevskiyandGG Bond edaf0a4275 Implement check ripple (#721)
* Implement check ripple

* Fix

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-12 14:21:18 +00:00
GG Bond 7735237ea7 Remember ImageRequest
ImageRequest is not Stable, each recompose will allocate an ImageRequest
2024-02-12 21:21:06 +08:00
FooIbar 7bbc4ea618 Fix failed to generate ComicInfo.xml on API < 33 2024-02-12 21:10:46 +08:00
GG Bond db99392082 Simplify GalleryList state a bit 2024-02-12 19:32:54 +08:00
FooIbar 7bf6a9f545 Add animation for DownloadsScreen grid view switch 2024-02-12 18:54:01 +08:00
FooIbar 51c0efebc4 Support grid view in DownloadsScreen (#714)
* Support grid view in DownloadsScreen

* Fix aspect

* Refine
2024-02-12 09:37:45 +00:00
FooIbar 7ef70aa16e Generate ComicInfo.xml on archive download 2024-02-11 21:01:16 +08:00
FooIbar c5c0c6c4bd Initial support for third-party download manager 2024-02-11 18:43:59 +08:00
GG Bond b26cba8ea9 Update compose snapshot 2024-02-10 22:46:08 +08:00
GG Bond 90b4c16166 Lock drawer on SignInScreen 2024-02-10 15:01:53 +08:00
GG Bond 9b0f6fe433 Lock drawer on all Webview Screen 2024-02-10 15:01:53 +08:00
GG Bond e24735c341 Relax GalleryCommentsScreen recompose 2024-02-10 15:01:53 +08:00
GG Bond 9680ada9ee Lift a Modifier.layout usage 2024-02-10 15:01:53 +08:00
GG Bond 028a7c2352 ListUrlBuilder.asDst 2024-02-10 15:01:53 +08:00
GG Bond 80e5f36c4e Long.asDstWith -> GalleryDetailScreenDestination 2024-02-10 15:01:53 +08:00
GG Bond e0b65184e2 BaseGalleryInfo.asDestination() 2024-02-10 15:01:53 +08:00
GG Bond 395a48ddeb Implement random walk in localFav 2024-02-10 15:01:53 +08:00
GG Bond e22a39f549 Refactor FavouritesScreen state management 2024-02-10 15:01:53 +08:00
renovate[bot] e17d934ebf Update dependency com.squareup.okio:okio-jvm to v3.8.0 2024-02-10 00:28:46 +00:00
FooIbar 10746417b6 Fix crash when visiting newer version galleries
(cherry picked from commit 9be19489a4)
2024-02-09 23:27:27 +08:00
FooIbar 3ef45ba0cc Refactor remaining dialogs to Compose 2024-02-09 14:45:26 +08:00
FooIbar a3e7b6ef43 Refactor select item dialog API to take List<T> 2024-02-08 12:39:04 +08:00
renovate[bot] c444a3b435 Update dependency androidx.compose.material3:material3 to v1.2.0 2024-02-08 00:26:23 +00:00
renovate[bot] 5161802465 Update androidx.navigation to v2.7.7 2024-02-07 21:41:33 +00:00
FooIbar 8c27feec7a Refine exporting downloaded gallery as cbz archive (#698) 2024-02-07 07:17:55 +00:00
FooIbar 37e895cdca Decouple hideHvEvents and requestNews settings (#697)
Visiting gallery page on e-hentai will show HV events as well.
2024-02-06 16:23:25 +00:00
FooIbar e1ad4e3b90 Rename .thumb to thumb.jpg so it can be previewed 2024-02-06 23:18:41 +08:00
FooIbar 5f0be5ae43 Move reading progress to database
In this way cbz archives can be read without SpiderInfo.
2024-02-06 21:33:24 +08:00
Vladimir Dostoyevskiy 9a7cabb221 Update snapshot 2024-02-06 20:33:20 +08:00
renovate[bot] 6acc552058 Update dependency sh.calvin.reorderable:reorderable to v1.3.1 2024-02-06 17:44:30 +08:00
FooIbar b4f4d442eb Ensure images use .jpg extension instead of .jpeg 2024-02-06 16:24:55 +08:00
Vladimir DostoyevskiyandGG Bond 21016dda53 Initial draft support export as cbz (#685)
* Initial draft support export as cbz

* Hint user progress

* Hint user result

* Emit comicInfo as well

* Write ComicInfo if not exist

* Use perFilename

* Print stacktrace

* Fix

* Refine

* Revert "Refine"

This reverts commit 2b368180fde00c7a8cfd4e895f1f2d5c29c3cf9f.

* Refine

* Refine

* Fix

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
2024-02-06 06:44:11 +00:00
Vladimir Dostoyevskiy 576e5b1b58 Revert "Update dependency sh.calvin.reorderable:reorderable to v1.3.0"
This reverts commit 17437a6882.
2024-02-06 14:21:33 +08:00
renovate[bot] 17437a6882 Update dependency sh.calvin.reorderable:reorderable to v1.3.0 2024-02-05 21:49:27 +00:00
FooIbar 04f9f087da Refactor image saving to run on background thread 2024-02-05 07:10:35 +08:00
FooIbar c508947728 Lazily initialize image cache of PageLoader
`OSUtils.totalMemory` needs to read from disk
2024-02-04 23:27:54 +08:00
FooIbar ce1dc55d3c Add support for non-UTF-8 encoded archives (#682) 2024-02-04 22:24:32 +08:00
renovate[bot]andFooIbar e9f615819f Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha04 (#677)
* Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha04

* Revert "Fix delayed application of HTTP engine changes"

This reverts commit c82de0b779.

* Revert "No longer need restart to switch HC"

This reverts commit 7e8b0a58

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-02-03 21:24:59 +08:00
FooIbar 8da3546558 Add back GifDecoder as service loader is disabled 2024-02-03 20:05:56 +08:00
FooIbar d8f8dcffbd Update compose snapshot and remove workaround 2024-02-03 18:14:03 +08:00
FooIbar e79113b19e Lazily initialize image cache on background thread 2024-02-03 18:04:42 +08:00
renovate[bot] 2a7f65a1bd Update dependency gradle to v8.6 2024-02-02 20:27:41 +00:00
FooIbar e322878b95 Don't use app-specific dir as default download dir 2024-02-02 11:01:06 +08:00
FooIbar 11b09f187c Refactor some dialogs to Compose (#675)
* Refactor some dialogs to Compose

* Implement rating bar

* Cleanup
2024-02-01 06:42:39 +00:00
renovate[bot] 926309733c Update dependency io.github.fornewid:material-motion-compose-core to v1.2.0 2024-01-31 05:24:14 +00:00
FooIbar c4b2bcab66 Allow clearing igneous cookie without signing out 2024-01-30 20:16:39 +08:00
FooIbar b88c96894d Prepare to save downloaded gallery as cbz archive 2024-01-30 19:15:40 +08:00
FooIbar 8502671203 Drop CookieSignInScreen and SelectSiteScreen
Cookie-based login is useless due to new e-hentai cookie policy.

Simplify login process a bit since exhentai is preferred.
2024-01-30 10:20:22 +08:00
renovate[bot] 4a7d6f7489 Update dependency io.github.fornewid:material-motion-compose-core to v1.1.2 2024-01-29 20:42:45 +00:00
FooIbar aec34c81aa Make PageCount field optional 2024-01-27 21:22:31 +08:00
FooIbar 1d4fe30e04 Restore download from ComicInfo.xml if available 2024-01-27 20:21:11 +08:00
FooIbar a2cfecd922 Upstream SearchBar Predictive Back support 2024-01-27 20:19:59 +08:00
renovate[bot] d36aef3214 Update actions/upload-artifact action to v4 2024-01-27 09:23:06 +00:00
FooIbar c632231813 Update xz to v5.4.6 2024-01-27 17:00:01 +08:00
FooIbar d8dd68c9b6 Fix sad panda check false positive 2024-01-27 14:19:26 +08:00
FooIbar d6a58d5d34 Generate ComicInfo.xml on download 2024-01-27 13:43:08 +08:00
renovate[bot] 41f8168c28 Update gradle/gradle-build-action action to v3 2024-01-26 21:54:50 +00:00
renovate[bot] 4513ead30a Update compose.destinations to v1.10.0 2024-01-26 05:21:27 +00:00
renovate[bot] f4e23a5738 Update dependency dev.chrisbanes.compose:compose-bom to v2024.01.00-alpha03 2024-01-24 22:00:36 +00:00
FooIbar 79b167002c Use spotless's default ktlint version (#657)
* Use spotless's default ktlint version

* Suppress property naming rule for backing property
2024-01-24 08:32:01 +00:00
FooIbar a9a763ae5f Update Kotlin, KSP and Compose Compiler 2024-01-24 15:43:36 +08:00
FooIbar a5fc29bc66 Fix uploader chip not clickable in thumbnail mode 2024-01-24 14:52:51 +08:00
FooIbar 02c568fd7a Update issue template
[no ci]
2024-01-24 13:33:08 +08:00
renovate[bot] ce55891bc2 Update dependency com.android.application to v8.2.2 2024-01-23 18:58:25 +00:00
renovate[bot] 87b1160c72 Update plugin spotless to v6.25.0 2024-01-23 11:18:25 +00:00
GG Bond ccc6d004de Refactor FilterScreen dialog to Compose, animateItemPlacement 2024-01-23 12:49:12 +08:00
renovate[bot]andFooIbar 20b145bee5 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha03 (#648)
* Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha03

* Adapt coil3 changes

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2024-01-21 06:11:13 +00:00
renovate[bot] a37cc59a56 Update dependency org.chromium.net:cronet-api to v119 2024-01-17 18:54:41 +00:00
renovate[bot] 7cbffff12c Update compose.destinations to v1.9.62 2024-01-17 17:47:56 +00:00
renovate[bot] 4e5ae53775 Update actions/cache action to v4 2024-01-17 17:43:46 +00:00
renovate[bot] a53de63695 Update compose.destinations to v1.9.61 2024-01-17 14:54:53 +00:00
renovate[bot] 8020c139ec Update dependency io.github.fornewid:material-motion-compose-core to v1.1.1 2024-01-16 20:29:51 +00:00
FooIbar 3c5375a9a6 Prepare for next iteration 2024-01-15 23:41:08 +08:00
645 changed files with 31977 additions and 29426 deletions

No files matched your search

+3
View File
@@ -1,4 +1,7 @@
[*.{kt,kts}]
ij_kotlin_imports_layout = *
ij_kotlin_line_break_after_multiline_when_entry = false
ktlint_code_style = intellij_idea
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_class-signature = disabled
ktlint_standard_mixed-condition-operators = disabled
-3
View File
@@ -141,9 +141,6 @@ body:
请确认您已经遵守所有必选项
Please ensure you have obtained all needed options
options:
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
required: false
- label: 您已搜索过 [Issue Tracker](https://github.com/FooIbar/EhViewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/FooIbar/EhViewer/issues), No duplicate or related open issue has been found
required: true
+65
View File
@@ -0,0 +1,65 @@
name: Baseline profile generation
on:
schedule:
- cron: '30 0 * * *'
workflow_dispatch:
push:
branches:
- '**'
paths:
- 'benchmark/**'
- '.github/workflows/baseline-profile.yml'
jobs:
baseline-profile:
runs-on: ubuntu-latest
steps:
- name: Free disk space
run: rm -rf $AGENT_TOOLSDIRECTORY
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Checkout
uses: actions/checkout@v6
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
- name: CMake Cache
uses: actions/cache/restore@v5
with:
path: app/.cxx
key: cmake-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
cache-read-only: true
- name: Generate Baseline Profile
run: ./gradlew generateBaselineProfile
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
- name: Upload reports
if: failure()
uses: actions/upload-artifact@v6
with:
name: reports
path: benchmark/build/reports
+126 -143
View File
@@ -8,28 +8,26 @@ on:
workflow_dispatch:
jobs:
linux:
name: Build
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
submodules: 'recursive'
uses: actions/checkout@v6
- name: Setup Java 21
uses: actions/setup-java@v4
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
- name: Rustfmt Check
working-directory: ./app/src/main/rust
@@ -37,177 +35,162 @@ jobs:
- name: Clippy Check
working-directory: ./app/src/main/rust
run: cargo clippy --all-targets --all-features -- -D warnings
run: cargo clippy --target aarch64-linux-android --all-features -- -D warnings
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/actions/setup-gradle@v5
with:
gradle-home-cache-cleanup: true
cache-read-only: true
- name: Check build-logic
run: ./gradlew :build-logic:convention:check
- name: Spotless Check
run: ./gradlew spotlessCheck
- name: Lint Check
run: ./gradlew lintMarshmallowRelease
default:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
save-if: "false"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
- name: CMake Cache
uses: actions/cache@v3
uses: actions/cache@v5
with:
path: |
app/.cxx
app/src/main/cpp/nettle/nettle
key: ${{ runner.os }}-cmake-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cmake-
path: app/.cxx
key: cmake-default-${{ hashFiles('rust-toolchain.toml', 'settings.gradle.kts', 'app/src/main/cpp/CMakeLists.txt', 'app/src/main/rust/Cargo.lock') }}
restore-keys: cmake-default-
- name: Build OSS
run: ./gradlew assembleDefaultOssRelease
# https://github.com/mikepenz/AboutLibraries/issues/1225
- name: Generate aboutlibraries.json
run: ./gradlew :app:prepareLibraryDefinitionsDefaultRelease --rerun
- name: Upload OSS ARM64
uses: actions/upload-artifact@v3
- name: Build
run: ./gradlew assembleDefaultRelease
- name: Upload ARM64
uses: actions/upload-artifact@v6
with:
name: default-oss-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-arm64-v8a-release.apk
name: default-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/default/release/app-default-arm64-v8a-release.apk
- name: Upload OSS x86_64
uses: actions/upload-artifact@v3
- name: Upload x86_64
uses: actions/upload-artifact@v6
with:
name: default-oss-x86_64-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-x86_64-release.apk
name: default-x86_64-${{ github.sha }}
path: app/build/outputs/apk/default/release/app-default-x86_64-release.apk
- name: Upload OSS ARM32
uses: actions/upload-artifact@v3
- name: Upload ARM32
uses: actions/upload-artifact@v6
with:
name: default-oss-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-armeabi-v7a-release.apk
name: default-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/default/release/app-default-armeabi-v7a-release.apk
- name: Upload OSS x86
uses: actions/upload-artifact@v3
- name: Upload mapping
if: always()
uses: actions/upload-artifact@v6
with:
name: default-oss-x86-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-x86-release.apk
name: default-mapping-${{ github.sha }}
path: app/build/outputs/mapping/defaultRelease
- name: Upload OSS mapping
uses: actions/upload-artifact@v3
- name: Upload native debug symbols
uses: actions/upload-artifact@v6
with:
name: default-oss-mapping-${{ github.sha }}
path: app/build/outputs/mapping/defaultOssRelease/mapping.txt
name: default-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/defaultRelease/native-debug-symbols.zip
- name: Upload OSS native debug symbols
uses: actions/upload-artifact@v3
marshmallow:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Java
uses: actions/setup-java@v5
with:
name: default-oss-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/defaultOssRelease/native-debug-symbols.zip
distribution: temurin
java-version: 21
- name: Build GMS
run: ./gradlew assembleDefaultGmsRelease
- name: Install Rust Toolchain
run: rustup target add armv7-linux-androideabi
- name: Upload GMS ARM64
uses: actions/upload-artifact@v3
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
name: default-gms-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-arm64-v8a-release.apk
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
save-if: "false"
- name: Upload GMS x86_64
uses: actions/upload-artifact@v3
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
name: default-gms-x86_64-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-x86_64-release.apk
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
- name: Upload GMS ARM32
uses: actions/upload-artifact@v3
- name: CMake Cache
uses: actions/cache@v5
with:
name: default-gms-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-armeabi-v7a-release.apk
path: app/.cxx
key: cmake-marshmallow-${{ hashFiles('rust-toolchain.toml', 'settings.gradle.kts', 'app/src/main/cpp/CMakeLists.txt', 'app/src/main/rust/Cargo.lock') }}
restore-keys: cmake-marshmallow-
- name: Upload GMS x86
uses: actions/upload-artifact@v3
# https://github.com/mikepenz/AboutLibraries/issues/1225
- name: Generate aboutlibraries.json
run: ./gradlew :app:prepareLibraryDefinitionsMarshmallowRelease --rerun
- name: Build
run: ./gradlew assembleMarshmallowRelease
- name: Upload ARM64
uses: actions/upload-artifact@v6
with:
name: default-gms-x86-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-x86-release.apk
name: marshmallow-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-arm64-v8a-release.apk
- name: Upload GMS mapping
uses: actions/upload-artifact@v3
- name: Upload x86_64
uses: actions/upload-artifact@v6
with:
name: default-gms-mapping-${{ github.sha }}
path: app/build/outputs/mapping/defaultGmsRelease/mapping.txt
name: marshmallow-x86_64-${{ github.sha }}
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-x86_64-release.apk
- name: Upload GMS native debug symbols
uses: actions/upload-artifact@v3
- name: Upload ARM32
uses: actions/upload-artifact@v6
with:
name: default-gms-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/defaultGmsRelease/native-debug-symbols.zip
name: marshmallow-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-armeabi-v7a-release.apk
- name: Build Marshmallow OSS
run: ./gradlew assembleMarshmallowOssRelease
- name: Upload Marshmallow OSS ARM64
uses: actions/upload-artifact@v3
- name: Upload mapping
if: always()
uses: actions/upload-artifact@v6
with:
name: marshmallow-oss-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-arm64-v8a-release.apk
name: marshmallow-mapping-${{ github.sha }}
path: app/build/outputs/mapping/marshmallowRelease
- name: Upload Marshmallow OSS x86_64
uses: actions/upload-artifact@v3
- name: Upload native debug symbols
uses: actions/upload-artifact@v6
with:
name: marshmallow-oss-x86_64-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-x86_64-release.apk
- name: Upload Marshmallow OSS ARM32
uses: actions/upload-artifact@v3
with:
name: marshmallow-oss-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-armeabi-v7a-release.apk
- name: Upload Marshmallow OSS x86
uses: actions/upload-artifact@v3
with:
name: marshmallow-oss-x86-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-x86-release.apk
- name: Upload Marshmallow OSS mapping
uses: actions/upload-artifact@v3
with:
name: marshmallow-oss-mapping-${{ github.sha }}
path: app/build/outputs/mapping/marshmallowOssRelease/mapping.txt
- name: Upload Marshmallow OSS native debug symbols
uses: actions/upload-artifact@v3
with:
name: marshmallow-oss-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/marshmallowOssRelease/native-debug-symbols.zip
- name: Build Marshmallow GMS
run: ./gradlew assembleMarshmallowGmsRelease
- name: Upload Marshmallow GMS ARM64
uses: actions/upload-artifact@v3
with:
name: marshmallow-gms-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-arm64-v8a-release.apk
- name: Upload Marshmallow GMS x86_64
uses: actions/upload-artifact@v3
with:
name: marshmallow-gms-x86_64-${{ github.sha }}
path: app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-x86_64-release.apk
- name: Upload Marshmallow GMS ARM32
uses: actions/upload-artifact@v3
with:
name: marshmallow-gms-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-armeabi-v7a-release.apk
- name: Upload Marshmallow GMS x86
uses: actions/upload-artifact@v3
with:
name: marshmallow-gms-x86-${{ github.sha }}
path: app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-x86-release.apk
- name: Upload Marshmallow GMS mapping
uses: actions/upload-artifact@v3
with:
name: marshmallow-gms-mapping-${{ github.sha }}
path: app/build/outputs/mapping/marshmallowGmsRelease/mapping.txt
- name: Upload Marshmallow GMS native debug symbols
uses: actions/upload-artifact@v3
with:
name: marshmallow-gms-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/marshmallowGmsRelease/native-debug-symbols.zip
name: marshmallow-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/marshmallowRelease/native-debug-symbols.zip
+2 -2
View File
@@ -2,7 +2,7 @@ name: 'Lock Threads'
on:
schedule:
- cron: '0 0 * * *'
- cron: '0 8 * * *'
workflow_dispatch:
permissions:
@@ -15,7 +15,7 @@ jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
- uses: dessant/lock-threads@v6
with:
issue-inactive-days: '1'
process-only: 'issues'
+60 -79
View File
@@ -6,23 +6,29 @@ on:
- "*"
jobs:
linux:
name: Build
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
submodules: 'recursive'
- name: Free disk space
run: rm -rf $AGENT_TOOLSDIRECTORY
- name: Setup Java 21
uses: actions/setup-java@v4
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Checkout
uses: actions/checkout@v6
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
@@ -30,86 +36,61 @@ jobs:
workspaces: "./app/src/main/rust"
- name: CMake Cache
uses: actions/cache@v3
uses: actions/cache/restore@v5
with:
path: |
app/.cxx
app/src/main/cpp/nettle/nettle
key: ${{ runner.os }}-cmake-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cmake-
path: app/.cxx
key: cmake-
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
arguments: assembleRelease
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
cache-read-only: true
- name: Generate Baseline Profile
run: ./gradlew generateBaselineProfile
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
# https://github.com/mikepenz/AboutLibraries/issues/1225
- name: Generate aboutlibraries.json
run: ./gradlew
:app:prepareLibraryDefinitionsDefaultRelease --rerun
:app:prepareLibraryDefinitionsMarshmallowRelease --rerun
- name: Build
run: ./gradlew assembleRelease
- name: Rename Apks
run: |
mv app/build/outputs/apk/defaultOss/release/app-default-oss-universal-release.apk EhViewer-${{ github.ref_name }}-default-oss-universal.apk
mv app/build/outputs/apk/defaultOss/release/app-default-oss-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-default-oss-arm64-v8a.apk
mv app/build/outputs/apk/defaultOss/release/app-default-oss-x86_64-release.apk EhViewer-${{ github.ref_name }}-default-oss-x86_64.apk
mv app/build/outputs/apk/defaultOss/release/app-default-oss-x86-release.apk EhViewer-${{ github.ref_name }}-default-oss-x86.apk
mv app/build/outputs/apk/defaultOss/release/app-default-oss-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-default-oss-armeabi-v7a.apk
mv app/build/outputs/mapping/defaultOssRelease/mapping.txt EhViewer-${{ github.ref_name }}-default-oss-mapping.txt
mv app/build/outputs/native-debug-symbols/defaultOssRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-default-oss-native-debug-symbols.zip
mv app/build/outputs/apk/default/release/app-default-universal-release.apk EhViewer-${{ github.ref_name }}-default-universal.apk
mv app/build/outputs/apk/default/release/app-default-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-default-arm64-v8a.apk
mv app/build/outputs/apk/default/release/app-default-x86_64-release.apk EhViewer-${{ github.ref_name }}-default-x86_64.apk
mv app/build/outputs/apk/default/release/app-default-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-default-armeabi-v7a.apk
mv app/build/outputs/mapping/defaultRelease/mapping.txt EhViewer-${{ github.ref_name }}-default-mapping.txt
mv app/build/outputs/native-debug-symbols/defaultRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-default-native-debug-symbols.zip
mv app/build/outputs/apk/defaultGms/release/app-default-gms-universal-release.apk EhViewer-${{ github.ref_name }}-default-gms-universal.apk
mv app/build/outputs/apk/defaultGms/release/app-default-gms-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-default-gms-arm64-v8a.apk
mv app/build/outputs/apk/defaultGms/release/app-default-gms-x86_64-release.apk EhViewer-${{ github.ref_name }}-default-gms-x86_64.apk
mv app/build/outputs/apk/defaultGms/release/app-default-gms-x86-release.apk EhViewer-${{ github.ref_name }}-default-gms-x86.apk
mv app/build/outputs/apk/defaultGms/release/app-default-gms-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-default-gms-armeabi-v7a.apk
mv app/build/outputs/mapping/defaultGmsRelease/mapping.txt EhViewer-${{ github.ref_name }}-default-gms-mapping.txt
mv app/build/outputs/native-debug-symbols/defaultGmsRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-default-gms-native-debug-symbols.zip
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-universal-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-universal.apk
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-arm64-v8a.apk
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-x86_64-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-x86_64.apk
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-x86-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-x86.apk
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-armeabi-v7a.apk
mv app/build/outputs/mapping/marshmallowOssRelease/mapping.txt EhViewer-${{ github.ref_name }}-marshmallow-oss-mapping.txt
mv app/build/outputs/native-debug-symbols/marshmallowOssRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-marshmallow-oss-native-debug-symbols.zip
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-universal-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-universal.apk
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-arm64-v8a.apk
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-x86_64-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-x86_64.apk
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-x86-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-x86.apk
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-armeabi-v7a.apk
mv app/build/outputs/mapping/marshmallowGmsRelease/mapping.txt EhViewer-${{ github.ref_name }}-marshmallow-gms-mapping.txt
mv app/build/outputs/native-debug-symbols/marshmallowGmsRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-marshmallow-gms-native-debug-symbols.zip
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-universal-release.apk EhViewer-${{ github.ref_name }}-marshmallow-universal.apk
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-arm64-v8a.apk
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-x86_64-release.apk EhViewer-${{ github.ref_name }}-marshmallow-x86_64.apk
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-armeabi-v7a.apk
mv app/build/outputs/mapping/marshmallowRelease/mapping.txt EhViewer-${{ github.ref_name }}-marshmallow-mapping.txt
mv app/build/outputs/native-debug-symbols/marshmallowRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-marshmallow-native-debug-symbols.zip
- name: Releases
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
body: Bump Version
files: |
EhViewer-${{ github.ref_name }}-default-oss-universal.apk
EhViewer-${{ github.ref_name }}-default-oss-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-default-oss-x86_64.apk
EhViewer-${{ github.ref_name }}-default-oss-x86.apk
EhViewer-${{ github.ref_name }}-default-oss-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-default-oss-mapping.txt
EhViewer-${{ github.ref_name }}-default-oss-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-default-universal.apk
EhViewer-${{ github.ref_name }}-default-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-default-x86_64.apk
EhViewer-${{ github.ref_name }}-default-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-default-mapping.txt
EhViewer-${{ github.ref_name }}-default-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-default-gms-universal.apk
EhViewer-${{ github.ref_name }}-default-gms-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-default-gms-x86_64.apk
EhViewer-${{ github.ref_name }}-default-gms-x86.apk
EhViewer-${{ github.ref_name }}-default-gms-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-default-gms-mapping.txt
EhViewer-${{ github.ref_name }}-default-gms-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-marshmallow-oss-universal.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-x86_64.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-x86.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-mapping.txt
EhViewer-${{ github.ref_name }}-marshmallow-oss-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-marshmallow-gms-universal.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-x86_64.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-x86.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-mapping.txt
EhViewer-${{ github.ref_name }}-marshmallow-gms-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-marshmallow-universal.apk
EhViewer-${{ github.ref_name }}-marshmallow-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-marshmallow-x86_64.apk
EhViewer-${{ github.ref_name }}-marshmallow-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-marshmallow-mapping.txt
EhViewer-${{ github.ref_name }}-marshmallow-native-debug-symbols.zip
+4 -3
View File
@@ -1,9 +1,10 @@
.gradle
/local.properties
.DS_Store
/build
/src
build
.cxx
.kotlin
*.iml
.idea
.vscode
/captures
google-services.json
+465
View File
@@ -1,5 +1,470 @@
# 变更日志
## [1.14.6] - 2025-12-17
### 新功能
* 支持显示画廊阅读进度(于 EH 设置中开启) [#2277](https://github.com/FooIbar/EhViewer/issues/2277)
### 改进
* 画廊列表变化时重新显示悬浮操作按钮 [#2681](https://github.com/FooIbar/EhViewer/issues/2681)
* 移除文件名中的尾随空格和点以兼容 Windows [#2727](https://github.com/FooIbar/EhViewer/issues/2727)
* 长按画廊详情中的标签时显示标签名称 [#2769](https://github.com/FooIbar/EhViewer/issues/2769)
* 杂项优化
### Bug 修复
* 对话框中的下拉菜单显示错位
* HTTP 状态错误显示为“什么都没有找到”
* 某些老设备无法通过 Cloudflare 验证 [#2794](https://github.com/FooIbar/EhViewer/issues/2794)
* 杂项修复
## [1.14.5] - 2025-10-19
### 改进
* [阅读器] 显示画廊标题
* [阅读器] 显示菜单时改变标题栏颜色
### Bug 修复
* 横屏模式下打开阅读器设置会闪退 [#2673](https://github.com/FooIbar/EhViewer/issues/2673)
* 强制横屏时竖直拿取设备加载图片会横竖屏乱跳 [#2674](https://github.com/FooIbar/EhViewer/issues/2674)
## [1.14.4] - 2025-10-12
### 改进
* [阅读器] 支持禁用使用音量键翻页 [#2593](https://github.com/FooIbar/EhViewer/issues/2593)
* 视搜索警告为错误 [#2604](https://github.com/FooIbar/EhViewer/issues/2604)
* 余额不足时停止下载并显示通知 [#2479](https://github.com/FooIbar/EhViewer/issues/2479)
* [阅读器] 使用默认阅读模式时对带有 webtoon 标签的画廊自动使用条漫模式 [#2632](https://github.com/FooIbar/EhViewer/issues/2632)
* 优化按页数排序下载项目的速度
* 开启显示标签投票状态时为 power >= 100 的标签添加下划线
* 杂项优化
### Bug 修复
* Android 16 QPR1 上画廊详情中的标签文本对比度不足
* 初始化 DownloadManager 用时过长导致 ANR [#2588](https://github.com/FooIbar/EhViewer/issues/2588)
* 某些设备上无法下载名称较长的画廊 [#2607](https://github.com/FooIbar/EhViewer/issues/2607)
* 评论区链接实际可点击区域比画面显示上的略长 [#2615](https://github.com/FooIbar/EhViewer/issues/2615)
* 主页快速搜索再次打开出现以前的画廊重复出现在最上层 [#2628](https://github.com/FooIbar/EhViewer/issues/2628)
* 非条漫模式下原始大小较小的动图不播放 [#2652](https://github.com/FooIbar/EhViewer/issues/2652)
* Android 9 以下打开评论区切屏后回来会闪退 [#2655](https://github.com/FooIbar/EhViewer/issues/2655)
* 杂项修复
## [1.14.3] - 2025-09-01
### 新功能
* 支持以 label: 语法搜索下载标签
### Bug 修复
* [阅读器] 关闭滤镜设置时崩溃 [#2573](https://github.com/FooIbar/EhViewer/issues/2573)
* 编辑评论时上下文菜单缺少格式化选项
* 某些情况下搜索相似画廊时出错
## [1.14.2] - 2025-08-16
### 改进
* [下载] 支持将最低下载速度设为 0 [#2545](https://github.com/FooIbar/EhViewer/issues/2545)
* [下载] 移除令人困惑的连接超时设置 [#2556](https://github.com/FooIbar/EhViewer/issues/2556)
### Bug 修复
* 下载速度计算不准确导致下载失败误报 [#2545](https://github.com/FooIbar/EhViewer/issues/2545)
* 无法解析带有 location: 标签的画廊
## [1.14.1] - 2025-07-31
### 改进
* 更新 GitHub 访问令牌
## [1.14.0] - 2025-07-23
### 重大变更
* 默认隐藏分数为 -100 的评论
* 迁移部分组件到 Material 3 Expressive
### 新功能
* [EH] 支持显示标签投票状态
* 支持添加画廊标签时自动补全
* [下载] 支持指定连接超时和最低响应速度 [#1915](https://github.com/FooIbar/EhViewer/issues/1915)
* [阅读器] 支持反转物理按键控制 [#1962](https://github.com/FooIbar/EhViewer/issues/1962)
* [高级] 支持禁用 QUIC 支持
* [高级] 重新添加桌面版网站选项以绕过 Cloudflare [#2230](https://github.com/FooIbar/EhViewer/issues/2230)
* 支持 Android 6.0 上的动画 WebP
### 改进
* 优化压缩包加载性能
* 优化预加载策略
* 更新日本语翻译
* 为已选中的项目使用不同的颜色 [#2121](https://github.com/FooIbar/EhViewer/issues/2121)
* 缩略图模式下显示已收藏图标 [#2128](https://github.com/FooIbar/EhViewer/issues/2128)
* 迁移大部分解析器到 Rust 实现
* 优化动画 WebP 解码性能 [#2415](https://github.com/FooIbar/EhViewer/issues/2415)
* 优化标签自动补全命中率 [#2473](https://github.com/FooIbar/EhViewer/issues/2473)
* 在状态栏后绘制半透明背景以提高对比度
* 杂项优化
### Bug 修复
* 退出阅读器时崩溃
* Android 8.0 以下的设备无法读取某些压缩包
* 列表模式下点击画廊封面无法进入详情页
* 点击某些评论时崩溃
* 开启裁剪边缘时某些图片被不正确裁剪
* 下载数较多时无网络时启动 app 时崩溃 [#2041](https://github.com/FooIbar/EhViewer/issues/2041)
* 标签翻译数据无法更新 [#2075](https://github.com/FooIbar/EhViewer/issues/2075)
* 画廊详情页面显示的收藏夹名称错误 [#2081](https://github.com/FooIbar/EhViewer/issues/2081)
* 不使用 CI 频道时无法更新
* 添加后移动快捷搜索闪退 [#2124](https://github.com/FooIbar/EhViewer/issues/2124)
* 某些数据无法导入
* App 位于后台时清除下载通知时崩溃
* 杂项修复
## [1.13.1] - 2024-11-11
### 重大变更
* 由于网站限制,移除搜索封面功能
* 移除桌面版网站选项,默认使用 1280x 分辨率
### 改进
* 更新日本语翻译
* 优化压缩包读取
* 缓解某些小米系统上应用闪退 [#1826](https://github.com/FooIbar/EhViewer/issues/1826)
* 杂项优化
### Bug 修复
* Android 9 上的下载失败误报 [#1914](https://github.com/FooIbar/EhViewer/issues/1914)
## [1.13.0] - 2024-11-01
### 重大变更
* 不再捆绑 Cronet 库,在不支持 HttpEngine 的设备上回退到 OkHttp
* 适配 E 站缩略图和 WebP 等相关变更
### 改进
* 使用磁力链接代替种子下载
* 在搜索栏右侧显示论坛头像
* 支持重置下载路径
* 为 Android 10 以下且没有 DocumentsUI 的设备创建默认下载目录 [#1735](https://github.com/FooIbar/EhViewer/issues/1735)
* 重新添加了强制使用 e-hentai 缩略图服务器的选项
* 阅读器支持 PageUp/PageDown 和方向键上/下翻页 [#1801](https://github.com/FooIbar/EhViewer/issues/1801)
* 未找到搜索结果时显示可能存在的警告 [#1787](https://github.com/FooIbar/EhViewer/issues/1787)
* 更新了日本语翻译
* 杂项优化
### Bug 修复
* 阅读器屏幕方向与手机屏幕方向不同时闪退 [#1779](https://github.com/FooIbar/EhViewer/issues/1779)
* 下载文件哈希不匹配导致下载失败 [#1811](https://github.com/FooIbar/EhViewer/issues/1811)
* 无法同时下载和查看 [#1822](https://github.com/FooIbar/EhViewer/issues/1822)
* 杂项修复
## [1.12.1] - 2024-09-28
### 新功能
* 屏蔽含有二维码的图片(位于高级设置)
### 改进
* 移除高级设置中的自定义 User Agent,改为桌面版网站选项
* 画廊预览与画廊详情页面合并
* 为快速搜索和下载标签列表添加了滚动条
* 无网络条件下启动时自动转到下载页面
* 杂项优化
### Bug 修复
* 从左到右或右到左模式观看使用鼠标滚轮无法翻页 [#1541](https://github.com/FooIbar/EhViewer/issues/1541)
* 状态栏颜色未跟随阅读器主题变化 [#1542](https://github.com/FooIbar/EhViewer/issues/1542)
* 打开 EH 设置时闪退 [#1563](https://github.com/FooIbar/EhViewer/issues/1563)
* 画廊种子过多时显示没有种子 [#1561](https://github.com/FooIbar/EhViewer/issues/1561)
* 加载大于 100 MB 的位图时崩溃
* 阅读器加载小圆圈在黑色背景下不自动变色 [#1579](https://github.com/FooIbar/EhViewer/issues/1579)
* 因闪退造成的无法挽回的下载失败 [#1602](https://github.com/FooIbar/EhViewer/issues/1602)
* 某些情况下闪退 [#1548](https://github.com/FooIbar/EhViewer/issues/1548) [#1555](https://github.com/FooIbar/EhViewer/issues/1555) [#1568](https://github.com/FooIbar/EhViewer/issues/1568)
* Android 7.0 及以下的设备无法加载图片 [#1633](https://github.com/FooIbar/EhViewer/issues/1633)
* 某些情况下导入数据失败 [#1634](https://github.com/FooIbar/EhViewer/issues/1634)
* 下载时创建重复文件夹 [#1619](https://github.com/FooIbar/EhViewer/issues/1619)
* 滚动方向改变后预载图片不生效 [#1560](https://github.com/FooIbar/EhViewer/issues/1560)
* 选择无效的下载位置后闪退
* 杂项修复
## [1.12.0] - 2024-08-19
### 重大变更
* Compose 阅读器已稳定,移除了旧阅读器实现 [#847](https://github.com/FooIbar/EhViewer/issues/847)
* 由于 E 站缩略图服务器变更,移除了强制使用 e-hentai 缩略图服务器的选项
### 改进
* 更新高级搜索选项中页数过滤器的范围限制以遵循 E 站变更
### Bug 修复
* 评论页面刷新指示器边距不正确 [#1428](https://github.com/FooIbar/EhViewer/issues/1428)
* 关闭打开失败的压缩包时崩溃
* 无法打开带密码的压缩包 [#1454](https://github.com/FooIbar/EhViewer/issues/1454)
* 使用音量键翻页时无法关闭过渡动画 [#1467](https://github.com/FooIbar/EhViewer/issues/1467)
* 带有图片的评论显示错位 [#1468](https://github.com/FooIbar/EhViewer/issues/1468)
* 在包含相同项目的页面间切换时崩溃 [#1490](https://github.com/FooIbar/EhViewer/issues/1490)
* 无法检查更新 [#1512](https://github.com/FooIbar/EhViewer/issues/1512)
* 画廊详情未加载完成时退出阅读器导致崩溃 [#1507](https://github.com/FooIbar/EhViewer/issues/1507)
* 无法为带有临时标签的画廊生成 ComicInfo.xml [#1509](https://github.com/FooIbar/EhViewer/issues/1509)
* 已下载的画廊翻页时出现加载动画 [#1510](https://github.com/FooIbar/EhViewer/issues/1510)
* 画廊详情页面的缩略图无法加载 [#1529](https://github.com/FooIbar/EhViewer/issues/1529)
* 图片未加载完成时无法通过点击翻页
* 搜索结果为空时解析失败 [#1418](https://github.com/FooIbar/EhViewer/issues/1418)
* 缩略图分辨率设置不起作用
## [1.11.7] - 2024-07-10
### Bug 修复
* 无法下载标题包含 # 或 % 的画廊
* 下载页数较多的画廊时应用无响应
* 退出搜索栏时文本闪烁
## [1.11.6] - 2024-07-09 [YANKED]
### 改进
* 某些设备上无法加载长图时可尝试减小 设置-高级 中的硬件位图阈值 [#1321](https://github.com/FooIbar/EhViewer/issues/1321)
* 清理下载冗余时显示确认对话框 [#1369](https://github.com/FooIbar/EhViewer/issues/1369)
* 修改收藏时显示收藏备注 [#1392](https://github.com/FooIbar/EhViewer/issues/1392)
### Bug 修复
* 图片完整性校验误报 [#1286](https://github.com/FooIbar/EhViewer/issues/1286)
* 某些情况下应用崩溃 [#1290](https://github.com/FooIbar/EhViewer/issues/1290) [#1302](https://github.com/FooIbar/EhViewer/issues/1302)
* 某些情况下渲染问题 [#1284](https://github.com/FooIbar/EhViewer/issues/1284)
* 无法下载标题过长的画廊 [#1340](https://github.com/FooIbar/EhViewer/issues/1340)
* 某些界面拖动滚动条时黑屏 [#1329](https://github.com/FooIbar/EhViewer/issues/1329)
* 搜索栏多行文本遮挡其他内容 [#1365](https://github.com/FooIbar/EhViewer/issues/1365)
* CIFS Documents Provider 兼容问题 [#1356](https://github.com/FooIbar/EhViewer/issues/1356)
* 清理下载冗余未按预期运行 [#1369](https://github.com/FooIbar/EhViewer/issues/1369)
* 读取压缩包失败时崩溃
* 某些设备上崩溃 [#1276](https://github.com/FooIbar/EhViewer/issues/1276)
* 检查更新失效
## [1.11.6-RC2] - 2024-06-06
### 改进
* 下载图片时执行完整性校验以防止文件损坏/被篡改
## [1.11.6-RC1] - 2024-06-02
### 改进
* 下载列表默认显示全部下载项目
* 替换搜索文本中的换行符为空格 [#1258](https://github.com/FooIbar/EhViewer/issues/1258)
### Bug 修复
* 评价画廊时未使用输入的评分
* 搜索时意外选择 Non-H 类别 [#1246](https://github.com/FooIbar/EhViewer/issues/1246)
* 侧边栏与导航抽屉重叠 [#1262](https://github.com/FooIbar/EhViewer/issues/1262)
* 快速搜索/下载标签排序失效 [#1267](https://github.com/FooIbar/EhViewer/issues/1267)
* 加载压缩包/种子列表出错时 UI 冻结
* 低版本 Android 上某些 UI 被遮挡
## [1.11.5] - 2024-05-19
### 重大变更
* 不再支持 32 位 x86
### 新功能
* 支持按作者对下载分组
* 由于 E 站不再强制要求人机验证,重新支持账号密码登录
### 改进
* 在登录时加载收藏名称 [#1077](https://github.com/FooIbar/EhViewer/issues/1077)
* 优化归档和种子界面 UI
* 禁止在 cookie 界面截图以防止泄露
* 在缩略图模式下显示画廊页数
* 记住最低评分和页数搜索参数
* 更新 User-Agent 以缓解 IP 封禁 [#1182](https://github.com/FooIbar/EhViewer/issues/1182)
* 账号密码登录支持自动填充
* 更新中文(台灣)翻译
### Bug 修复
* 非触摸模式下无法退出搜索界面 [#1060](https://github.com/FooIbar/EhViewer/issues/1060)
* 某些设备上无法安装更新 [#1067](https://github.com/FooIbar/EhViewer/issues/1067)
* 某些设备上崩溃/卡顿 [#996](https://github.com/FooIbar/EhViewer/issues/996) [#1023](https://github.com/FooIbar/EhViewer/issues/1023)
* 搜索记录中存在重复条目时崩溃 [#1130](https://github.com/FooIbar/EhViewer/issues/1130)
* 条漫模式下缩小后点按区域偏移 [#127](https://github.com/FooIbar/EhViewer/issues/127)
* 多窗口模式下点按区域偏移
* 点击下载通知时未清除状态
* 保存未完成加载的图片时崩溃 [#1154](https://github.com/FooIbar/EhViewer/issues/1154)
* 历史记录界面无法在项目上滑动打开抽屉 [#464](https://github.com/FooIbar/EhViewer/issues/464)
* 导入数据失败时显示导入成功 [#1174](https://github.com/FooIbar/EhViewer/issues/1174)
* 某些条件下收藏界面崩溃 [#1190](https://github.com/FooIbar/EhViewer/issues/1190)
* 反转点按区域功能失效 [#1217](https://github.com/FooIbar/EhViewer/issues/1217)
### 已知问题
* 某些设备上从后台返回时某些 UI 消失,可通过关闭列表项目动画部分缓解 [#1184](https://github.com/FooIbar/EhViewer/issues/1184)
## [1.11.4] - 2024-04-21
### 新功能
* 更新前自动备份数据
### 改进
* 在 设置-高级 中加入了禁用列表项目动画的选项,该功能在部分设备上导致崩溃/卡顿 [#996](https://github.com/FooIbar/EhViewer/issues/996) [#1023](https://github.com/FooIbar/EhViewer/issues/1023)
* 下载列表多选模式下避免误触 [#1016](https://github.com/FooIbar/EhViewer/issues/1016)
* 杂项 UI 优化
### Bug 修复
* 重命名下载标签后删除该标签时崩溃 [#1008](https://github.com/FooIbar/EhViewer/issues/1008)
* 搜索栏中某些输入法长按退格无法连续删除 [#606](https://github.com/FooIbar/EhViewer/issues/606)
* 评论/屏蔽/搜索建议/下载标签/快速搜索列表卡顿 [#1041](https://github.com/FooIbar/EhViewer/issues/1041)
## [1.11.3] - 2024-04-06
### 改进
* 优化预加载逻辑和图片加载顺序
* 优化画廊详情和评论页面 UI
### Bug 修复
* 某些模式下搜索结果不显示画廊语言 [#923](https://github.com/FooIbar/EhViewer/issues/923)
* 某些情况下在搜索结果中直接阅读画廊后崩溃 [#927](https://github.com/FooIbar/EhViewer/issues/927)
* 更新至最新版本后仍提示有新版本 [#927](https://github.com/FooIbar/EhViewer/issues/927)
* 从旧版本升级时包含不兼容数据导致崩溃/无法导出数据 [#940](https://github.com/FooIbar/EhViewer/issues/940) [#974](https://github.com/FooIbar/EhViewer/issues/974)
* Android 10 以下的设备打开 设置-隐私 时崩溃 [#953](https://github.com/FooIbar/EhViewer/issues/953)
* 下载标签无法拖动
* 刷新 和 刷新(原图) 无法在已下载的图片上使用
* 临时 IP 封禁错误显示为解析失败
* 某些情况下下载时闪退 [#980](https://github.com/FooIbar/EhViewer/issues/980)
* 某些设备上下载列表中的缩略图无法显示 [#977](https://github.com/FooIbar/EhViewer/issues/977)
## [1.11.2] - 2024-03-17
### 重大变更
* Default 变种最低支持版本更改为 Android 8.0,重新添加了最低支持 Android 6.0 的 Marshmallow 变种
### 改进
* 重新添加了删除快速搜索/下载标签时的确认对话框
### Bug 修复
* 收藏页面 搜索栏中文标签转英文异常 [#902](https://github.com/FooIbar/EhViewer/issues/902)
* 在收藏或者取消收藏之后,收藏按钮会无反应 [#908](https://github.com/FooIbar/EhViewer/issues/908)
* 横屏页面下阅读设置UI重合 [#909](https://github.com/FooIbar/EhViewer/issues/909)
* Search bar does not work properly in Japanese [#906](https://github.com/FooIbar/EhViewer/issues/906)
* 搜索结果中的 Disowned 画廊不显示上传者
* 使用 CI 频道更新时,有时会获取到错误的更新链接
## [1.11.1] - 2024-03-12
### 重大变更
* 由于 E 站强制要求人机验证,仅支持通过网页登录
* 默认支持 Android 6,不再提供 Marshmallow 变种
* 集成 Cronet 库,不再提供 GMS 变种
### 改进
* 支持在排序下载列表前按下载标签分组
### Bug 修复
* 下载画廊时崩溃 [#893](https://github.com/FooIbar/EhViewer/issues/893)
## [1.11.0] - 2024-03-09
### 重大变更
* 由于 E 站 Cookie 策略变更,不再支持 Cookie 登录
* 登录时自动选择 exhentai (如果可用)
* 不再提供默认下载路径
* 手动排序更改为以添加时间/上传时间/标题/下载标签/页数对下载列表排序
* 由于数据库结构变更,此版本导出的数据将无法被旧版本导入
### 新功能
* 下载画廊时生成 ComicInfo.xml 格式元数据
* 归档下载时生成 ComicInfo.xml 格式元数据 [#711](https://github.com/FooIbar/EhViewer/issues/711)
* 归档下载支持第三方下载管理器 [#79](https://github.com/FooIbar/EhViewer/issues/79)
* 保存下载结果为 CBZ 压缩包 [#660](https://github.com/FooIbar/EhViewer/issues/660)
* 清除 `igneous` Cookie (将于下次搜索时重新获取)
* 随机打开本地收藏/下载中的画廊 [#695](https://github.com/FooIbar/EhViewer/issues/695)
* 随机在搜索结果中跳转
* 以网格模式查看下载列表
* 本地收藏/历史/下载中的标签搜索 [#860](https://github.com/FooIbar/EhViewer/issues/860)
### 改进
* 搜索时优先显示已选择的类别卡片
* 多处性能优化
### Bug 修复
* 显示模式为 Thumbnail 时无法点击上传者卡片 [#653](https://github.com/FooIbar/EhViewer/issues/653)
* [Marshmallow] GIF 只显示第一帧
* 无法在 `定时请求新闻页面` 关闭时开启 `隐藏 HV 事件通知`
* 响应正文含有无效 UTF-8 字符导致解码失败 [#643](https://github.com/FooIbar/EhViewer/issues/643)
* 某些情况下应用崩溃 [#710](https://github.com/FooIbar/EhViewer/issues/710)
* 画廊名称包含非法路径字符时无法保存图片 [#815](https://github.com/FooIbar/EhViewer/issues/815)
* 无法解码超长图片 [#732](https://github.com/FooIbar/EhViewer/issues/732)
* E 站变更导致种子解析失败 [#880](https://github.com/FooIbar/EhViewer/issues/880)
* E 站不再支持相似扫描导致图片搜索无法使用
* 杂项修复
### 已知问题
* Android 14 上使用手势导航返回时过渡动画过早结束 [#775](https://github.com/FooIbar/EhViewer/issues/775)
## [1.10.3] - 2024-01-15
### 新功能
+17 -22
View File
@@ -8,10 +8,14 @@
<a href="/docs/README/zh-tw.md">
正體中文
</a>
<span> | </span>
<a href="/docs/README/ja.md">
日本語
</a>
</p>
<h1 align="center">
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200" alt="EhViewer">
<img src="https://github.com/FooIbar/EhViewer-art/blob/master/launcher_icon-web.svg" width="200" alt="EhViewer">
<br>EhViewer<br>
</h1>
@@ -59,57 +63,48 @@
# Description
EhViewer fork dedicated to lightweight and high-performance
A modern EhViewer fork dedicated to high-performance
with [Material Design 3](https://m3.material.io/)
and [Dynamic Color](https://m3.material.io/styles/color/dynamic-color/overview) Support
# Download
| Flavor | Feature |
|-------------|-----------------------------------|
| Default | Android 9+, full support |
| Marshmallow | Android 6.0-8.1, limited support* |
| OSS | FOSS dependencies** |
| GMS | Proprietary GMS component |
| Flavor | Minimum Android Version | Notes |
|-------------|-------------------------|--------------------------------|
| Default | 8.0 | Full support |
| Marshmallow | 6.0* | Limited support, no guarantees |
\* May not support some archives
** Fall back to OkHttp on devices that don't support [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine)
*Devices running Android 6 need to have the [ISRG Root X1](https://letsencrypt.org/certs/isrgrootx1.pem) certificate installed
<a href="https://github.com/FooIbar/EhViewer/releases">
<img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/>
<img alt="Get it on GitHub" src="https://github.com/FooIbar/EhViewer-art/blob/master/get-it-on-github.svg" width="200px"/>
</a>
# Screenshot
![screenshot-01](https://github.com/Ehviewer-Overhauled/Art/blob/master/screenshot-01.png)
![screenshot-02](https://github.com/Ehviewer-Overhauled/Art/blob/master/screenshot-02.png)
![screenshots-01](https://github.com/FooIbar/EhViewer-art/blob/master/screenshots-01.webp)
![screenshots-02](https://github.com/FooIbar/EhViewer-art/blob/master/screenshots-02.webp)
# Thanks
Here is the libraries
- [Arrow](https://arrow-kt.io/)
- [AOSP & AndroidX](http://source.android.com/)
- [AOSP & AndroidX](https://source.android.com/)
- [Kotlin & KotlinX](https://kotlinlang.org/)
- [MDC-Android](https://github.com/material-components/material-components-android)
- [Material Icons](https://github.com/google/material-design-icons)
- [OkHttp](https://square.github.io/okhttp/)
- [Ktor](https://ktor.io/)
- [Coil](https://coil-kt.github.io/coil/)
- [Compose Destinations](https://composedestinations.rafaelcosta.xyz/)
- [libarchive](http://www.libarchive.org/)
- [jsoup](https://jsoup.org/)
Specially thanks [Tachiyomi](https://tachiyomi.org/) for its reader implementation!
- [libarchive](https://www.libarchive.org/)
# License
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
Copyright 2022-2023 Tarsin Norbin
Copyright 2023 Foolbar
Copyright 2023-2024 Foolbar
EhViewer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
-8
View File
@@ -1,8 +0,0 @@
/build
/src/main/java-gen
manifest-merger-release-report.txt
/src/main/libs
/src/main/obj
# Intellij
*.iml
/.cxx
+111 -156
View File
@@ -1,35 +1,28 @@
import com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
import com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import com.mikepenz.aboutlibraries.plugin.DuplicateMode
import com.mikepenz.aboutlibraries.plugin.DuplicateRule
import java.util.regex.Pattern
val isRelease: Boolean
get() = gradle.startParameter.taskNames.any { it.contains("Release") }
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.ehviewer.android.application)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.spotless)
alias(libs.plugins.aboutlibrariesPlugin)
alias(libs.plugins.rustAndroidPlugin)
alias(libs.plugins.composeCompilerReportGenerator)
alias(libs.plugins.aboutlibraries)
alias(libs.plugins.aboutlibrariesAndroid)
alias(libs.plugins.baselineprofile)
}
android {
compileSdk = 34
buildToolsVersion = "34.0.0"
ndkVersion = "26.1.10909125"
val supportedAbis = arrayOf("arm64-v8a", "x86_64", "armeabi-v7a")
android {
splits {
abi {
isEnable = true
reset()
if (isRelease) {
include("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
include(*supportedAbis)
isUniversalApk = true
} else {
include("arm64-v8a", "x86_64")
@@ -50,10 +43,9 @@ android {
commandLine = "git rev-parse --short=7 HEAD".split(' ')
}.standardOutput.asText.get().trim()
val buildTime by lazy {
val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm").withZone(ZoneOffset.UTC)
formatter.format(Instant.now())
}
val commitTime = providers.exec {
commandLine = "git log -1 --format=%ct".split(' ')
}.standardOutput.asText.get().trim()
val repoName = providers.exec {
commandLine = "git remote get-url origin".split(' ')
@@ -62,54 +54,30 @@ android {
defaultConfig {
applicationId = "moe.tarsin.ehviewer"
minSdk = 28
targetSdk = 34
versionCode = 180050
versionName = "1.10.3"
resourceConfigurations.addAll(
listOf(
"zh",
"zh-rCN",
"zh-rHK",
"zh-rTW",
"es",
"ja",
"ko",
"fr",
"de",
"th",
"tr",
"nb-rNO",
),
)
versionCode = 180063
versionName = "1.14.6"
// versionNameSuffix = "-SNAPSHOT"
buildConfigField("String", "RAW_VERSION_NAME", "\"$versionName${versionNameSuffix.orEmpty()}\"")
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
buildConfigField("long", "COMMIT_TIME", commitTime)
buildConfigField("String", "REPO_NAME", "\"$repoName\"")
ndk {
if (isRelease) {
abiFilters.addAll(supportedAbis)
}
debugSymbolLevel = "FULL"
}
}
flavorDimensions += listOf("api", "oss")
flavorDimensions += "api"
productFlavors {
create("default") {
dimension = "api"
minSdk = 26
}
create("marshmallow") {
dimension = "api"
minSdk = 23
applicationIdSuffix = ".m"
versionNameSuffix = "-M"
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
create("oss") {
dimension = "oss"
}
create("gms") {
dimension = "oss"
versionNameSuffix = "-gms"
}
}
@@ -119,45 +87,50 @@ android {
}
}
kotlinOptions {
freeCompilerArgs = listOf(
// https://kotlinlang.org/docs/compiler-reference.html#progressive
"-progressive",
"-Xjvm-default=all",
"-Xlambdas=indy",
"-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.paging.ExperimentalPagingApi",
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-opt-in=splitties.experimental.ExperimentalSplittiesApi",
"-opt-in=splitties.preferences.DataStorePreferencesPreview",
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:experimentalStrongSkipping=true",
)
}
lint {
disable += setOf("MissingTranslation", "MissingQuantity")
fatal += setOf("NewApi", "InlineApi")
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
packaging {
resources {
excludes += "/META-INF/**"
excludes += "/okhttp3/**"
excludes += "/kotlin/**"
excludes += "**.txt"
excludes += "**.bin"
dex {
useLegacyPackaging = false
}
jniLibs {
excludes += "**/libdatastore_shared_counter.so" // DataStore multi-process
}
resources {
// Required by Layout Inspector
pickFirsts += "/META-INF/androidx.compose.ui_ui.version"
excludes += listOf(
"/META-INF/**",
"/kotlin/**",
"**.txt",
"**.bin",
)
}
}
androidResources {
ignoreAssetsPatterns += listOf(
"!PublicSuffixDatabase.list", // OkHttp
"!composepreference.preference.generated.resources",
)
generateLocaleConfig = true
localeFilters += listOf(
"zh",
"zh-rCN",
"zh-rHK",
"zh-rTW",
"es",
"ja",
"ko",
"fr",
"de",
"th",
"tr",
"nb-rNO",
)
}
dependenciesInfo.includeInApk = false
@@ -168,149 +141,131 @@ android {
isShrinkResources = true
proguardFiles("proguard-rules.pro")
signingConfig = signConfig
buildConfigField("String", "BUILD_TIME", "\"$buildTime\"")
}
debug {
applicationIdSuffix = ".debug"
buildConfigField("String", "BUILD_TIME", "\"\"")
}
create("benchmarkRelease") {
initWith(buildTypes.getByName("release"))
matchingFallbacks += listOf("release")
applicationIdSuffix = ".benchmark"
signingConfig = signingConfigs.getByName("debug")
isDebuggable = false
}
}
buildFeatures {
buildConfig = true
compose = true
viewBinding = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
namespace = "com.hippo.ehviewer"
}
baselineProfile {
mergeIntoMain = true
}
dependencies {
implementation(projects.core.data)
implementation(projects.core.i18n)
implementation(projects.core.ui)
// https://developer.android.com/jetpack/androidx/releases/activity
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.biometric)
implementation(libs.androidx.browser)
// https://developer.android.com/jetpack/androidx/releases/compose-material3
api(platform(libs.compose.bom))
implementation(libs.bundles.compose)
implementation(libs.androidx.webkit)
implementation(libs.compose.destinations.core)
ksp(libs.compose.destinations.compiler)
implementation(libs.compose.preference) {
// R8 won't remove it because it adds a content provider
exclude(group = "org.jetbrains.compose.components", module = "components-resources")
}
implementation(libs.androidx.core)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.constraintlayout.compose)
implementation(libs.androidx.datastore)
// https://developer.android.com/jetpack/androidx/releases/lifecycle
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.lifecycle.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
// https://developer.android.com/jetpack/androidx/releases/navigation
implementation(libs.bundles.androidx.navigation)
// https://developer.android.com/jetpack/androidx/releases/paging
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.recyclerview)
// https://developer.android.com/jetpack/androidx/releases/room
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.paging)
implementation(libs.androidx.work.runtime)
implementation(libs.photoview) // Dead Dependency
implementation(libs.directionalviewpager) // Dead Dependency
implementation(libs.material)
implementation(libs.material.motion.core)
implementation(libs.material.kolor)
implementation(libs.bundles.splitties)
// https://square.github.io/okhttp/changelogs/changelog/
implementation(platform(libs.okhttp.bom))
implementation(libs.bundles.okhttp)
implementation(libs.okio.jvm)
implementation(libs.logcat)
implementation(libs.diff)
implementation(libs.aboutlibraries.core)
implementation(libs.aboutlibraries.compose.m3)
implementation(libs.insetter) // Dead Dependency
implementation(libs.accompanist.drawable.painter)
implementation(libs.reorderable)
implementation(platform(libs.arrow.stack))
implementation(libs.arrow.fx.coroutines)
implementation(libs.bundles.arrow)
// https://coil-kt.github.io/coil/changelog/
implementation(platform(libs.coil.bom))
implementation(libs.bundles.coil)
implementation(libs.bundles.ktor)
implementation(libs.telephoto.zoomable)
implementation(libs.ktor.client.okhttp)
implementation(libs.bundles.kotlinx.serialization)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.datetime)
implementation(libs.jsoup)
coreLibraryDesugaring(libs.desugar)
"gmsImplementation"(libs.bundles.cronet)
}
implementation(libs.androidx.profileinstaller)
"baselineProfile"(project(":benchmark"))
configurations.all {
resolutionStrategy {
exclude("androidx.navigation", "navigation-compose")
}
debugImplementation(libs.compose.ui.tooling)
implementation(libs.compose.ui.tooling.preview)
}
kotlin {
jvmToolchain(21)
compilerOptions {
optIn.addAll(
"coil3.annotation.ExperimentalCoilApi",
"androidx.paging.ExperimentalPagingApi",
"me.saket.telephoto.ExperimentalTelephotoApi",
)
}
}
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.generateKotlin", "true")
arg("compose-destinations.codeGenPackageName", "com.hippo.ehviewer.ui")
}
aboutLibraries {
duplicationMode = MERGE
duplicationRule = GROUP
}
cargo {
module = "src/main/rust"
libname = "ehviewer_rust"
targets = if (isRelease) listOf("arm", "x86", "arm64", "x86_64") else listOf("arm64", "x86_64")
if (isRelease) profile = "release"
}
val ktlintVersion = libs.versions.ktlint.get()
spotless {
kotlin {
// https://github.com/diffplug/spotless/issues/111
target("src/**/*.kt")
ktlint(ktlintVersion)
collect {
includePlatform = false
}
kotlinGradle {
ktlint(ktlintVersion)
}
}
tasks.configureEach {
if (name.startsWith("merge") && name.endsWith("JniLibFolders")) {
dependsOn("cargoBuild")
// fix mergeDebugJniLibFolders UP-TO-DATE
inputs.dir(layout.buildDirectory.dir("rustJniLibs/android"))
library {
exclusionPatterns.add(Pattern.compile("org\\.jetbrains\\.(?:compose|androidx)\\..*"))
duplicationMode = DuplicateMode.MERGE
duplicationRule = DuplicateRule.GROUP
}
}
+9 -1
View File
@@ -11,10 +11,18 @@
public static final ** CREATOR;
}
-keepclassmembers,allowobfuscation class com.hippo.ehviewer.coil.AnimatedWebPDrawable {
java.nio.ByteBuffer source;
}
# Ktor logger
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn org.conscrypt.Conscrypt
# https://issuetracker.google.com/222232895
-dontwarn androidx.window.extensions.**
-dontwarn androidx.window.sidecar.Sidecar*
-keepattributes LineNumberTable
-allowaccessmodification
-repackageclasses
@@ -1,20 +0,0 @@
package com.hippo.ehviewer.cronet
import com.hippo.ehviewer.Settings
import okio.Path.Companion.toOkioPath
import org.chromium.net.CronetEngine
import splitties.init.appCtx
val cronetHttpClient: CronetEngine = CronetEngine.Builder(appCtx).apply {
enableBrotli(true)
setUserAgent(Settings.userAgent)
// Cache Quic hint only since the real cache mechanism should on Ktor layer
val cache = (appCtx.cacheDir.toOkioPath() / "http_cache").toFile().apply { mkdirs() }
setStoragePath(cache.absolutePath)
enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 4096)
addQuicHint("e-hentai.org", 443, 443)
addQuicHint("forums.e-hentai.org", 443, 443)
addQuicHint("exhentai.org", 443, 443)
}.build()
@@ -1,176 +0,0 @@
package com.hippo.ehviewer.ktor
import com.hippo.ehviewer.cronet.cronetDispatcher
import com.hippo.ehviewer.cronet.cronetHttpClient
import com.hippo.ehviewer.cronet.cronetHttpClientExecutor
import com.hippo.ehviewer.cronet.pool
import io.ktor.client.engine.HttpClientEngineBase
import io.ktor.client.engine.HttpClientEngineConfig
import io.ktor.client.engine.callContext
import io.ktor.client.request.HttpRequestData
import io.ktor.client.request.HttpResponseData
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpProtocolVersion
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.OutgoingContent
import io.ktor.http.headers
import io.ktor.util.date.GMTDate
import io.ktor.util.flattenForEach
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.InternalAPI
import io.ktor.utils.io.pool.useInstance
import io.ktor.utils.io.writer
import java.nio.ByteBuffer
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import org.chromium.net.CronetException
import org.chromium.net.UploadDataProvider
import org.chromium.net.UploadDataSink
import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
object CronetEngine : HttpClientEngineBase("Cronet") {
override val config = HttpClientEngineConfig()
override val dispatcher = cronetDispatcher
@InternalAPI
override suspend fun execute(data: HttpRequestData): HttpResponseData {
val callContext = callContext()
return executeHttpRequest(callContext, data)
}
@OptIn(DelicateCoroutinesApi::class)
private suspend fun executeHttpRequest(
callContext: CoroutineContext,
data: HttpRequestData,
): HttpResponseData = suspendCancellableCoroutine { continuation ->
val requestTime = GMTDate()
val callback = object : UrlRequest.Callback() {
lateinit var chunkChan: Channel<ByteBuffer>
override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newLocationUrl: String) {
continuation.resume(
info.toHttpResponseData(
requestTime = requestTime,
callContext = callContext,
),
)
}
override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
val channel = GlobalScope.writer(callContext) {
pool.useInstance {
chunkChan = Channel(1)
request.read(it)
chunkChan.consumeEach { buffer ->
buffer.flip()
channel.writeFully(buffer)
buffer.clear()
request.read(buffer)
}
}
}.channel
continuation.resume(
info.toHttpResponseData(
requestTime = requestTime,
callContext = callContext,
responseBody = channel,
),
)
}
override fun onReadCompleted(request: UrlRequest, info: UrlResponseInfo, byteBuffer: ByteBuffer) {
chunkChan.trySend(byteBuffer).getOrThrow()
}
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
chunkChan.close()
}
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
if (::chunkChan.isInitialized) {
chunkChan.close(error)
} else {
continuation.resumeWithException(error)
}
}
}
val request = cronetHttpClient.newUrlRequestBuilder(data.url.toString(), callback, cronetHttpClientExecutor).apply {
setHttpMethod(data.method.value)
data.headers.flattenForEach { key, value -> addHeader(key, value) }
data.body.contentType?.let { addHeader(HttpHeaders.ContentType, it.toString()) }
data.body.contentLength?.let { addHeader(HttpHeaders.ContentLength, it.toString()) }
data.body.toUploadDataProvider(callContext)?.let { setUploadDataProvider(it, cronetHttpClientExecutor) }
}.build()
request.start()
callContext[Job]!!.invokeOnCompletion { request.cancel() }
continuation.invokeOnCancellation { request.cancel() }
}
}
private fun UrlResponseInfo.toHttpResponseData(
requestTime: GMTDate,
callContext: CoroutineContext,
responseBody: ByteReadChannel = ByteReadChannel.Empty,
): HttpResponseData {
return HttpResponseData(
statusCode = HttpStatusCode.fromValue(httpStatusCode),
requestTime = requestTime,
headers = headers {
allHeaders.forEach { (key, value) ->
appendAll(key, value)
}
},
version = when (negotiatedProtocol) {
"h2" -> HttpProtocolVersion.HTTP_2_0
"h3" -> HttpProtocolVersion.QUIC
"quic/1+spdy/3" -> HttpProtocolVersion.SPDY_3
else -> HttpProtocolVersion.HTTP_1_1
},
body = responseBody,
callContext = callContext,
)
}
@OptIn(DelicateCoroutinesApi::class)
private fun OutgoingContent.toUploadDataProvider(callContext: CoroutineContext): UploadDataProvider? = when (this) {
is OutgoingContent.NoContent -> null
is OutgoingContent.ByteArrayContent -> object : UploadDataProvider() {
val buffer = ByteBuffer.wrap(bytes()).slice()
override fun getLength() = buffer.limit().toLong()
override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) {
byteBuffer.put(buffer)
uploadDataSink.onReadSucceeded(false)
}
override fun rewind(uploadDataSink: UploadDataSink) {
buffer.position(0)
uploadDataSink.onRewindSucceeded()
}
}
is OutgoingContent.WriteChannelContent -> object : UploadDataProvider() {
fun launch() = GlobalScope.writer(callContext) { writeTo(channel) }.channel
var chan = launch()
override fun getLength() = contentLength!!
override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) {
GlobalScope.launch(callContext) {
chan.readAvailable(byteBuffer)
uploadDataSink.onReadSucceeded(false)
}
}
override fun rewind(uploadDataSink: UploadDataSink) {
chan = launch()
uploadDataSink.onRewindSucceeded()
}
}
else -> error("UnsupportedContentType $this")
}
@@ -1,12 +0,0 @@
package com.hippo.ehviewer.util
import com.google.android.gms.net.CronetProviderInstaller
import org.chromium.net.CronetProvider
import splitties.init.appCtx
val isCronetAvailable: Boolean
get() = CronetProvider.getAllProviders(appCtx).any { it.isEnabled }.also {
if (!it) {
CronetProviderInstaller.installProvider(appCtx)
}
}
+34 -40
View File
@@ -29,7 +29,6 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="@bool/large_heap"
android:localeConfig="@xml/locale_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
@@ -40,7 +39,7 @@
<activity
android:name=".ui.MainActivity"
android:banner="@mipmap/ic_leanback_banner"
android:configChanges="screenSize"
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/AppTheme.Splash"
@@ -71,6 +70,39 @@
<data android:host="exhentai.org" />
<data android:host="e-hentai.org" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/zip" />
<data android:mimeType="application/x-7z-compressed" />
<data android:mimeType="application/x-tar" />
<data android:mimeType="application/x-xz" />
<data android:mimeType="application/gzip" />
<data android:mimeType="application/vnd.rar" />
<data android:mimeType="application/vnd.comicbook+zip" />
<data android:mimeType="application/vnd.comicbook-rar" />
<!-- unofficial -->
<data android:mimeType="application/7z" />
<data android:mimeType="application/rar" />
<data android:mimeType="application/x-cbz" />
<data android:mimeType="application/x-cbr" />
<data android:mimeType="application/x-cbt" />
<data android:mimeType="application/x-cb7" />
<data android:mimeType="application/x-zip-compressed" />
<data android:mimeType="application/x-rar-compressed" />
<data android:mimeType="application/x-compressed-tar" />
<data android:mimeType="application/x-bzip-compressed-tar" />
<data android:mimeType="application/x-lzma-compressed-tar" />
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
<intent-filter android:label="@string/keyword_search">
<action android:name="android.intent.action.SEND" />
@@ -86,44 +118,6 @@
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/7z" />
<data android:mimeType="application/rar" />
<data android:mimeType="application/zip" />
<data android:mimeType="application/x-7z-compressed" />
<data android:mimeType="application/x-rar-compressed" />
<data android:mimeType="application/x-zip-compressed" />
<data android:mimeType="application/x-tar" />
<data android:mimeType="application/x-xz" />
<data android:mimeType="application/gzip" />
<data android:mimeType="application/x-cbz" />
<data android:mimeType="application/x-cbr" />
<data android:mimeType="application/x-cbt" />
<data android:mimeType="application/x-cb7" />
<!-- untested -->
<data android:mimeType="application/vnd.comicbook+zip" />
<data android:mimeType="application/vnd.comicbook-rar" />
<data android:mimeType="application/x-compressed-tar" />
<data android:mimeType="application/x-bzip-compressed-tar" />
<data android:mimeType="application/x-lzma-compressed-tar" />
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
</activity>
<activity
android:name=".ui.SecurityActivity"
android:configChanges="screenSize"
@@ -0,0 +1,25 @@
From e971b9f23727833cc39b9e325db30f383e5bfc30 Mon Sep 17 00:00:00 2001
From: Dude so hot <djohn@fbi.gov>
Date: Thu, 14 Nov 2024 00:28:13 +0800
Subject: [PATCH] Fix zip_time performance
Signed-off-by: Dude so hot <djohn@fbi.gov>
---
libarchive/archive_time.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/libarchive/archive_time.c b/libarchive/archive_time.c
index 3352c809..9289bf1e 100644
--- a/libarchive/archive_time.c
+++ b/libarchive/archive_time.c
@@ -53,6 +53,7 @@ FILETIME_to_ntfs(const FILETIME* filetime)
int64_t
dos_to_unix(uint32_t dos_time)
{
+ return 0;
uint16_t msTime, msDate;
struct tm ts;
time_t t;
--
2.47.0
@@ -0,0 +1,27 @@
From 86d199429b3fabc83f7dcc9afd72e7b4c7750b1a Mon Sep 17 00:00:00 2001
From: FooIbar <118464521+FooIbar@users.noreply.github.com>
Date: Thu, 14 Nov 2024 20:22:52 +0800
Subject: [PATCH] Use UTF-8 as default charset on bionic
---
libarchive/archive_string.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/libarchive/archive_string.c b/libarchive/archive_string.c
index abf7ad66..63a73c58 100644
--- a/libarchive/archive_string.c
+++ b/libarchive/archive_string.c
@@ -423,7 +423,9 @@ static const char *
default_iconv_charset(const char *charset) {
if (charset != NULL && charset[0] != '\0')
return charset;
-#if HAVE_LOCALE_CHARSET && !defined(__APPLE__)
+#ifdef __BIONIC__
+ return "UTF-8";
+#elif HAVE_LOCALE_CHARSET && !defined(__APPLE__)
/* locale_charset() is broken on Mac OS */
return locale_charset();
#elif HAVE_NL_LANGINFO
--
2.43.0
+122 -40
View File
@@ -1,48 +1,77 @@
cmake_minimum_required(VERSION 3.14)
project(ehviewer C ASM)
project(ehviewer C)
include(ExternalProject)
include(FetchContent)
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -funroll-loops -flto \
-mllvm -polly \
-mllvm -polly-run-dce \
-mllvm -polly-run-inliner \
-mllvm -polly-isl-arg=--no-schedule-serialize-sccs \
-mllvm -polly-ast-use-context \
-mllvm -polly-detect-keep-going \
-mllvm -polly-position=before-vectorizer \
-mllvm -polly-vectorizer=stripmine \
-mllvm -polly-detect-profitability-min-per-loop-insts=40 \
-mllvm -polly-invariant-load-hoisting")
string(CONCAT CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -ffast-math -fvisibility=hidden -fvisibility-inlines-hidden -funroll-loops -flto "
"-ffile-prefix-map=${ANDROID_TOOLCHAIN_ROOT}= "
"-ffile-prefix-map=${CMAKE_SOURCE_DIR}= "
"-ffile-prefix-map=${CMAKE_BINARY_DIR}= "
"-mllvm -polly "
"-mllvm -polly-run-dce "
"-mllvm -polly-run-inliner "
"-mllvm -polly-isl-arg=--no-schedule-serialize-sccs "
"-mllvm -polly-ast-use-context "
"-mllvm -polly-detect-keep-going "
"-mllvm -polly-position=before-vectorizer "
"-mllvm -polly-vectorizer=stripmine "
"-mllvm -polly-detect-profitability-min-per-loop-insts=40 "
"-mllvm -polly-invariant-load-hoisting")
endif (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
# Change the default path of fetch content to avoid downloading the same dependencies per architecture
get_filename_component(deps "../_deps" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
set(FETCHCONTENT_BASE_DIR ${deps})
set(BUILD_TESTING 0)
FetchContent_Declare(liblzma
GIT_REPOSITORY https://github.com/FooIbar/xz.git
GIT_TAG v5.4.5
)
option(BUILD_TESTING OFF)
option(XZ_DOC OFF)
option(XZ_LZIP_DECODER OFF)
option(XZ_MICROLZMA_DECODER OFF)
option(XZ_MICROLZMA_ENCODER OFF)
option(XZ_TOOL_LZMADEC OFF)
option(XZ_TOOL_LZMAINFO OFF)
option(XZ_TOOL_XZ OFF)
option(XZ_TOOL_XZDEC OFF)
FetchContent_Declare(
liblzma
GIT_REPOSITORY https://github.com/tukaani-project/xz.git
GIT_TAG v5.8.1
GIT_SHALLOW 1
)
FetchContent_MakeAvailable(liblzma)
include_directories(${liblzma_SOURCE_DIR}/src/liblzma/api)
# Build GNUTLS libnettle
FetchContent_Declare(nettle
URL https://ftp.gnu.org/gnu/nettle/nettle-3.9.1.tar.gz
URL_MD5 29fcd2dec6bf5b48e5e3ffb3cbc4779e
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/nettle/nettle
)
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
set(NDK_VERSION "${ANDROID_NDK_MAJOR}.${ANDROID_NDK_MINOR}.${ANDROID_NDK_BUILD}")
set(TOOLCHAIN "$ANDROID_HOME/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/linux-x86_64/bin")
set(WSL wsl)
else ()
set(TOOLCHAIN "${ANDROID_TOOLCHAIN_ROOT}/bin")
endif ()
set(CONFIGURE_COMMAND ./configure --disable-public-key --disable-dependency-tracking
"--host=${CMAKE_C_COMPILER_TARGET}"
"CFLAGS=${CMAKE_C_FLAGS} -Qunused-arguments"
"AR=${TOOLCHAIN}/llvm-ar"
"CC=${TOOLCHAIN}/clang --target=${CMAKE_C_COMPILER_TARGET}"
"RANLIB=${TOOLCHAIN}/llvm-ranlib"
)
ExternalProject_Add(
nettle
URL https://ftpmirror.gnu.org/nettle/nettle-3.10.2.tar.gz
URL_MD5 b28bcbf6f045ff007940a9401673600d
CONFIGURE_COMMAND ${WSL} ${CONFIGURE_COMMAND}
BUILD_COMMAND ${WSL} make -j4 libnettle.a
INSTALL_COMMAND ""
BUILD_IN_SOURCE 1
BUILD_BYPRODUCTS <SOURCE_DIR>/libnettle.a
)
FetchContent_MakeAvailable(nettle)
add_subdirectory(nettle)
ExternalProject_Get_Property(nettle SOURCE_DIR)
include_directories(${SOURCE_DIR}/..)
add_library(libnettle STATIC IMPORTED)
set_property(TARGET libnettle PROPERTY IMPORTED_LOCATION ${SOURCE_DIR}/libnettle.a)
add_dependencies(libnettle nettle)
# Configure libnettle support for libarchive
include_directories(nettle)
include_directories(.)
set(HAVE_LIBNETTLE 1)
set(HAVE_NETTLE_AES_H 1)
set(HAVE_NETTLE_HMAC_H 1)
@@ -58,23 +87,76 @@ SET(HAVE_LZMA_STREAM_ENCODER_MT 1)
SET(HAVE_LZMADEC_H 1)
SET(HAVE_LIBLZMADEC 1)
option(ENABLE_OPENSSL OFF)
option(ENABLE_TAR OFF)
option(ENABLE_CPIO OFF)
option(ENABLE_CAT OFF)
option(ENABLE_UNZIP OFF)
option(ENABLE_TEST OFF)
# Configure libarchive link's static lib
SET(LIBARCHIVE_CUSTOM_LIBS "nettle" "liblzma")
FetchContent_Declare(libarchive
GIT_REPOSITORY https://github.com/FooIbar/libarchive.git
GIT_TAG v3.7.2
)
set(LIBARCHIVE_PATCH
${CMAKE_CURRENT_LIST_DIR}/0001-Fix-zip_time-performance.patch
${CMAKE_CURRENT_LIST_DIR}/0002-Use-UTF-8-as-default-charset-on-bionic.patch
)
FetchContent_Declare(
libarchive
GIT_REPOSITORY https://github.com/libarchive/libarchive.git
GIT_TAG v3.8.4
GIT_SHALLOW 1
PATCH_COMMAND git apply --check -R ${LIBARCHIVE_PATCH} || git apply ${LIBARCHIVE_PATCH}
)
FetchContent_MakeAvailable(libarchive)
target_link_libraries(archive_static liblzma libnettle)
include_directories(${libarchive_SOURCE_DIR}/libarchive)
FetchContent_Declare(
libwebp
GIT_REPOSITORY https://github.com/webmproject/libwebp.git
GIT_TAG v1.6.0
GIT_SHALLOW 1
)
option(WEBP_BUILD_ANIM_UTILS OFF)
option(WEBP_BUILD_CWEBP OFF)
option(WEBP_BUILD_DWEBP OFF)
option(WEBP_BUILD_VWEBP OFF)
option(WEBP_BUILD_WEBPINFO OFF)
option(WEBP_BUILD_LIBWEBPMUX OFF)
option(WEBP_BUILD_EXTRAS OFF)
option(WEBP_ENABLE_SWAP_16BIT_CSP ON)
# Disable runtime detection of NEON support as NEON is required since API level 23
# https://source.android.com/docs/compatibility/6.0/android-6.0-cdd#3_3_1_application_binary_interfaces
unset(ANDROID)
FetchContent_MakeAvailable(libwebp)
if (CMAKE_SYSTEM_NAME STREQUAL "Android")
set(ANDROID 1)
endif ()
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ../../../../rust-toolchain.toml)
unset(Rust_TOOLCHAIN CACHE)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.6
GIT_SHALLOW 1
)
FetchContent_MakeAvailable(Corrosion)
corrosion_import_crate(
MANIFEST_PATH ../rust/Cargo.toml
LOCKED
FEATURES $<IF:$<VERSION_GREATER_EQUAL:${ANDROID_NATIVE_API_LEVEL},26>,android-26,android>
FLAGS $<$<NOT:$<CONFIG:Debug>>:-Z build-std -Z build-std-features=>
)
corrosion_add_target_rustflags(ehviewer_rust $<$<NOT:$<CONFIG:Debug>>:-Z unstable-options -C panic=immediate-abort -Z location-detail=none -Z fmt-debug=none>)
corrosion_link_libraries(ehviewer_rust android jnigraphics log webp webpdemux)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED archive.c border.c gifutils.c natsort/strnatcmp.c)
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics)
add_library(${PROJECT_NAME} SHARED archive.c gifutils.c hash.c natsort/strnatcmp.c)
target_link_libraries(
${PROJECT_NAME} archive_static ehviewer_rust
-Wl,-u,JNI_OnLoad -Wl,--undefined-glob,Java_*
-Wl,--version-script,${CMAKE_SOURCE_DIR}/libehviewer.map.txt
)
+225 -223
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Tarsin Norbin
* Copyright 2022-2024 Tarsin Norbin
*
* This file is part of EhViewer
*
@@ -34,7 +34,6 @@
#include "natsort/strnatcmp.h"
#include "ehviewer.h"
#include "spinlock.h"
typedef struct {
int using;
@@ -46,33 +45,29 @@ typedef struct {
typedef struct {
const char *filename;
int index;
size_t size;
ssize_t size;
void *addr;
} entry;
_Atomic mcs_lock ctx_lock;
static archive_ctx **ctx_pool;
#define CTX_POOL_SIZE 20
#define MAX_PARALLEL_DECOMP 4
#define max(a, b) ((a) > (b) ? (a) : (b))
static void *mempool = NULL;
static size_t *mempoolofs = NULL;
#define PAGE_ALIGN(x) ((x + ~PAGE_MASK) & PAGE_MASK)
#define MEMPOOL_ADDR_BY_SORTED_IDX(x) (mempool + (index ? mempoolofs[index - 1] : 0))
#define MEMPOOL_SIZE (mempoolofs[entryCount - 1])
#define MEMPOOL_ENTITY_SIZE(x) PAGE_ALIGN(entries[x].size)
#define PROT_RW (PROT_WRITE | PROT_READ)
#define MAP_ANON_POOL (MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_UNINITIALIZED)
static pthread_mutex_t ctx_pool_mutex = PTHREAD_MUTEX_INITIALIZER;
static archive_ctx **ctx_pool = NULL;
static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
static void *decode_buffer[MAX_PARALLEL_DECOMP];
static bool need_encrypt = false;
static char *passwd = NULL;
static void *archiveAddr = NULL;
static void *archiveAddr = MAP_FAILED;
static size_t archiveSize = 0;
static entry *entries = NULL;
static size_t entryCount = 0;
static ssize_t max_file_size = 0;
const char supportExt[10][6] = {
#define SUPPORT_EXT_COUNT 11
const char supportExt[SUPPORT_EXT_COUNT][5] = {
"jpeg",
"jpg",
"png",
@@ -81,6 +76,7 @@ const char supportExt[10][6] = {
"bmp",
"ico",
"wbmp",
"heic",
"heif",
"avif"
};
@@ -92,84 +88,92 @@ static inline int filename_is_playable_file(const char *name) {
if (!dotptr++)
return false;
int i;
for (i = 0; i < 10; i++)
for (i = 0; i < SUPPORT_EXT_COUNT; i++)
if (strcmp(dotptr, supportExt[i]) == 0)
return true;
return false;
}
static inline bool archive_entry_is_file(struct archive_entry *entry) {
return archive_entry_filetype(entry) == AE_IFREG;
}
static inline bool archive_entry_is_playable(struct archive_entry *entry) {
return archive_entry_is_file(entry) &&
filename_is_playable_file(archive_entry_pathname(entry));
}
static inline int compare_entries(const void *a, const void *b) {
const char *fa = ((entry *) a)->filename;
const char *fb = ((entry *) b)->filename;
return strnatcmp(fa, fb);
}
static long archive_map_entries_index(archive_ctx *ctx) {
long count = 0;
#define ADDR_IN_FILE_MAPPING(addr) (addr >= archiveAddr && addr < archiveAddr + archiveSize)
static bool fill_entry_zero_copy(struct archive *arc, entry *entry) {
void *buffer = NULL;
size_t buffer_size = 0;
la_int64_t output_ofs = 0;
archive_read_data_block(arc, (const void **) &buffer, &buffer_size, &output_ofs);
bool zero_copy = ADDR_IN_FILE_MAPPING(buffer) && !output_ofs && buffer_size == entry->size;
entry->addr = zero_copy ? buffer : NULL;
return zero_copy;
}
static void archive_map_entries_index(archive_ctx *ctx, bool sort) {
int count = 0;
bool zero_copy = true;
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
const char *name = archive_entry_pathname_utf8(ctx->entry);
if (filename_is_playable_file(name)) {
const char *name = archive_entry_pathname(ctx->entry);
if (archive_entry_is_file(ctx->entry) && filename_is_playable_file(name)) {
entries[count].filename = strdup(name);
entries[count].index = count;
entries[count].size = archive_entry_size(ctx->entry);
ssize_t size = archive_entry_size(ctx->entry);
max_file_size = max(size, max_file_size);
entries[count].size = size;
// We don't expect zero copy if first content can't do zero copy
if (zero_copy) zero_copy = fill_entry_zero_copy(ctx->arc, &entries[count]);
count++;
}
}
if (!count)
LOGE("%s", archive_error_string(ctx->arc));
qsort(entries, entryCount, sizeof(entry), compare_entries);
return count;
if (sort) qsort(entries, entryCount, sizeof(entry), compare_entries);
}
static bool archive_prealloc_mempool() {
mempoolofs = calloc(entryCount, sizeof(size_t));
for (int i = 0; i < entryCount; ++i) {
if (!i)
mempoolofs[i] = PAGE_ALIGN(entries[i].size);
else
mempoolofs[i] = PAGE_ALIGN(entries[i].size) + mempoolofs[i - 1];
static void *acquire_decode_buffer() {
void *addr = NULL;
pthread_mutex_lock(&buffer_mutex);
for (int i = 0; i < MAX_PARALLEL_DECOMP; ++i) {
addr = decode_buffer[i];
if (addr) {
decode_buffer[i] = NULL;
break;
}
}
mempool = mmap(0, MEMPOOL_SIZE, PROT_RW, MAP_ANON_POOL, -1, 0);
if (mempool == MAP_FAILED) {
LOGE("%s%s", "mmap failed with error ", strerror(errno));
return false;
pthread_mutex_unlock(&buffer_mutex);
if (!addr) addr = malloc(max_file_size);
return addr;
}
static void release_decode_buffer(void *buffer) {
pthread_mutex_lock(&buffer_mutex);
for (int i = 0; i < MAX_PARALLEL_DECOMP; ++i) {
void *addr = decode_buffer[i];
if (!addr) {
decode_buffer[i] = buffer;
pthread_mutex_unlock(&buffer_mutex);
return;
}
}
return true;
pthread_mutex_unlock(&buffer_mutex);
free(buffer);
}
// Mincore does not support anon mapping yet
// Now that we use MADV_FREE, how can we detect whether pages is reclaimed?
static bool is_mempool_pages_present_and_lock(int index) {
void *addr = MEMPOOL_ADDR_BY_SORTED_IDX(index);
size_t size = MEMPOOL_ENTITY_SIZE(index);
mlock(addr, size);
#if 0
unsigned char vec[size / PAGE_SIZE];
unsigned char r = 1;
mincore(addr, size, vec);
for (int i = 0; i < size / PAGE_SIZE; ++i) r &= vec[i];
return r;
#endif
return false;
}
#define MEMPOOL_IN_CORE_AND_LOCK(x) is_mempool_pages_present_and_lock(x)
static void mempool_release_pages(void *addr, size_t size) {
size = PAGE_ALIGN(size);
munlock(addr, size);
int ret = madvise(addr, size, MADV_FREE);
if (ret == -EINVAL) madvise(addr, size, MADV_DONTNEED);
}
static long archive_list_all_entries(archive_ctx *ctx) {
long count = 0;
static int archive_list_all_entries(archive_ctx *ctx) {
int count = 0;
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
if (filename_is_playable_file(archive_entry_pathname_utf8(ctx->entry)))
if (archive_entry_is_playable(ctx->entry))
count++;
if (!count)
LOGE("%s", archive_error_string(ctx->arc));
return count;
}
@@ -181,18 +185,10 @@ static void archive_release_ctx(archive_ctx *ctx) {
}
}
static int archive_alloc_ctx(archive_ctx **ctxptr) {
archive_ctx *ctx;
int err;
ctx = calloc(1, sizeof(archive_ctx));
if (!ctx)
return -ENOMEM;
static archive_ctx *archive_alloc_ctx() {
archive_ctx *ctx = calloc(1, sizeof(archive_ctx));
ctx->arc = archive_read_new();
ctx->using = 1;
if (!ctx->arc) {
free(ctx);
return -ENOMEM;
}
archive_read_support_format_tar(ctx->arc);
archive_read_support_format_7zip(ctx->arc);
archive_read_support_format_rar5(ctx->arc);
@@ -202,19 +198,19 @@ static int archive_alloc_ctx(archive_ctx **ctxptr) {
archive_read_set_option(ctx->arc, "zip", "ignorecrc32", "1");
if (passwd)
archive_read_add_passphrase(ctx->arc, passwd);
err = archive_read_open_memory(ctx->arc, archiveAddr, archiveSize);
if (err) {
int err = archive_read_open_memory(ctx->arc, archiveAddr, archiveSize);
if (err < ARCHIVE_OK) {
LOGE("%s%s", "Open archive failed: ", archive_error_string(ctx->arc));
archive_read_free(ctx->arc);
free(ctx);
return err;
return NULL;
}
*ctxptr = ctx;
return 0;
return ctx;
}
static int archive_skip_to_index(archive_ctx *ctx, int index) {
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname_utf8(ctx->entry)))
if (!archive_entry_is_playable(ctx->entry))
continue;
if (ctx->next_index++ == index) {
return ctx->next_index - 1;
@@ -226,8 +222,7 @@ static int archive_skip_to_index(archive_ctx *ctx, int index) {
static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
int ret;
archive_ctx *ctx = NULL;
mcs_lock_t local_lock;
lock_mcs(&ctx_lock, &local_lock);
pthread_mutex_lock(&ctx_pool_mutex);
for (int i = 0; i < CTX_POOL_SIZE; i++) {
if (!ctx_pool[i])
continue;
@@ -242,16 +237,14 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
}
if (ctx)
ctx->using = 1;
unlock_mcs(&ctx_lock, &local_lock);
pthread_mutex_unlock(&ctx_pool_mutex);
if (!ctx) {
archive_ctx *victimCtx = NULL;
int victimIdx = 0;
int replace = 1;
ret = archive_alloc_ctx(&ctx);
if (ret)
return ret;
lock_mcs(&ctx_lock, &local_lock);
ctx = archive_alloc_ctx();
pthread_mutex_lock(&ctx_pool_mutex);
for (int i = 0; i < CTX_POOL_SIZE; i++) {
if (!ctx_pool[i]) {
ctx_pool[i] = ctx;
@@ -266,13 +259,13 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
}
}
if (replace) ctx_pool[victimIdx] = ctx;
unlock_mcs(&ctx_lock, &local_lock);
pthread_mutex_unlock(&ctx_pool_mutex);
if (replace) archive_release_ctx(victimCtx);
}
ret = archive_skip_to_index(ctx, idx);
if (ret != idx) {
ret = archive_errno(ctx->arc);
LOGE("Skip to index failed:%s", archive_error_string(ctx->arc));
LOGE("Skip to index failed: %s", archive_error_string(ctx->arc));
archive_release_ctx(ctx);
return ret;
}
@@ -281,132 +274,118 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
}
JNIEXPORT jint JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_openArchive(JNIEnv *env, jclass thiz, jint fd,
jlong size) {
Java_com_hippo_ehviewer_jni_ArchiveKt_openArchive(JNIEnv *env, jclass thiz, jint fd, jlong size, jboolean sort_entries) {
EH_UNUSED(env);
EH_UNUSED(thiz);
archive_ctx *ctx = NULL;
int mmap_flags = MAP_PRIVATE;
archiveAddr = mmap64(0, size, PROT_READ, mmap_flags, fd, 0);
archiveAddr = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (archiveAddr == MAP_FAILED) {
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
LOGE("%s%s", "mmap failed with error ", strerror(errno));
return 0;
}
archiveSize = size;
madvise_log_if_error(archiveAddr, archiveSize, MADV_WILLNEED);
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
if (!ctx_pool) {
LOGE("Allocate archive ctx pool failed:ENOMEM");
ctx = archive_alloc_ctx();
if (!ctx) return 0;
entryCount = archive_list_all_entries(ctx);
LOGI("%s%zu%s", "Found ", entryCount, " images in archive");
if (!entryCount) {
LOGE("%s%s", "Archive read failed: ", archive_error_string(ctx->arc));
archive_release_ctx(ctx);
return 0;
}
long r = archive_alloc_ctx(&ctx);
if (r) {
if (r == -ENOMEM)
LOGE("%s", "Mem alloc failed");
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
} else {
entryCount = archive_list_all_entries(ctx);
LOGI("%s%zu%s", "Found ", entryCount, " image entries in archive");
// We must read through the file|vm then we can know whether it is encrypted
int encryptRet = archive_read_has_encrypted_entries(ctx->arc);
switch (encryptRet) {
case 1: // At lease 1 encrypted entry
need_encrypt = true;
break;
case 0: // format supports but no encrypted entry found
default:
need_encrypt = false;
}
int format = archive_format(ctx->arc);
switch (format) {
case ARCHIVE_FORMAT_ZIP:
case ARCHIVE_FORMAT_RAR_V5:
madvise_log_if_error(archiveAddr, archiveSize, MADV_SEQUENTIAL);
break;
case ARCHIVE_FORMAT_7ZIP: // Seek is bad
madvise_log_if_error(archiveAddr, archiveSize, MADV_RANDOM);
break;
default:;
}
// We must read through the file|vm then we can know whether it is encrypted
int encryptRet = archive_read_has_encrypted_entries(ctx->arc);
switch (encryptRet) {
case 1: // At lease 1 encrypted entry
need_encrypt = true;
break;
case 0: // format supports but no encrypted entry found
default:
need_encrypt = false;
}
archive_release_ctx(ctx);
r = archive_alloc_ctx(&ctx);
if (r) {
if (r == -ENOMEM)
LOGE("%s", "Mem alloc failed");
r = 0;
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
} else {
entries = calloc(entryCount, sizeof(entry));
r = archive_map_entries_index(ctx);
if (!archive_prealloc_mempool()) {
r = 0;
for (int i = 0; i < entryCount; ++i) {
free((void *) entries[i].filename);
}
free(entries);
}
int format = archive_format(ctx->arc);
switch (format) {
case ARCHIVE_FORMAT_ZIP:
case ARCHIVE_FORMAT_RAR_V5:
madvise_log_if_error(archiveAddr, archiveSize, MADV_SEQUENTIAL);
break;
case ARCHIVE_FORMAT_7ZIP: // Seek is bad
madvise_log_if_error(archiveAddr, archiveSize, MADV_RANDOM);
break;
default:;
}
archive_release_ctx(ctx);
return r;
ctx = archive_alloc_ctx();
if (!ctx) return 0;
entries = calloc(entryCount, sizeof(entry));
archive_map_entries_index(ctx, sort_entries);
archive_release_ctx(ctx);
return (int) entryCount;
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "ConstantConditionsOC"
#pragma ide diagnostic ignored "UnreachableCode"
JNIEXPORT jobject JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_extractToByteBuffer(JNIEnv *env, jclass thiz,
jint index) {
Java_com_hippo_ehviewer_jni_ArchiveKt_extractToByteBuffer(JNIEnv *env, jclass thiz, jint index) {
EH_UNUSED(env);
EH_UNUSED(thiz);
void *addr = MEMPOOL_ADDR_BY_SORTED_IDX(index);
size_t size = entries[index].size;
if (!MEMPOOL_IN_CORE_AND_LOCK(index)) {
index = entries[index].index;
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
if (ret) return 0;
ret = archive_read_data(ctx->arc, addr, size);
ctx->using = 0;
if (ret == size)
return (*env)->NewDirectByteBuffer(env, addr, size);
if (ret != size)
LOGE("%s", "No enough data read, WTF?");
if (ret < 0)
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
mempool_release_pages(addr, size);
entry *entry = &entries[index];
ssize_t size = entry->size;
if (entry->addr) {
return (*env)->NewDirectByteBuffer(env, entry->addr, size);
} else {
return (*env)->NewDirectByteBuffer(env, addr, size);
archive_ctx *ctx = NULL;
if (!archive_get_ctx(&ctx, entry->index)) {
void *addr = acquire_decode_buffer();
ssize_t bytes = archive_read_data(ctx->arc, addr, size);
ctx->using = 0;
if (bytes == size) {
return (*env)->NewDirectByteBuffer(env, addr, size);
} else {
if (bytes < 0) {
LOGE("%s%s", "Archive read failed: ", archive_error_string(ctx->arc));
} else {
LOGE("%s", "No enough data read, WTF?");
}
}
release_decode_buffer(addr);
}
}
return 0;
}
#pragma clang diagnostic pop
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_closeArchive(JNIEnv *env, jclass thiz) {
EH_UNUSED(env);
EH_UNUSED(thiz);
for (int i = 0; i < CTX_POOL_SIZE; i++)
archive_release_ctx(ctx_pool[i]);
if (ctx_pool) {
for (int i = 0; i < CTX_POOL_SIZE; i++)
archive_release_ctx(ctx_pool[i]);
free(ctx_pool);
ctx_pool = NULL;
}
free(passwd);
free(ctx_pool);
passwd = NULL;
need_encrypt = false;
munmap(archiveAddr, archiveSize);
munmap(mempool, MEMPOOL_SIZE);
free(mempoolofs);
for (int i = 0; i < entryCount; ++i) {
free((void *) entries[i].filename);
if (archiveAddr != MAP_FAILED) {
munmap(archiveAddr, archiveSize);
archiveAddr = MAP_FAILED;
}
for (int i = 0; i < MAX_PARALLEL_DECOMP; ++i) {
free(decode_buffer[i]);
decode_buffer[i] = NULL;
}
max_file_size = 0;
if (entries) {
for (int i = 0; i < entryCount; ++i) {
free((void *) entries[i].filename);
}
free(entries);
entries = NULL;
}
free(entries);
}
JNIEXPORT jboolean JNICALL
@@ -417,28 +396,24 @@ Java_com_hippo_ehviewer_jni_ArchiveKt_needPassword(JNIEnv *env, jclass thiz) {
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_providePassword(JNIEnv *env, jclass thiz,
jstring str) {
Java_com_hippo_ehviewer_jni_ArchiveKt_providePassword(JNIEnv *env, jclass thiz, jstring str) {
EH_UNUSED(thiz);
struct archive_entry *entry;
archive_ctx *ctx;
jboolean ret = true;
int len = (*env)->GetStringUTFLength(env, str);
passwd = realloc(passwd, len * sizeof(char));
if (!passwd) {
LOGE("Allocate passwd buffer failed");
return false;
}
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
archive_alloc_ctx(&ctx);
void *tmpBuf = alloca(4096);
passwd = realloc(passwd, len + 1);
(*env)->GetStringUTFRegion(env, str, 0, len, passwd);
passwd[len] = 0;
ctx = archive_alloc_ctx();
char tmpBuf[4096];
while (archive_read_next_header(ctx->arc, &entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname_utf8(entry)))
if (!archive_entry_is_playable(entry))
continue;
if (!archive_entry_is_encrypted(entry))
continue;
if (archive_read_data(ctx->arc, tmpBuf, 4096) < ARCHIVE_OK) {
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
LOGE("%s%s", "Archive read failed: ", archive_error_string(ctx->arc));
ret = false;
}
break;
@@ -448,24 +423,15 @@ Java_com_hippo_ehviewer_jni_ArchiveKt_providePassword(JNIEnv *env, jclass thiz,
}
JNIEXPORT jstring JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_getFilename(JNIEnv *env, jclass thiz,
jint index) {
Java_com_hippo_ehviewer_jni_ArchiveKt_getExtension(JNIEnv *env, jclass thiz, jint index) {
EH_UNUSED(env);
EH_UNUSED(thiz);
index = entries[index].index;
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
if (ret)
return NULL;
jstring str = (*env)->NewStringUTF(env, archive_entry_pathname_utf8(ctx->entry));
ctx->using = 0;
return str;
const char *ext = strrchr(entries[index].filename, '.') + 1;
return (*env)->NewStringUTF(env, ext);
}
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_extractToFd(JNIEnv *env, jclass thiz,
jint index, jint fd) {
JNIEXPORT jboolean JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_extractToFd(JNIEnv *env, jclass thiz, jint index, jint fd) {
EH_UNUSED(env);
EH_UNUSED(thiz);
index = entries[index].index;
@@ -473,16 +439,52 @@ Java_com_hippo_ehviewer_jni_ArchiveKt_extractToFd(JNIEnv *env, jclass thiz,
int ret;
ret = archive_get_ctx(&ctx, index);
if (!ret) {
archive_read_data_into_fd(ctx->arc, fd);
ret = archive_read_data_into_fd(ctx->arc, fd);
ctx->using = 0;
}
return ret == ARCHIVE_OK;
}
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_releaseByteBuffer(JNIEnv *env, jclass thiz, jobject buffer) {
EH_UNUSED(thiz);
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
if (!ADDR_IN_FILE_MAPPING(addr)) {
release_decode_buffer(addr);
}
}
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_jni_ArchiveKt_releaseByteBuffer(JNIEnv *env, jclass thiz,
jobject buffer) {
EH_UNUSED(thiz);
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
mempool_release_pages(addr, size);
Java_com_hippo_ehviewer_jni_ArchiveKt_archiveFdBatch(JNIEnv *env, jclass clazz, jintArray fd_batch, jobjectArray names, jint arc_fd, jint size) {
EH_UNUSED(clazz);
struct archive *arc = archive_write_new();
struct stat st;
char buff[8192];
jint fdBatch[size];
(*env)->GetIntArrayRegion(env, fd_batch, 0, size, fdBatch);
archive_write_set_format_zip(arc);
archive_write_zip_set_compression_store(arc);
archive_write_open_fd(arc, arc_fd);
struct archive_entry *entry = archive_entry_new();
for (int i = 0; i < size; i++) {
int fd = fdBatch[i];
jobject name = (*env)->GetObjectArrayElement(env, names, i);
const char *cname = (*env)->GetStringUTFChars(env, name, false);
archive_entry_set_pathname(entry, cname);
(*env)->ReleaseStringUTFChars(env, name, cname);
fstat(fd, &st);
archive_entry_copy_stat(entry, &st);
archive_entry_set_perm(entry, 0644);
archive_write_header(arc, entry);
size_t len;
do {
len = read(fd, buff, sizeof(buff));
archive_write_data(arc, buff, len);
} while (len > 0);
archive_write_finish_entry(arc);
archive_entry_clear(entry);
}
archive_entry_free(entry);
archive_write_close(arc);
archive_write_free(arc);
}
-241
View File
@@ -1,241 +0,0 @@
#include <jni.h>
#include <math.h>
#include <stdbool.h>
#include <android/bitmap.h>
#include <stdlib.h>
#include <string.h>
/** A line will be considered as having content if 0.25% of it is filled. */
const float filledRatioLimit = 0.0025;
/** When the threshold is closer to 1, less content will be cropped. **/
#define THRESHOLD 0.75
const uint8_t thresholdForBlack = (uint8_t) (255.0 * THRESHOLD);
const uint8_t thresholdForWhite = (uint8_t) (255.0 - 255.0 * THRESHOLD);
static uint8_t inline grey(uint32_t pixel) {
const uint8_t r = ((uint8_t *) &pixel)[0];
const uint8_t g = ((uint8_t *) &pixel)[1];
const uint8_t b = ((uint8_t *) &pixel)[2];
return (r * 77 + g * 151 + b * 28) >> 8;
}
static bool inline isBlackPixel(const uint32_t *pixels, uint32_t width, uint32_t x, uint32_t y) {
const uint32_t pixel = *((uint32_t *) pixels + (y * width + x));
return grey(pixel) < thresholdForBlack;
}
static bool inline isWhitePixel(const uint32_t *pixels, uint32_t width, uint32_t x, uint32_t y) {
const uint32_t pixel = *((uint32_t *) pixels + (y * width + x));
return grey(pixel) > thresholdForWhite;
}
/** Return the first x position where there is a substantial amount of fill,
* starting the search from the left. */
uint32_t findBorderLeft(uint32_t *pixels, uint32_t width, uint32_t height,
uint32_t top, uint32_t bottom) {
int x, y;
const uint32_t filledLimit = round(height * filledRatioLimit / 2);
// Scan first line to detect dominant color
uint32_t whitePixels = 0;
uint32_t blackPixels = 0;
for (y = top; y < bottom; y += 2) {
if (isBlackPixel(pixels, width, 0, y)) {
blackPixels++;
} else if (isWhitePixel(pixels, width, 0, y)) {
whitePixels++;
}
}
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
if (whitePixels > filledLimit && blackPixels > filledLimit) {
// Mixed fill found... don't crop anything
return 0;
} else if (blackPixels > filledLimit) {
detectFunc = isWhitePixel;
}
// Scan vertical lines in search of filled lines
for (x = 1; x < width; x++) {
uint32_t filledCount = 0;
for (y = top; y < bottom; y += 2) {
if (detectFunc(pixels, width, x, y)) {
filledCount++;
}
}
if (filledCount > filledLimit) {
// This line contains enough fill
return x;
}
}
// No fill found... don't crop anything
return 0;
}
/** Return the first x position where there is a substantial amount of fill,
* starting the search from the right. */
uint32_t findBorderRight(uint32_t *pixels, uint32_t width, uint32_t height,
uint32_t top, uint32_t bottom) {
int x, y;
const uint32_t filledLimit = round(height * filledRatioLimit / 2);
// Scan first line to detect dominant color
uint32_t whitePixels = 0;
uint32_t blackPixels = 0;
uint32_t lastX = width - 1;
for (y = top; y < bottom; y += 2) {
if (isBlackPixel(pixels, width, lastX, y)) {
blackPixels++;
} else if (isWhitePixel(pixels, width, lastX, y)) {
whitePixels++;
}
}
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
if (whitePixels > filledLimit && blackPixels > filledLimit) {
// Mixed fill found... don't crop anything
return width;
} else if (blackPixels > filledLimit) {
detectFunc = isWhitePixel;
}
// Scan vertical lines in search of filled lines
for (x = width - 2; x > 0; x--) {
uint32_t filledCount = 0;
for (y = top; y < bottom; y += 2) {
if (detectFunc(pixels, width, x, y)) {
filledCount++;
}
}
if (filledCount > filledLimit) {
// This line contains enough fill
return x + 1;
}
}
// No fill found... don't crop anything
return width;
}
/** Return the first y position where there is a substantial amount of fill,
* starting the search from the top. */
uint32_t findBorderTop(uint32_t *pixels, uint32_t width, uint32_t height) {
int x, y;
const uint32_t filledLimit = round(width * filledRatioLimit / 2);
// Scan first line to detect dominant color
uint32_t whitePixels = 0;
uint32_t blackPixels = 0;
for (x = 0; x < width; x += 2) {
if (isBlackPixel(pixels, width, x, 0)) {
blackPixels++;
} else if (isWhitePixel(pixels, width, x, 0)) {
whitePixels++;
}
}
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
if (whitePixels > filledLimit && blackPixels > filledLimit) {
// Mixed fill found... don't crop anything
return 0;
} else if (blackPixels > filledLimit) {
detectFunc = isWhitePixel;
}
// Scan horizontal lines in search of filled lines
for (y = 1; y < height; y++) {
uint32_t filledCount = 0;
for (x = 0; x < width; x += 2) {
if (detectFunc(pixels, width, x, y)) {
filledCount++;
}
}
if (filledCount > filledLimit) {
// This line contains enough fill
return y;
}
}
// No fill found... don't crop anything
return 0;
}
/** Return the first y position where there is a substantial amount of fill,
* starting the search from the bottom. */
uint32_t findBorderBottom(uint32_t *pixels, uint32_t width, uint32_t height) {
int x, y;
const uint32_t filledLimit = round(width * filledRatioLimit / 2);
// Scan first line to detect dominant color
uint32_t whitePixels = 0;
uint32_t blackPixels = 0;
uint32_t lastY = height - 1;
for (x = 0; x < width; x += 2) {
if (isBlackPixel(pixels, width, x, lastY)) {
blackPixels++;
} else if (isWhitePixel(pixels, width, x, lastY)) {
whitePixels++;
}
}
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
if (whitePixels > filledLimit && blackPixels > filledLimit) {
// Mixed fill found... don't crop anything
return height;
} else if (blackPixels > filledLimit) {
detectFunc = isWhitePixel;
}
// Scan horizontal lines in search of filled lines
for (y = height - 2; y > 0; y--) {
uint32_t filledCount = 0;
for (x = 0; x < width; x += 2) {
if (detectFunc(pixels, width, x, y)) {
filledCount++;
}
}
if (filledCount > filledLimit) {
// This line contains enough fill
return y + 1;
}
}
// No fill found... don't crop anything
return height;
}
JNIEXPORT jintArray JNICALL
Java_com_hippo_ehviewer_image_ImageKt_detectBorder(JNIEnv *env, jclass clazz, jobject bitmap) {
void *pixels = NULL;
AndroidBitmapInfo info;
memset(&info, 0, sizeof(info));
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, &pixels);
uint32_t width = info.width;
uint32_t height = info.height;
uint32_t top = findBorderTop(pixels, width, height);
uint32_t bottom = findBorderBottom(pixels, width, height);
uint32_t left = findBorderLeft(pixels, width, height, top, bottom);
uint32_t right = findBorderRight(pixels, width, height, top, bottom);
int array[4] = {left, top, right, bottom};
AndroidBitmap_unlockPixels(env, bitmap);
jintArray ret = (*env)->NewIntArray(env, 4);
(*env)->SetIntArrayRegion(env, ret, 0, 4, array);
return ret;
}
+1 -1
View File
@@ -37,7 +37,7 @@ static void doRewrite(byte *addr, size_t size) {
byte *end = addr + i + 4;
if (end[4] != 0) continue;
int frameDelay = end[2] << 8 | end[1];
if (frameDelay > MINIMUM_FRAME_DELAY)
if (frameDelay >= MINIMUM_FRAME_DELAY)
break; // Quit if the first block looks normal, for performance
end[1] = DEFAULT_FRAME_DELAY;
end[2] = 0;
+39
View File
@@ -0,0 +1,39 @@
#include <unistd.h>
#include <jni.h>
#include <nettle/sha1.h>
#include "ehviewer.h"
#define BUFFER_SIZE 8192
typedef uint8_t byte;
const char hex_digits[] = "0123456789abcdef";
JNIEXPORT jstring JNICALL
Java_com_hippo_ehviewer_jni_HashKt_sha1(JNIEnv *env, jclass clazz, jint fd) {
EH_UNUSED(clazz);
struct sha1_ctx ctx;
sha1_init(&ctx);
size_t bytes_read;
byte buffer[BUFFER_SIZE];
while ((bytes_read = read(fd, buffer, BUFFER_SIZE)) > 0) {
sha1_update(&ctx, bytes_read, buffer);
}
byte digest[SHA1_DIGEST_SIZE];
sha1_digest(&ctx, SHA1_DIGEST_SIZE, digest);
byte byte;
char hex_digest[2 * SHA1_DIGEST_SIZE + 1];
for (int i = 0; i < SHA1_DIGEST_SIZE; i++) {
byte = digest[i];
hex_digest[2 * i] = hex_digits[byte >> 4 & 0xF];
hex_digest[2 * i + 1] = hex_digits[byte & 0xF];
}
hex_digest[2 * SHA1_DIGEST_SIZE] = '\0';
return (*env)->NewStringUTF(env, hex_digest);
}
+7
View File
@@ -0,0 +1,7 @@
{
global:
JNI_OnLoad;
Java_*;
local:
*;
};
+24 -85
View File
@@ -30,7 +30,6 @@
* negative chars in their default char type.
*/
#include <stddef.h> /* size_t */
#include <ctype.h>
#include "strnatcmp.h"
@@ -39,28 +38,21 @@
/* These are defined as macros to make it easier to adapt this code to
* different characters types or comparison functions. */
static inline int
nat_isdigit(nat_char a)
nat_isdigit(const char *a)
{
return isdigit((unsigned char) a);
return isdigit((unsigned char) *a);
}
static inline int
nat_isspace(nat_char a)
nat_isspace(const char *a)
{
return isspace((unsigned char) a);
}
static inline nat_char
nat_toupper(nat_char a)
{
return toupper((unsigned char) a);
return isspace((unsigned char) *a) || *a == '0' && nat_isdigit(a + 1);
}
static int
compare_right(nat_char const *a, nat_char const *b)
compare_right(const char *a, const char *b)
{
int bias = 0;
@@ -69,11 +61,11 @@ compare_right(nat_char const *a, nat_char const *b)
both numbers to know that they have the same magnitude, so we
remember it in BIAS. */
for (;; a++, b++) {
if (!nat_isdigit(*a) && !nat_isdigit(*b))
if (!nat_isdigit(a) && !nat_isdigit(b))
return bias;
if (!nat_isdigit(*a))
if (!nat_isdigit(a))
return -1;
if (!nat_isdigit(*b))
if (!nat_isdigit(b))
return +1;
if (*a < *b) {
if (!bias)
@@ -84,94 +76,41 @@ compare_right(nat_char const *a, nat_char const *b)
} else if (!*a && !*b)
return bias;
}
return 0;
}
static int
compare_left(nat_char const *a, nat_char const *b)
int
strnatcmp(const char *a, const char *b)
{
/* Compare two left-aligned numbers: the first to have a
different value wins. */
for (;; a++, b++) {
if (!nat_isdigit(*a) && !nat_isdigit(*b))
return 0;
if (!nat_isdigit(*a))
return -1;
if (!nat_isdigit(*b))
return +1;
if (*a < *b)
return -1;
if (*a > *b)
return +1;
}
int result;
return 0;
}
while (1) {
/* skip over leading spaces or zeros */
while (nat_isspace(a))
a++;
static int
strnatcmp0(nat_char const *a, nat_char const *b, int fold_case)
{
int ai, bi;
nat_char ca, cb;
int fractional, result;
ai = bi = 0;
while (1) {
ca = a[ai]; cb = b[bi];
/* skip over leading spaces or zeros */
while (nat_isspace(ca))
ca = a[++ai];
while (nat_isspace(cb))
cb = b[++bi];
while (nat_isspace(b))
b++;
/* process run of digits */
if (nat_isdigit(ca) && nat_isdigit(cb)) {
fractional = (ca == '0' || cb == '0');
if (fractional) {
if ((result = compare_left(a+ai, b+bi)) != 0)
return result;
} else {
if ((result = compare_right(a+ai, b+bi)) != 0)
return result;
}
if (nat_isdigit(a) && nat_isdigit(b)) {
if ((result = compare_right(a, b)) != 0)
return result;
}
if (!ca && !cb) {
if (!*a && !*b) {
/* The strings compare the same. Perhaps the caller
will want to call strcmp to break the tie. */
return 0;
}
if (fold_case) {
ca = nat_toupper(ca);
cb = nat_toupper(cb);
}
if (ca < cb)
if (*a < *b)
return -1;
if (ca > cb)
if (*a > *b)
return +1;
++ai; ++bi;
a++; b++;
}
}
int
strnatcmp(nat_char const *a, nat_char const *b) {
return strnatcmp0(a, b, 0);
}
/* Compare, recognizing numeric string and ignoring case. */
int
strnatcasecmp(nat_char const *a, nat_char const *b) {
return strnatcmp0(a, b, 1);
}
+1 -8
View File
@@ -24,14 +24,7 @@
extern "C" {
#endif
/* CUSTOMIZATION SECTION
*
* You can change this typedef, but must then also change the inline
* functions in strnatcmp.c */
typedef char nat_char;
int strnatcmp(nat_char const *a, nat_char const *b);
int strnatcasecmp(nat_char const *a, nat_char const *b);
int strnatcmp(const char *a, const char *b);
#ifdef __cplusplus
}
-1
View File
@@ -1 +0,0 @@
/nettle
-238
View File
@@ -1,238 +0,0 @@
cmake_minimum_required(VERSION 3.4.1)
project(nettle C)
set(LIBNETTLE_DEFINITIONS
-DHAVE_CONFIG_H)
set(LIBNETTLE_SOURCES
nettle/aes-decrypt-internal.c
nettle/aes-decrypt.c
nettle/aes-decrypt-table.c
nettle/aes128-decrypt.c
nettle/aes192-decrypt.c
nettle/aes256-decrypt.c
nettle/aes-encrypt-internal.c
nettle/aes-encrypt.c
nettle/aes-encrypt-table.c
nettle/aes128-encrypt.c
nettle/aes192-encrypt.c
nettle/aes256-encrypt.c
nettle/aes-invert-internal.c
nettle/aes-set-key-internal.c
nettle/aes-set-encrypt-key.c
nettle/aes-set-decrypt-key.c
nettle/aes128-set-encrypt-key.c
nettle/aes128-set-decrypt-key.c
nettle/aes128-meta.c
nettle/aes192-set-encrypt-key.c
nettle/aes192-set-decrypt-key.c
nettle/aes192-meta.c
nettle/aes256-set-encrypt-key.c
nettle/aes256-set-decrypt-key.c
nettle/aes256-meta.c
nettle/nist-keywrap.c
nettle/arcfour.c
nettle/arctwo.c
nettle/arctwo-meta.c
nettle/blowfish.c
nettle/blowfish-bcrypt.c
nettle/base16-encode.c
nettle/base16-decode.c
nettle/base16-meta.c
nettle/base64-encode.c
nettle/base64-decode.c
nettle/base64-meta.c
nettle/base64url-encode.c
nettle/base64url-decode.c
nettle/base64url-meta.c
nettle/buffer.c
nettle/buffer-init.c
nettle/camellia-crypt-internal.c
nettle/camellia-table.c
nettle/camellia-absorb.c
nettle/camellia-invert-key.c
nettle/camellia128-set-encrypt-key.c
nettle/camellia128-crypt.c
nettle/camellia128-set-decrypt-key.c
nettle/camellia128-meta.c
nettle/camellia192-meta.c
nettle/camellia256-set-encrypt-key.c
nettle/camellia256-crypt.c
nettle/camellia256-set-decrypt-key.c
nettle/camellia256-meta.c
nettle/cast128.c
nettle/cast128-meta.c
nettle/cbc.c
nettle/cbc-aes128-encrypt.c
nettle/cbc-aes192-encrypt.c
nettle/cbc-aes256-encrypt.c
nettle/ccm.c
nettle/ccm-aes128.c
nettle/ccm-aes192.c
nettle/ccm-aes256.c
nettle/cfb.c
nettle/siv-cmac.c
nettle/siv-cmac-aes128.c
nettle/siv-cmac-aes256.c
nettle/cnd-memcpy.c
nettle/chacha-crypt.c
nettle/chacha-core-internal.c
nettle/chacha-poly1305.c
nettle/chacha-poly1305-meta.c
nettle/chacha-set-key.c
nettle/chacha-set-nonce.c
nettle/ctr.c
nettle/ctr16.c
nettle/des.c
nettle/des3.c
nettle/eax.c
nettle/eax-aes128.c
nettle/eax-aes128-meta.c
nettle/ghash-set-key.c
nettle/ghash-update.c
nettle/gcm.c
nettle/gcm-aes.c
nettle/gcm-aes128.c
nettle/gcm-aes128-meta.c
nettle/gcm-aes192.c
nettle/gcm-aes192-meta.c
nettle/gcm-aes256.c
nettle/gcm-aes256-meta.c
nettle/gcm-camellia128.c
nettle/gcm-camellia128-meta.c
nettle/gcm-camellia256.c
nettle/gcm-camellia256-meta.c
nettle/cmac.c
nettle/cmac64.c
nettle/cmac-aes128.c
nettle/cmac-aes256.c
nettle/cmac-des3.c
nettle/cmac-aes128-meta.c
nettle/cmac-aes256-meta.c
nettle/cmac-des3-meta.c
nettle/gost28147.c
nettle/gosthash94.c
nettle/gosthash94-meta.c
nettle/hmac.c
nettle/hmac-gosthash94.c
nettle/hmac-md5.c
nettle/hmac-ripemd160.c
nettle/hmac-sha1.c
nettle/hmac-sha224.c
nettle/hmac-sha256.c
nettle/hmac-sha384.c
nettle/hmac-sha512.c
nettle/hmac-streebog.c
nettle/hmac-sm3.c
nettle/hmac-md5-meta.c
nettle/hmac-ripemd160-meta.c
nettle/hmac-sha1-meta.c
nettle/hmac-sha224-meta.c
nettle/hmac-sha256-meta.c
nettle/hmac-sha384-meta.c
nettle/hmac-sha512-meta.c
nettle/hmac-streebog-meta.c
nettle/hmac-sm3-meta.c
nettle/knuth-lfib.c
nettle/hkdf.c
nettle/md2.c
nettle/md2-meta.c
nettle/md4.c
nettle/md4-meta.c
nettle/md5.c
nettle/md5-compress.c
nettle/md5-compat.c
nettle/md5-meta.c
nettle/memeql-sec.c
nettle/memxor.c
nettle/memxor3.c
nettle/nettle-lookup-hash.c
nettle/nettle-meta-aeads.c
nettle/nettle-meta-armors.c
nettle/nettle-meta-ciphers.c
nettle/nettle-meta-hashes.c
nettle/nettle-meta-macs.c
nettle/pbkdf2.c
nettle/pbkdf2-hmac-gosthash94.c
nettle/pbkdf2-hmac-sha1.c
nettle/pbkdf2-hmac-sha256.c
nettle/pbkdf2-hmac-sha384.c
nettle/pbkdf2-hmac-sha512.c
nettle/poly1305-aes.c
nettle/poly1305-internal.c
nettle/realloc.c
nettle/ripemd160.c
nettle/ripemd160-compress.c
nettle/ripemd160-meta.c
nettle/salsa20-core-internal.c
nettle/salsa20-crypt-internal.c
nettle/salsa20-crypt.c
nettle/salsa20r12-crypt.c
nettle/salsa20-set-key.c
nettle/salsa20-set-nonce.c
nettle/salsa20-128-set-key.c
nettle/salsa20-256-set-key.c
nettle/sha1.c
nettle/sha1-compress.c
nettle/sha1-meta.c
nettle/sha256.c
nettle/sha256-compress-n.c
nettle/sha224-meta.c
nettle/sha256-meta.c
nettle/sha512.c
nettle/sha512-compress.c
nettle/sha384-meta.c
nettle/sha512-meta.c
nettle/sha512-224-meta.c
nettle/sha512-256-meta.c
nettle/sha3.c
nettle/sha3-permute.c
nettle/sha3-224.c
nettle/sha3-224-meta.c
nettle/sha3-256.c
nettle/sha3-256-meta.c
nettle/sha3-384.c
nettle/sha3-384-meta.c
nettle/sha3-512.c
nettle/sha3-512-meta.c
nettle/shake256.c
nettle/sm3.c
nettle/sm3-meta.c
nettle/serpent-set-key.c
nettle/serpent-encrypt.c
nettle/serpent-decrypt.c
nettle/serpent-meta.c
nettle/streebog.c
nettle/streebog-meta.c
nettle/twofish.c
nettle/twofish-meta.c
nettle/umac-nh.c
nettle/umac-nh-n.c
nettle/umac-l2.c
nettle/umac-l3.c
nettle/umac-poly64.c
nettle/umac-poly128.c
nettle/umac-set-key.c
nettle/umac32.c
nettle/umac64.c
nettle/umac96.c
nettle/umac128.c
nettle/version.c
nettle/write-be32.c
nettle/write-le32.c
nettle/write-le64.c
nettle/yarrow256.c
nettle/yarrow_key_event.c
nettle/xts.c
nettle/xts-aes128.c
nettle/xts-aes256.c
)
set(LIBNETTLE_INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/nettle
)
add_library(nettle STATIC ${LIBNETTLE_SOURCES})
target_include_directories(nettle PUBLIC ${LIBNETTLE_INCLUDES})
target_compile_definitions(nettle PRIVATE ${LIBNETTLE_DEFINITIONS})
-280
View File
@@ -1,280 +0,0 @@
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.ac by autoheader. */
/* Define if building universal (internal helper macro) */
/* #undef AC_APPLE_UNIVERSAL_BUILD */
/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
systems. This function is required for `alloca.c' support on those systems.
*/
/* #undef CRAY_STACKSEG_END */
/* Define to 1 if using `alloca.c'. */
/* #undef C_ALLOCA */
/* Define to 1 if you have `alloca', as a function or macro. */
#define HAVE_ALLOCA 1
/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
*/
#define HAVE_ALLOCA_H 1
/* Define if __builtin_bswap64 is available */
#define HAVE_BUILTIN_BSWAP64 1
/* Define if clock_gettime is available */
#define HAVE_CLOCK_GETTIME 1
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1
/* Define if fcntl file locking is available */
#define HAVE_FCNTL_LOCKING 1
/* Define if the compiler understands __attribute__ */
#define HAVE_GCC_ATTRIBUTE 1
/* Define to 1 if you have the `getline' function. */
#define HAVE_GETLINE 1
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
/* Define to 1 if you have dlopen (with -ldl). */
#define HAVE_LIBDL 1
/* Define to 1 if you have the `gmp' library (-lgmp). */
/* #undef HAVE_LIBGMP */
/* Define if compiler and linker supports __attribute__ ifunc */
#define HAVE_LINK_IFUNC 1
/* Define to 1 if you have the <malloc.h> header file. */
#define HAVE_MALLOC_H 1
/* Define to 1 if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1
/* Define to 1 each of the following for which a native (ie. CPU specific)
implementation of the corresponding routine exists. */
/* #undef HAVE_NATIVE_memxor3 */
/* #undef HAVE_NATIVE_aes_decrypt */
/* #undef HAVE_NATIVE_aes_encrypt */
/* #undef HAVE_NATIVE_aes128_decrypt */
/* #undef HAVE_NATIVE_aes128_encrypt */
/* #undef HAVE_NATIVE_aes128_invert_key */
/* #undef HAVE_NATIVE_aes128_set_decrypt_key */
/* #undef HAVE_NATIVE_aes128_set_encrypt_key */
/* #undef HAVE_NATIVE_aes192_decrypt */
/* #undef HAVE_NATIVE_aes192_encrypt */
/* #undef HAVE_NATIVE_aes192_invert_key */
/* #undef HAVE_NATIVE_aes192_set_decrypt_key */
/* #undef HAVE_NATIVE_aes192_set_encrypt_key */
/* #undef HAVE_NATIVE_aes256_decrypt */
/* #undef HAVE_NATIVE_aes256_encrypt */
/* #undef HAVE_NATIVE_aes256_invert_key */
/* #undef HAVE_NATIVE_aes256_set_decrypt_key */
/* #undef HAVE_NATIVE_aes256_set_encrypt_key */
/* #undef HAVE_NATIVE_cbc_aes128_encrypt */
/* #undef HAVE_NATIVE_cbc_aes192_encrypt */
/* #undef HAVE_NATIVE_cbc_aes256_encrypt */
/* #undef HAVE_NATIVE_chacha_core */
/* #undef HAVE_NATIVE_chacha_2core */
#define HAVE_NATIVE_chacha_3core 1
/* #undef HAVE_NATIVE_chacha_4core */
/* #undef HAVE_NATIVE_fat_chacha_2core */
/* #undef HAVE_NATIVE_fat_chacha_3core */
/* #undef HAVE_NATIVE_fat_chacha_4core */
/* #undef HAVE_NATIVE_ecc_curve25519_modp */
/* #undef HAVE_NATIVE_ecc_curve448_modp */
/* #undef HAVE_NATIVE_ecc_secp192r1_modp */
/* #undef HAVE_NATIVE_ecc_secp192r1_redc */
/* #undef HAVE_NATIVE_ecc_secp224r1_modp */
/* #undef HAVE_NATIVE_ecc_secp224r1_redc */
/* #undef HAVE_NATIVE_ecc_secp256r1_modp */
/* #undef HAVE_NATIVE_ecc_secp256r1_redc */
/* #undef HAVE_NATIVE_ecc_secp384r1_modp */
/* #undef HAVE_NATIVE_ecc_secp384r1_redc */
/* #undef HAVE_NATIVE_ecc_secp521r1_modp */
/* #undef HAVE_NATIVE_ecc_secp521r1_redc */
/* #undef HAVE_NATIVE_poly1305_set_key */
/* #undef HAVE_NATIVE_poly1305_block */
/* #undef HAVE_NATIVE_poly1305_digest */
/* #undef HAVE_NATIVE_poly1305_blocks */
/* #undef HAVE_NATIVE_fat_poly1305_blocks */
/* #undef HAVE_NATIVE_ghash_set_key */
/* #undef HAVE_NATIVE_ghash_update */
/* #undef HAVE_NATIVE_salsa20_core */
#define HAVE_NATIVE_salsa20_2core 1
/* #undef HAVE_NATIVE_fat_salsa20_2core */
/* #undef HAVE_NATIVE_sha1_compress */
/* #undef HAVE_NATIVE_sha256_compress_n */
/* #undef HAVE_NATIVE_sha512_compress */
/* #undef HAVE_NATIVE_sha3_permute */
/* #undef HAVE_NATIVE_umac_nh */
/* #undef HAVE_NATIVE_umac_nh_n */
/* Define to 1 if you have the <openssl/ecdsa.h> header file. */
/* #undef HAVE_OPENSSL_ECDSA_H */
/* Define to 1 if you have the <openssl/evp.h> header file. */
/* #undef HAVE_OPENSSL_EVP_H */
/* Define to 1 if you have the `secure_getenv' function. */
#define HAVE_SECURE_GETENV 1
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the `strerror' function. */
#define HAVE_STRERROR 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1
/* Define to 1 if you have the <valgrind/memcheck.h> header file. */
/* #undef HAVE_VALGRIND_MEMCHECK_H */
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "nettle-bugs@lists.lysator.liu.se"
/* Define to the full name of this package. */
#define PACKAGE_NAME "nettle"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "nettle 3.9.1"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "nettle"
/* Define to the home page for this package. */
#define PACKAGE_URL ""
/* Define to the version of this package. */
#define PACKAGE_VERSION "3.9.1"
/* The size of `long', as computed by sizeof. */
#define SIZEOF_LONG __SIZEOF_LONG__
/* The size of `size_t', as computed by sizeof. */
#define SIZEOF_SIZE_T __SIZEOF_SIZE_T__
/* If using the C implementation of alloca, define if you know the
direction of stack growth for your system; otherwise it will be
automatically deduced at runtime.
STACK_DIRECTION > 0 => grows toward higher addresses
STACK_DIRECTION < 0 => grows toward lower addresses
STACK_DIRECTION = 0 => direction of growth unknown */
/* #undef STACK_DIRECTION */
/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
#define TIME_WITH_SYS_TIME 1
/* Defined if public key features are enabled */
/* #undef WITH_HOGWEED */
/* Define if you have openssl's libcrypto (used for benchmarking) */
/* #undef WITH_OPENSSL */
/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
significant byte first (like Motorola and SPARC, unlike Intel). */
#if defined AC_APPLE_UNIVERSAL_BUILD
# if defined __BIG_ENDIAN__
# define WORDS_BIGENDIAN 1
# endif
#else
# ifndef WORDS_BIGENDIAN
/* # undef WORDS_BIGENDIAN */
# endif
#endif
/* Define to empty if `const' does not conform to ANSI C. */
/* #undef const */
/* Define to `int' if <sys/types.h> doesn't define. */
/* #undef gid_t */
/* Define to `__inline__' or `__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */
#ifndef __cplusplus
/* #undef inline */
#endif
/* Define to `unsigned int' if <sys/types.h> does not define. */
/* #undef size_t */
/* Define to `int' if <sys/types.h> doesn't define. */
/* #undef uid_t */
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
/* Needed for alloca on windows */
# if HAVE_MALLOC_H
# include <malloc.h>
# endif
# endif
#else /* defined __GNUC__ */
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
/* Needed for alloca on windows, also with gcc */
# if HAVE_MALLOC_H
# include <malloc.h>
# endif
# endif
#endif
#if HAVE_STRERROR
#define STRERROR strerror
#else
#define STRERROR(x) (sys_errlist[x])
#endif
#if __GNUC__ && HAVE_GCC_ATTRIBUTE
# define NORETURN __attribute__ ((__noreturn__))
# define PRINTF_STYLE(f, a) __attribute__ ((__format__ (__printf__, f, a)))
# define UNUSED __attribute__ ((__unused__))
#else
# define NORETURN
# define PRINTF_STYLE(f, a)
# define UNUSED
#endif
#if defined(__x86_64__) || defined(__arch64__)
# define HAVE_NATIVE_64_BIT 1
#else
/* Needs include of <limits.h> before use. */
# define HAVE_NATIVE_64_BIT (SIZEOF_LONG * CHAR_BIT >= 64)
#endif
-1
View File
@@ -1 +0,0 @@
-1
View File
@@ -1 +0,0 @@
-64
View File
@@ -1,64 +0,0 @@
/* version.h
Information about library version.
Copyright (C) 2015 Red Hat, Inc.
Copyright (C) 2015 Niels Möller
This file is part of GNU Nettle.
GNU Nettle is free software: you can redistribute it and/or
modify it under the terms of either:
* the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your
option) any later version.
or
* the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your
option) any later version.
or both in parallel, as here.
GNU Nettle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received copies of the GNU General Public License and
the GNU Lesser General Public License along with this program. If
not, see http://www.gnu.org/licenses/.
*/
#ifndef NETTLE_VERSION_H_INCLUDED
#define NETTLE_VERSION_H_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif
/* Individual version numbers in decimal */
#define NETTLE_VERSION_MAJOR 3
#define NETTLE_VERSION_MINOR 9
#define NETTLE_USE_MINI_GMP 0
/* We need a preprocessor constant for GMP_NUMB_BITS, simply using
sizeof(mp_limb_t) * CHAR_BIT is not good enough. */
#if NETTLE_USE_MINI_GMP
# define GMP_NUMB_BITS n/a
#endif
int
nettle_version_major (void);
int
nettle_version_minor (void);
#ifdef __cplusplus
}
#endif
#endif /* NETTLE_VERSION_H_INCLUDED */
-44
View File
@@ -1,44 +0,0 @@
#ifndef EHVIEWER_SPINLOCK_H
#define EHVIEWER_SPINLOCK_H
#include <stddef.h>
#include <stdatomic.h>
#define cmpxchg(P, O, N) __sync_val_compare_and_swap((P), (O), (N))
#define barrier() __asm__ __volatile__("":::"memory")
#if defined(__aarch64__) || defined(__arm__)
#define cpu_relax() __asm__ volatile ("wfe");
#else
#define cpu_relax() __asm__ volatile ("pause" ::: "memory");
#endif
typedef struct mcs_lock_t mcs_lock_t;
struct mcs_lock_t {
mcs_lock_t *next;
int spin;
};
typedef struct mcs_lock_t *mcs_lock;
static inline void lock_mcs(_Atomic mcs_lock *m, mcs_lock_t *me) {
mcs_lock_t *tail;
me->next = NULL;
me->spin = 0;
tail = atomic_exchange(m, me);
if (!tail) return;
tail->next = me;
barrier();
while (!me->spin) cpu_relax();
return;
}
static inline void unlock_mcs(mcs_lock *m, mcs_lock_t *me) {
if (!me->next) {
if (cmpxchg(m, me, NULL) == me) return;
while (!me->next) cpu_relax();
}
me->next->spin = 1;
}
#endif //EHVIEWER_SPINLOCK_H
@@ -1,502 +0,0 @@
/*
* Copyright 2022 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("ktlint:standard:property-naming")
package androidx.compose.material3
import androidx.activity.BackEventCompat
import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.EaseOut
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.foundation.MutatorMutex
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.MutableWindowInsets
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.onConsumedWindowInsetsChanged
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.GenericShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text2.BasicTextField2
import androidx.compose.foundation.text2.input.TextFieldLineLimits
import androidx.compose.foundation.text2.input.TextFieldState
import androidx.compose.material3.SearchBarDefaults.InputFieldHeight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.unit.offset
import androidx.compose.ui.util.lerp
import androidx.compose.ui.zIndex
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.math.sign
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
/**
* <a href="https://m3.material.io/components/search/overview" class="external" target="_blank">Material Design search</a>.
*
* A search bar represents a floating search field that allows users to enter a keyword or phrase
* and get relevant information. It can be used as a way to navigate through an app via search
* queries.
*
* An active search bar expands into a search "view" and can be used to display dynamic suggestions.
*
* ![Search bar image](https://developer.android.com/images/reference/androidx/compose/material3/search-bar.png)
*
* A [SearchBar] expands to occupy the entirety of its allowed size when active. For full-screen
* behavior as specified by Material guidelines, parent layouts of the [SearchBar] must not pass
* any [Constraints] that limit its size, and the host activity should set
* `WindowCompat.setDecorFitsSystemWindows(window, false)`.
*
* If this expansion behavior is undesirable, for example on large tablet screens, [DockedSearchBar]
* can be used instead.
*
* @param state the query text to be shown in the search bar's input field
* @param onSearch the callback to be invoked when the input service triggers the [ImeAction.Search]
* action.
* @param active whether this search bar is active
* @param onActiveChange the callback to be invoked when this search bar's active state is changed
* @param modifier the [Modifier] to be applied to this search bar
* @param enabled controls the enabled state of this search bar. When `false`, this component will
* not respond to user input, and it will appear visually disabled and disabled to accessibility
* services.
* @param placeholder the placeholder to be displayed when the search bar's query is empty.
* @param leadingIcon the leading icon to be displayed at the beginning of the search bar container
* @param trailingIcon the trailing icon to be displayed at the end of the search bar container
* @param shape the shape of this search bar when it is not [active]. When [active], the shape will
* always be [SearchBarDefaults.fullScreenShape].
* @param colors [SearchBarColors] that will be used to resolve the colors used for this search bar
* in different states. See [SearchBarDefaults.colors].
* @param tonalElevation when [SearchBarColors.containerColor] is [ColorScheme.surface], a
* translucent primary color overlay is applied on top of the container. A higher tonal elevation
* value will result in a darker color in light theme and lighter color in dark theme. See also:
* [Surface].
* @param shadowElevation the elevation for the shadow below the search bar
* @param windowInsets the window insets that the search bar will respect
* @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
* for this search bar. You can create and pass in your own `remember`ed instance to observe
* [Interaction]s and customize the appearance / behavior of this search bar in different states.
* @param content the content of this search bar that will be displayed below the input field
*/
@ExperimentalMaterial3Api
@Composable
fun SearchBar(
state: TextFieldState,
onSearch: () -> Unit,
active: Boolean,
onActiveChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
title: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
shape: Shape = SearchBarDefaults.inputFieldShape,
colors: SearchBarColors = SearchBarDefaults.colors(),
tonalElevation: Dp = SearchBarDefaults.TonalElevation,
shadowElevation: Dp = SearchBarDefaults.ShadowElevation,
windowInsets: WindowInsets = SearchBarDefaults.windowInsets,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable ColumnScope.() -> Unit,
) {
val animationProgress = remember { Animatable(initialValue = if (active) 1f else 0f) }
var firstBackEvent by remember { mutableStateOf<BackEventCompat?>(null) }
var currentBackEvent by remember { mutableStateOf<BackEventCompat?>(null) }
val focusManager = LocalFocusManager.current
val density = LocalDensity.current
val defaultInputFieldShape = SearchBarDefaults.inputFieldShape
val defaultFullScreenShape = SearchBarDefaults.fullScreenShape
val useFullScreenShape by remember {
derivedStateOf(structuralEqualityPolicy()) { animationProgress.value == 1f }
}
val animatedShape = remember(useFullScreenShape, shape) {
when {
shape == defaultInputFieldShape ->
// The shape can only be animated if it's the default spec value
GenericShape { size, _ ->
val radius = with(density) {
(SearchBarCornerRadius * (1 - animationProgress.value)).toPx()
}
addRoundRect(RoundRect(size.toRect(), CornerRadius(radius)))
}
useFullScreenShape -> defaultFullScreenShape
else -> shape
}
}
// The main animation complexity is allowing the component to smoothly expand while keeping the
// input field at the same relative location on screen. `Modifier.windowInsetsPadding` does not
// support animation and thus is not suitable. Instead, we convert the insets to a padding
// applied to the Surface, which gradually becomes padding applied to the input field as the
// animation proceeds.
val unconsumedInsets = remember { MutableWindowInsets() }
val topPadding = remember(density) {
derivedStateOf {
SearchBarVerticalPadding +
unconsumedInsets.asPaddingValues(density).calculateTopPadding()
}
}
Surface(
shape = animatedShape,
color = colors.containerColor,
contentColor = contentColorFor(colors.containerColor),
tonalElevation = tonalElevation,
shadowElevation = shadowElevation,
modifier = modifier
.padding(horizontal = lerp(SearchBarHorizontalPadding, 0.dp, animationProgress.value))
.zIndex(1f)
.onConsumedWindowInsetsChanged { consumedInsets ->
unconsumedInsets.insets = windowInsets.exclude(consumedInsets)
}
.consumeWindowInsets(unconsumedInsets)
.layout { measurable, constraints ->
val animatedTopPadding =
lerp(topPadding.value, 0.dp, animationProgress.value).roundToPx()
val startWidth: Int
val startHeight: Int
if (currentBackEvent == null) {
startWidth = max(constraints.minWidth, SearchBarMinWidth.roundToPx())
.coerceAtMost(min(constraints.maxWidth, SearchBarMaxWidth.roundToPx()))
startHeight = max(constraints.minHeight, InputFieldHeight.roundToPx())
.coerceAtMost(constraints.maxHeight)
} else {
startWidth =
(constraints.maxWidth * SearchBarPredictiveBackMinScale).roundToInt()
startHeight =
(constraints.maxHeight * SearchBarPredictiveBackMinScale).roundToInt()
}
val endWidth = constraints.maxWidth
val endHeight = constraints.maxHeight
val width = lerp(startWidth, endWidth, animationProgress.value)
val height =
lerp(startHeight, endHeight, animationProgress.value) + animatedTopPadding
val minOffsetMargin = SearchBarPredictiveBackMinMargin.roundToPx()
val predictiveBackOffsetX = calculatePredictiveBackOffsetX(
constraints,
minOffsetMargin,
currentBackEvent,
animationProgress.value,
)
val predictiveBackOffsetY = calculatePredictiveBackOffsetY(
constraints,
minOffsetMargin,
currentBackEvent,
firstBackEvent,
height,
SearchBarPredictiveBackMaxOffsetY.roundToPx(),
)
val placeable = measurable.measure(
Constraints
.fixed(width, height)
.offset(
vertical = -animatedTopPadding,
),
)
layout(width, height) {
placeable.placeRelative(
predictiveBackOffsetX,
animatedTopPadding + predictiveBackOffsetY,
)
}
},
) {
Column {
val animatedInputFieldPadding = remember {
AnimatedPaddingValues2(animationProgress.asState(), topPadding)
}
SearchBarInputField(
state = state,
onSearch = onSearch,
onActiveChange = onActiveChange,
modifier = Modifier.padding(paddingValues = animatedInputFieldPadding),
enabled = enabled,
label = title.takeUnless { active || animationProgress.value > 0 || state.text.isNotEmpty() },
placeholder = placeholder,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
colors = colors.inputFieldColors,
interactionSource = interactionSource,
)
val showResults by remember {
derivedStateOf(structuralEqualityPolicy()) { animationProgress.value > 0 }
}
if (showResults) {
Column(Modifier.graphicsLayer { alpha = animationProgress.value }) {
HorizontalDivider(color = colors.dividerColor)
content()
}
}
}
}
val isFocused = interactionSource.collectIsFocusedAsState().value
val shouldClearFocus = !active && isFocused
if (shouldClearFocus) {
LaunchedEffect(true) {
// Not strictly needed according to the motion spec, but since the animation already has
// a delay, this works around b/261632544.
delay(AnimationDelayMillis.toLong())
focusManager.clearFocus()
}
}
LaunchedEffect(active) {
val animationInProgress = animationProgress.value > 0 && animationProgress.value < 1
val animationSpec =
if (animationInProgress) {
AnimationPredictiveBackExitFloatSpec
} else if (active) {
AnimationEnterFloatSpec
} else {
AnimationExitFloatSpec
}
animationProgress.animateTo(
targetValue = if (active) 1f else 0f,
animationSpec = animationSpec,
)
}
val mutatorMutex = remember { MutatorMutex() }
if (active) {
PredictiveBackHandler(true) { progress ->
mutatorMutex.mutate {
var canceled = false
try {
progress.collect { backEvent ->
if (firstBackEvent == null) {
firstBackEvent = backEvent
}
currentBackEvent = backEvent
val interpolatedProgress = EaseOut.transform(backEvent.progress)
animationProgress.snapTo(targetValue = 1 - interpolatedProgress)
}
} catch (e: CancellationException) {
canceled = true
} finally {
firstBackEvent = null
currentBackEvent = null
onActiveChange(canceled)
}
}
}
}
}
private fun calculatePredictiveBackOffsetX(
constraints: Constraints,
minMargin: Int,
currentBackEvent: BackEventCompat?,
progress: Float,
): Int {
if (currentBackEvent == null) {
return 0
}
val directionMultiplier = if (currentBackEvent.swipeEdge == BackEventCompat.EDGE_LEFT) 1 else -1
val maxOffsetX =
(constraints.maxWidth * SearchBarPredictiveBackMaxOffsetXRatio) - minMargin
return (maxOffsetX * (1 - progress)).toInt() * directionMultiplier
}
private fun calculatePredictiveBackOffsetY(
constraints: Constraints,
minMargin: Int,
currentBackEvent: BackEventCompat?,
firstBackEvent: BackEventCompat?,
height: Int,
maxOffsetY: Int,
): Int {
if (firstBackEvent == null || currentBackEvent == null) {
return 0
}
val availableVerticalSpace = max(0, (constraints.maxHeight - height) / 2 - minMargin)
val adjustedMaxOffsetY = min(availableVerticalSpace, maxOffsetY)
val yDelta = currentBackEvent.touchY - firstBackEvent.touchY
val yProgress = abs(yDelta) / constraints.maxHeight
val directionMultiplier = sign(yDelta)
return (lerp(0, adjustedMaxOffsetY, yProgress) * directionMultiplier).roundToInt()
}
@Composable
private fun SearchBarInputField(
state: TextFieldState,
onSearch: () -> Unit,
onActiveChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
colors: TextFieldColors = SearchBarDefaults.inputFieldColors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val focusRequester = remember { FocusRequester() }
val focused by interactionSource.collectIsFocusedAsState()
val textColor = LocalTextStyle.current.color.takeOrElse {
if (focused) colors.focusedTextColor else colors.unfocusedTextColor
}
BasicTextField2(
state = state,
modifier = modifier
.height(InputFieldHeight)
.fillMaxWidth()
.focusRequester(focusRequester)
.onFocusChanged { if (it.isFocused) onActiveChange(true) }
.semantics {
onClick {
focusRequester.requestFocus()
true
}
},
enabled = enabled,
lineLimits = TextFieldLineLimits.SingleLine,
textStyle = LocalTextStyle.current.merge(TextStyle(color = textColor)),
cursorBrush = SolidColor(colors.cursorColor),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = { onSearch() }),
interactionSource = interactionSource,
decorator = @Composable { innerTextField ->
TextFieldDefaults.DecorationBox(
value = state.text.toString(),
innerTextField = innerTextField,
enabled = enabled,
singleLine = true,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
label = label,
placeholder = placeholder,
leadingIcon = leadingIcon?.let { leading ->
{
Box(Modifier.offset(x = SearchBarIconOffsetX)) { leading() }
}
},
trailingIcon = trailingIcon?.let { trailing ->
{
Box(Modifier.offset(x = -SearchBarIconOffsetX)) { trailing() }
}
},
shape = SearchBarDefaults.inputFieldShape,
colors = colors,
contentPadding = TextFieldDefaults.contentPaddingWithoutLabel(),
container = {},
)
},
)
}
@Stable
private class AnimatedPaddingValues2(
val animationProgress: State<Float>,
val topPadding: State<Dp>,
) : PaddingValues {
override fun calculateTopPadding(): Dp = topPadding.value * animationProgress.value
override fun calculateBottomPadding(): Dp = SearchBarVerticalPadding * animationProgress.value
override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = 0.dp
override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = 0.dp
}
// Measurement specs
private val SearchBarCornerRadius: Dp = InputFieldHeight / 2
internal val SearchBarMinWidth: Dp = 360.dp
private val SearchBarMaxWidth: Dp = 720.dp
internal val SearchBarVerticalPadding: Dp = 8.dp
internal val SearchBarHorizontalPadding: Dp = 16.dp
// Search bar has 16dp padding between icons and start/end, while by default text field has 12dp.
private val SearchBarIconOffsetX: Dp = 4.dp
private const val SearchBarPredictiveBackMinScale: Float = 9f / 10f
private val SearchBarPredictiveBackMinMargin: Dp = 8.dp
private const val SearchBarPredictiveBackMaxOffsetXRatio: Float = 1f / 20f
private val SearchBarPredictiveBackMaxOffsetY: Dp = 24.dp
// Animation specs
private const val AnimationEnterDurationMillis: Int = 600
private const val AnimationExitDurationMillis: Int = 350
private const val AnimationDelayMillis: Int = 100
private val AnimationEnterEasing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1.0f)
private val AnimationExitEasing = CubicBezierEasing(0.0f, 1.0f, 0.0f, 1.0f)
private val AnimationEnterFloatSpec: FiniteAnimationSpec<Float> = tween(
durationMillis = AnimationEnterDurationMillis,
delayMillis = AnimationDelayMillis,
easing = AnimationEnterEasing,
)
private val AnimationExitFloatSpec: FiniteAnimationSpec<Float> = tween(
durationMillis = AnimationExitDurationMillis,
delayMillis = AnimationDelayMillis,
easing = AnimationExitEasing,
)
private val AnimationPredictiveBackExitFloatSpec: FiniteAnimationSpec<Float> = tween(
durationMillis = AnimationExitDurationMillis,
easing = AnimationExitEasing,
)
@@ -1,63 +0,0 @@
/*
* Copyright 2023 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.navigation.compose
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph
import androidx.navigation.NavGraphNavigator
import androidx.navigation.Navigator
import androidx.navigation.NavigatorProvider
/**
* Custom subclass of [NavGraphNavigator] that adds support for defining
* transitions at the navigation graph level.
*/
@Navigator.Name("navigation")
internal class ComposeNavGraphNavigator(
navigatorProvider: NavigatorProvider,
) : NavGraphNavigator(navigatorProvider) {
override fun createDestination(): NavGraph {
return ComposeNavGraph(this)
}
internal class ComposeNavGraph(
navGraphNavigator: Navigator<out NavGraph>,
) : NavGraph(navGraphNavigator) {
internal var enterTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?
)? = null
internal var exitTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? = null
internal var popEnterTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?
)? = null
internal var popExitTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? = null
}
}
@@ -1,128 +0,0 @@
/*
* Copyright 2020 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.navigation.compose
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import androidx.navigation.compose.ComposeNavigator.Destination
/**
* Navigator that navigates through [Composable]s. Every destination using this Navigator must
* set a valid [Composable] by setting it directly on an instantiated [Destination] or calling
* [composable].
*/
@Navigator.Name("composable")
public class ComposeNavigator : Navigator<Destination>() {
/**
* Get the map of transitions currently in progress from the [state].
*/
internal val transitionsInProgress get() = state.transitionsInProgress
/**
* Get the back stack from the [state].
*/
public val backStack get() = state.backStack
internal val isPop = mutableStateOf(false)
override fun navigate(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Extras?,
) {
entries.forEach { entry ->
state.pushWithTransition(entry)
}
isPop.value = false
}
override fun createDestination(): Destination {
return Destination(this) { }
}
override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
state.popWithTransition(popUpTo, savedState)
isPop.value = true
}
/**
* Callback to mark a navigation in transition as complete.
*
* This should be called in conjunction with [navigate] and [popBackStack] as those
* calls merely start a transition to the target destination, and requires manually marking
* the transition as complete by calling this method.
*
* Failing to call this method could result in entries being prevented from reaching their
* final [Lifecycle.State].
*/
public fun onTransitionComplete(entry: NavBackStackEntry) {
state.markTransitionComplete(entry)
}
/**
* NavDestination specific to [ComposeNavigator]
*/
@NavDestination.ClassType(Composable::class)
public class Destination(
navigator: ComposeNavigator,
internal val content:
@Composable AnimatedContentScope.(@JvmSuppressWildcards NavBackStackEntry) -> Unit,
) : NavDestination(navigator) {
@Deprecated(
message = "Deprecated in favor of Destination that supports AnimatedContent",
level = DeprecationLevel.HIDDEN,
)
constructor(
navigator: ComposeNavigator,
content: @Composable (NavBackStackEntry) -> @JvmSuppressWildcards Unit,
) : this(navigator, content = { entry -> content(entry) })
internal var enterTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?
)? = null
internal var exitTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? = null
internal var popEnterTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?
)? = null
internal var popExitTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? = null
}
internal companion object {
internal const val NAME = "composable"
}
}
@@ -1,137 +0,0 @@
/*
* Copyright 2021 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.navigation.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavBackStackEntry
import androidx.navigation.compose.DialogNavigator.Destination
/**
* Show each [Destination] on the [DialogNavigator]'s back stack as a [Dialog].
*
* Note that [NavHost] will call this for you; you do not need to call it manually.
*/
@Composable
public fun DialogHost(dialogNavigator: DialogNavigator) {
val saveableStateHolder = rememberSaveableStateHolder()
val dialogBackStack by dialogNavigator.backStack.collectAsState()
val visibleBackStack = rememberVisibleList(dialogBackStack)
visibleBackStack.PopulateVisibleList(dialogBackStack)
val transitionInProgress by dialogNavigator.transitionInProgress.collectAsState()
val dialogsToDispose = remember { mutableStateListOf<NavBackStackEntry>() }
visibleBackStack.forEach { backStackEntry ->
val destination = backStackEntry.destination as Destination
Dialog(
onDismissRequest = { dialogNavigator.dismiss(backStackEntry) },
properties = destination.dialogProperties,
) {
DisposableEffect(backStackEntry) {
dialogsToDispose.add(backStackEntry)
onDispose {
dialogNavigator.onTransitionComplete(backStackEntry)
dialogsToDispose.remove(backStackEntry)
}
}
// while in the scope of the composable, we provide the navBackStackEntry as the
// ViewModelStoreOwner and LifecycleOwner
backStackEntry.LocalOwnersProvider(saveableStateHolder) {
destination.content(backStackEntry)
}
}
}
// Dialogs may have been popped before it was composed. To prevent leakage, we need to
// mark popped entries as complete here. Check that we don't accidentally complete popped
// entries that were composed, unless they were disposed of already.
LaunchedEffect(transitionInProgress, dialogsToDispose) {
transitionInProgress.forEach { entry ->
if (!dialogNavigator.backStack.value.contains(entry) &&
!dialogsToDispose.contains(entry)
) {
dialogNavigator.onTransitionComplete(entry)
}
}
}
}
@Composable
internal fun MutableList<NavBackStackEntry>.PopulateVisibleList(
backStack: Collection<NavBackStackEntry>,
) {
val isInspecting = LocalInspectionMode.current
backStack.forEach { entry ->
DisposableEffect(entry.lifecycle) {
val observer = LifecycleEventObserver { _, event ->
// show dialog in preview
if (isInspecting && !contains(entry)) {
add(entry)
}
// ON_START -> add to visibleBackStack, ON_STOP -> remove from visibleBackStack
if (event == Lifecycle.Event.ON_START) {
// We want to treat the visible lists as Sets but we want to keep
// the functionality of mutableStateListOf() so that we recompose in response
// to adds and removes.
if (!contains(entry)) {
add(entry)
}
}
if (event == Lifecycle.Event.ON_STOP) {
remove(entry)
}
}
entry.lifecycle.addObserver(observer)
onDispose {
entry.lifecycle.removeObserver(observer)
}
}
}
}
@Composable
internal fun rememberVisibleList(
backStack: Collection<NavBackStackEntry>,
): SnapshotStateList<NavBackStackEntry> {
// show dialog in preview
val isInspecting = LocalInspectionMode.current
return remember(backStack) {
mutableStateListOf<NavBackStackEntry>().also {
it.addAll(
backStack.filter { entry ->
if (isInspecting) {
true
} else {
entry.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
}
},
)
}
}
}
@@ -1,96 +0,0 @@
/*
* Copyright 2020 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.navigation.compose
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.FloatingWindow
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import androidx.navigation.compose.DialogNavigator.Destination
/**
* Navigator that navigates through [Composable]s that will be hosted within a
* [Dialog]. Every destination using this Navigator must set a valid [Composable] by setting it
* directly on an instantiated [Destination] or calling [dialog].
*/
@Navigator.Name("dialog")
public class DialogNavigator : Navigator<Destination>() {
/**
* Get the back stack from the [state].
*/
internal val backStack get() = state.backStack
/**
* Get the transitioning dialogs from the [state].
*/
internal val transitionInProgress get() = state.transitionsInProgress
/**
* Dismiss the dialog destination associated with the given [backStackEntry].
*/
internal fun dismiss(backStackEntry: NavBackStackEntry) {
popBackStack(backStackEntry, false)
}
override fun navigate(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Extras?,
) {
entries.forEach { entry ->
state.push(entry)
}
}
override fun createDestination(): Destination {
return Destination(this) { }
}
override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
state.popWithTransition(popUpTo, savedState)
// When popping, the incoming dialog is marked transitioning to hold it in
// STARTED. With pop complete, we can remove it from transition so it can move to RESUMED.
val popIndex = state.transitionsInProgress.value.indexOf(popUpTo)
// do not mark complete for entries up to and including popUpTo
state.transitionsInProgress.value.forEachIndexed { index, entry ->
if (index > popIndex) onTransitionComplete(entry)
}
}
internal fun onTransitionComplete(entry: NavBackStackEntry) {
state.markTransitionComplete(entry)
}
/**
* NavDestination specific to [DialogNavigator]
*/
@NavDestination.ClassType(Composable::class)
public class Destination(
navigator: DialogNavigator,
internal val dialogProperties: DialogProperties = DialogProperties(),
internal val content: @Composable (NavBackStackEntry) -> Unit,
) : NavDestination(navigator), FloatingWindow
internal companion object {
internal const val NAME = "dialog"
}
}
@@ -1,88 +0,0 @@
/*
* Copyright 2021 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("ktlint:standard:property-naming", "ktlint:standard:function-naming")
package androidx.navigation.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.saveable.SaveableStateHolder
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import java.lang.ref.WeakReference
import java.util.UUID
/**
* Provides [this] [NavBackStackEntry] as [LocalViewModelStoreOwner], [LocalLifecycleOwner] and
* [LocalSavedStateRegistryOwner] to the [content] and saves the [content]'s saveable states with
* the given [saveableStateHolder].
*
* @param saveableStateHolder The [SaveableStateHolder] that holds the saved states. The same
* holder should be used for all [NavBackStackEntry]s in the encapsulating [Composable] and the
* holder should be hoisted.
* @param content The content [Composable]
*/
@Composable
public fun NavBackStackEntry.LocalOwnersProvider(
saveableStateHolder: SaveableStateHolder,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
LocalViewModelStoreOwner provides this,
LocalLifecycleOwner provides this,
LocalSavedStateRegistryOwner provides this,
) {
saveableStateHolder.SaveableStateProvider(content)
}
}
@Composable
private fun SaveableStateHolder.SaveableStateProvider(content: @Composable () -> Unit) {
val viewModel = viewModel<BackStackEntryIdViewModel>()
// Stash a reference to the SaveableStateHolder in the ViewModel so that
// it is available when the ViewModel is cleared, marking the permanent removal of this
// NavBackStackEntry from the back stack. Which, because of animations,
// only happens after this leaves composition. Which means we can't rely on
// DisposableEffect to clean up this reference (as it'll be cleaned up too early)
viewModel.saveableStateHolderRef = WeakReference(this)
SaveableStateProvider(viewModel.id, content)
}
internal class BackStackEntryIdViewModel(handle: SavedStateHandle) : ViewModel() {
private val IdKey = "SaveableStateHolder_BackStackEntryKey"
// we create our own id for each back stack entry to support multiple entries of the same
// destination. this id will be restored by SavedStateHandle
val id: UUID = handle.get<UUID>(IdKey) ?: UUID.randomUUID().also { handle.set(IdKey, it) }
lateinit var saveableStateHolderRef: WeakReference<SaveableStateHolder>
// onCleared will be called on the entries removed from the back stack. here we notify
// SaveableStateProvider that we should remove any state is had associated with this
// destination as it is no longer needed.
override fun onCleared() {
super.onCleared()
saveableStateHolderRef.get()?.removeState(id)
saveableStateHolderRef.clear()
}
}
@@ -1,243 +0,0 @@
/*
* Copyright 2020 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.navigation.compose
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink
import androidx.navigation.NavGraph
import androidx.navigation.NavGraphBuilder
import androidx.navigation.get
/**
* Add the [Composable] to the [NavGraphBuilder]
*
* @param route route for the destination
* @param arguments list of arguments to associate with destination
* @param deepLinks list of deep links to associate with the destinations
* @param content composable for the destination
*/
@Deprecated(
message = "Deprecated in favor of composable builder that supports AnimatedContent",
level = DeprecationLevel.HIDDEN,
)
public fun NavGraphBuilder.composable(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
content: @Composable (NavBackStackEntry) -> Unit,
) {
addDestination(
ComposeNavigator.Destination(provider[ComposeNavigator::class]) {
entry ->
content(entry)
}.apply {
this.route = route
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
deepLinks.forEach { deepLink ->
addDeepLink(deepLink)
}
},
)
}
/**
* Add the [Composable] to the [NavGraphBuilder]
*
* @param route route for the destination
* @param arguments list of arguments to associate with destination
* @param deepLinks list of deep links to associate with the destinations
* @param enterTransition callback to determine the destination's enter transition
* @param exitTransition callback to determine the destination's exit transition
* @param popEnterTransition callback to determine the destination's popEnter transition
* @param popExitTransition callback to determine the destination's popExit transition
* @param content composable for the destination
*/
public fun NavGraphBuilder.composable(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
enterTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?
)? = null,
exitTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? = null,
popEnterTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?
)? =
enterTransition,
popExitTransition: (
@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? =
exitTransition,
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
) {
addDestination(
ComposeNavigator.Destination(
provider[ComposeNavigator::class],
content,
).apply {
this.route = route
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
deepLinks.forEach { deepLink ->
addDeepLink(deepLink)
}
this.enterTransition = enterTransition
this.exitTransition = exitTransition
this.popEnterTransition = popEnterTransition
this.popExitTransition = popExitTransition
},
)
}
/**
* Construct a nested [NavGraph]
*
* @sample androidx.navigation.compose.samples.NestedNavInGraphWithArgs
*
* @param startDestination the starting destination's route for this NavGraph
* @param route the destination's unique route
* @param arguments list of arguments to associate with destination
* @param deepLinks list of deep links to associate with the destinations
* @param builder the builder used to construct the graph
*/
@Deprecated(
message = "Deprecated in favor of navigation builder that supports AnimatedContent",
level = DeprecationLevel.HIDDEN,
)
public fun NavGraphBuilder.navigation(
startDestination: String,
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
builder: NavGraphBuilder.() -> Unit,
) {
addDestination(
NavGraphBuilder(provider, startDestination, route).apply(builder).build().apply {
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
deepLinks.forEach { deepLink ->
addDeepLink(deepLink)
}
},
)
}
/**
* Construct a nested [NavGraph]
*
* @param startDestination the starting destination's route for this NavGraph
* @param route the destination's unique route
* @param arguments list of arguments to associate with destination
* @param deepLinks list of deep links to associate with the destinations
* @param enterTransition callback to define enter transitions for destination in this NavGraph
* @param exitTransition callback to define exit transitions for destination in this NavGraph
* @param popEnterTransition callback to define pop enter transitions for destination in this
* NavGraph
* @param popExitTransition callback to define pop exit transitions for destination in this NavGraph
* @param builder the builder used to construct the graph
*
* @return the newly constructed nested NavGraph
*/
public fun NavGraphBuilder.navigation(
startDestination: String,
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
enterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? =
null,
exitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? =
null,
popEnterTransition: (
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?
)? = enterTransition,
popExitTransition: (
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? = exitTransition,
builder: NavGraphBuilder.() -> Unit,
) {
addDestination(
NavGraphBuilder(provider, startDestination, route).apply(builder).build().apply {
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
deepLinks.forEach { deepLink ->
addDeepLink(deepLink)
}
if (this is ComposeNavGraphNavigator.ComposeNavGraph) {
this.enterTransition = enterTransition
this.exitTransition = exitTransition
this.popEnterTransition = popEnterTransition
this.popExitTransition = popExitTransition
}
},
)
}
/**
* Add the [Composable] to the [NavGraphBuilder] that will be hosted within a
* [androidx.compose.ui.window.Dialog]. This is suitable only when this dialog represents
* a separate screen in your app that needs its own lifecycle and saved state, independent
* of any other destination in your navigation graph. For use cases such as `AlertDialog`,
* you should use those APIs directly in the [composable] destination that wants to show that
* dialog.
*
* @param route route for the destination
* @param arguments list of arguments to associate with destination
* @param deepLinks list of deep links to associate with the destinations
* @param dialogProperties properties that should be passed to [androidx.compose.ui.window.Dialog].
* @param content composable content for the destination that will be hosted within the Dialog
*/
public fun NavGraphBuilder.dialog(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
dialogProperties: DialogProperties = DialogProperties(),
content: @Composable (NavBackStackEntry) -> Unit,
) {
addDestination(
DialogNavigator.Destination(
provider[DialogNavigator::class],
dialogProperties,
content,
).apply {
this.route = route
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
deepLinks.forEach { deepLink ->
addDeepLink(deepLink)
}
},
)
}
@@ -1,397 +0,0 @@
/*
* Copyright 2020 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.navigation.compose
import android.annotation.SuppressLint
import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.EaseOut
import androidx.compose.animation.core.ExperimentalTransitionApi
import androidx.compose.animation.core.SeekableTransitionState
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.Navigator
import androidx.navigation.createGraph
import androidx.navigation.get
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.collectAsState
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
/**
* Provides in place in the Compose hierarchy for self contained navigation to occur.
*
* Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
* the provided [navController].
*
* The builder passed into this method is [remember]ed. This means that for this NavHost, the
* contents of the builder cannot be changed.
*
* @sample androidx.navigation.compose.samples.NavScaffold
*
* @param navController the navController for this host
* @param startDestination the route for the start destination
* @param modifier The modifier to be applied to the layout.
* @param route the route for the graph
* @param builder the builder used to construct the graph
*/
@Deprecated(
message = "Deprecated in favor of NavHost that supports AnimatedContent",
level = DeprecationLevel.HIDDEN,
)
@Composable
public fun NavHost(
navController: NavHostController,
startDestination: String,
modifier: Modifier = Modifier,
route: String? = null,
builder: NavGraphBuilder.() -> Unit,
) {
NavHost(
navController,
remember(route, startDestination, builder) {
navController.createGraph(startDestination, route, builder)
},
modifier,
)
}
/**
* Provides in place in the Compose hierarchy for self contained navigation to occur.
*
* Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
* the provided [navController].
*
* The builder passed into this method is [remember]ed. This means that for this NavHost, the
* contents of the builder cannot be changed.
*
* @param navController the navController for this host
* @param startDestination the route for the start destination
* @param modifier The modifier to be applied to the layout.
* @param contentAlignment The [Alignment] of the [AnimatedContent]
* @param route the route for the graph
* @param enterTransition callback to define enter transitions for destination in this host
* @param exitTransition callback to define exit transitions for destination in this host
* @param popEnterTransition callback to define popEnter transitions for destination in this host
* @param popExitTransition callback to define popExit transitions for destination in this host
* @param builder the builder used to construct the graph
*/
@Composable
public fun NavHost(
navController: NavHostController,
startDestination: String,
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.Center,
route: String? = null,
enterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) =
{ fadeIn(animationSpec = tween(700)) },
exitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
{ fadeOut(animationSpec = tween(700)) },
popEnterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) =
enterTransition,
popExitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
exitTransition,
builder: NavGraphBuilder.() -> Unit,
) {
NavHost(
navController,
remember(route, startDestination, builder) {
navController.createGraph(startDestination, route, builder)
},
modifier,
contentAlignment,
enterTransition,
exitTransition,
popEnterTransition,
popExitTransition,
)
}
/**
* Provides in place in the Compose hierarchy for self contained navigation to occur.
*
* Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
* the provided [navController].
*
* The graph passed into this method is [remember]ed. This means that for this NavHost, the graph
* cannot be changed.
*
* @param navController the navController for this host
* @param graph the graph for this host
* @param modifier The modifier to be applied to the layout.
*/
@Deprecated(
message = "Deprecated in favor of NavHost that supports AnimatedContent",
level = DeprecationLevel.HIDDEN,
)
@Composable
public fun NavHost(
navController: NavHostController,
graph: NavGraph,
modifier: Modifier = Modifier,
) = NavHost(navController, graph, modifier)
/**
* Provides in place in the Compose hierarchy for self contained navigation to occur.
*
* Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
* the provided [navController].
*
* @param navController the navController for this host
* @param graph the graph for this host
* @param modifier The modifier to be applied to the layout.
* @param contentAlignment The [Alignment] of the [AnimatedContent]
* @param enterTransition callback to define enter transitions for destination in this host
* @param exitTransition callback to define exit transitions for destination in this host
* @param popEnterTransition callback to define popEnter transitions for destination in this host
* @param popExitTransition callback to define popExit transitions for destination in this host
*/
@OptIn(ExperimentalTransitionApi::class)
@SuppressLint("StateFlowValueCalledInComposition")
@Composable
public fun NavHost(
navController: NavHostController,
graph: NavGraph,
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.Center,
enterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) =
{ fadeIn(animationSpec = tween(700)) },
exitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
{ fadeOut(animationSpec = tween(700)) },
popEnterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) =
enterTransition,
popExitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
exitTransition,
) {
val lifecycleOwner = LocalLifecycleOwner.current
val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"NavHost requires a ViewModelStoreOwner to be provided via LocalViewModelStoreOwner"
}
navController.setViewModelStore(viewModelStoreOwner.viewModelStore)
// Then set the graph
navController.graph = graph
// Find the ComposeNavigator, returning early if it isn't found
// (such as is the case when using TestNavHostController)
val composeNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
ComposeNavigator.NAME,
) as? ComposeNavigator ?: return
val currentBackStack by composeNavigator.backStack.collectAsState()
DisposableEffect(lifecycleOwner) {
// Setup the navController with proper owners
navController.setLifecycleOwner(lifecycleOwner)
onDispose { }
}
val saveableStateHolder = rememberSaveableStateHolder()
val allVisibleEntries by navController.visibleEntries.collectAsState()
// Intercept back only when there's a destination to pop
val visibleEntries by remember {
derivedStateOf {
allVisibleEntries.filter { entry ->
entry.destination.navigatorName == ComposeNavigator.NAME
}
}
}
val backStackEntry: NavBackStackEntry? = visibleEntries.lastOrNull()
val zIndices = remember { mutableMapOf<String, Float>() }
if (backStackEntry != null) {
val enablePredictiveBack by Settings.predictiveNavAnim.collectAsState()
val transitionState = remember { SeekableTransitionState(backStackEntry) }
val transition = if (enablePredictiveBack) {
LaunchedEffect(backStackEntry) {
transitionState.animateTo(backStackEntry)
}
rememberTransition(transitionState, label = "entry")
} else {
updateTransition(backStackEntry, label = "entry")
}
var inPredictiveBack by remember { mutableStateOf(false) }
PredictiveBackHandler(currentBackStack.size > 1) { backEvent ->
inPredictiveBack = true
try {
val prev = requireNotNull(navController.previousBackStackEntry)
backEvent.collect {
val interpolated = EaseOut.transform(it.progress)
transitionState.snapTo(prev, interpolated)
}
inPredictiveBack = false
navController.popBackStack()
} catch (e: CancellationException) {
withContext(NonCancellable) {
transitionState.animateTo(backStackEntry, snap())
inPredictiveBack = false
}
}
}
val finalEnter: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
val targetDestination = targetState.destination as ComposeNavigator.Destination
if (composeNavigator.isPop.value || inPredictiveBack) {
targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
destination.createPopEnterTransition(this)
} ?: popEnterTransition.invoke(this)
} else {
targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
destination.createEnterTransition(this)
} ?: enterTransition.invoke(this)
}
}
val finalExit: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
val initialDestination = initialState.destination as ComposeNavigator.Destination
if (composeNavigator.isPop.value || inPredictiveBack) {
initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
destination.createPopExitTransition(this)
} ?: popExitTransition.invoke(this)
} else {
initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
destination.createExitTransition(this)
} ?: exitTransition.invoke(this)
}
}
DisposableEffect(true) {
onDispose {
visibleEntries.forEach { entry ->
composeNavigator.onTransitionComplete(entry)
}
}
}
transition.AnimatedContent(
modifier,
transitionSpec = {
// If the initialState of the AnimatedContent is not in visibleEntries, we are in
// a case where visible has cleared the old state for some reason, so instead of
// attempting to animate away from the initialState, we skip the animation.
if (initialState in visibleEntries) {
val initialZIndex = zIndices[initialState.id]
?: 0f.also { zIndices[initialState.id] = 0f }
val targetZIndex = when {
targetState.id == initialState.id -> initialZIndex
composeNavigator.isPop.value -> initialZIndex - 1f
else -> initialZIndex + 1f
}.also { zIndices[targetState.id] = it }
ContentTransform(finalEnter(this), finalExit(this), targetZIndex)
} else {
EnterTransition.None togetherWith ExitTransition.None
}
},
contentAlignment,
contentKey = { it.id },
) { currentEntry ->
// while in the scope of the composable, we provide the navBackStackEntry as the
// ViewModelStoreOwner and LifecycleOwner
currentEntry.LocalOwnersProvider(saveableStateHolder) {
(currentEntry.destination as ComposeNavigator.Destination)
.content(this, currentEntry)
}
}
LaunchedEffect(transition.currentState, transition.targetState) {
if (transition.currentState == transition.targetState) {
visibleEntries.forEach { entry ->
composeNavigator.onTransitionComplete(entry)
}
zIndices
.filter { it.key != transition.targetState.id }
.forEach { zIndices.remove(it.key) }
}
}
}
val dialogNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
DialogNavigator.NAME,
) as? DialogNavigator ?: return
// Show any dialog destinations
DialogHost(dialogNavigator)
}
private fun NavDestination.createEnterTransition(
scope: AnimatedContentTransitionScope<NavBackStackEntry>,
): EnterTransition? = when (this) {
is ComposeNavigator.Destination -> this.enterTransition?.invoke(scope)
is ComposeNavGraphNavigator.ComposeNavGraph -> this.enterTransition?.invoke(scope)
else -> null
}
private fun NavDestination.createExitTransition(
scope: AnimatedContentTransitionScope<NavBackStackEntry>,
): ExitTransition? = when (this) {
is ComposeNavigator.Destination -> this.exitTransition?.invoke(scope)
is ComposeNavGraphNavigator.ComposeNavGraph -> this.exitTransition?.invoke(scope)
else -> null
}
private fun NavDestination.createPopEnterTransition(
scope: AnimatedContentTransitionScope<NavBackStackEntry>,
): EnterTransition? = when (this) {
is ComposeNavigator.Destination -> this.popEnterTransition?.invoke(scope)
is ComposeNavGraphNavigator.ComposeNavGraph -> this.popEnterTransition?.invoke(scope)
else -> null
}
private fun NavDestination.createPopExitTransition(
scope: AnimatedContentTransitionScope<NavBackStackEntry>,
): ExitTransition? = when (this) {
is ComposeNavigator.Destination -> this.popExitTransition?.invoke(scope)
is ComposeNavGraphNavigator.ComposeNavGraph -> this.popExitTransition?.invoke(scope)
else -> null
}
@@ -1,86 +0,0 @@
/*
* Copyright 2020 The Android 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("ktlint:standard:property-naming", "ktlint:standard:function-naming")
package androidx.navigation.compose
import android.content.Context
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavHostController
import androidx.navigation.Navigator
/**
* Gets the current navigation back stack entry as a [MutableState]. When the given navController
* changes the back stack due to a [NavController.navigate] or [NavController.popBackStack] this
* will trigger a recompose and return the top entry on the back stack.
*
* @return a mutable state of the current back stack entry
*/
@Composable
public fun NavController.currentBackStackEntryAsState(): State<NavBackStackEntry?> {
return currentBackStackEntryFlow.collectAsState(null)
}
/**
* Creates a NavHostController that handles the adding of the [ComposeNavigator] and
* [DialogNavigator]. Additional [Navigator] instances can be passed through [navigators] to
* be applied to the returned NavController. Note that each [Navigator] must be separately
* remembered before being passed in here: any changes to those inputs will cause the
* NavController to be recreated.
*
* @see NavHost
*/
@Composable
public fun rememberNavController(
vararg navigators: Navigator<out NavDestination>,
): NavHostController {
val context = LocalContext.current
return rememberSaveable(inputs = navigators, saver = NavControllerSaver(context)) {
createNavController(context)
}.apply {
for (navigator in navigators) {
navigatorProvider.addNavigator(navigator)
}
}
}
private fun createNavController(context: Context) =
NavHostController(context).apply {
navigatorProvider.addNavigator(ComposeNavGraphNavigator(navigatorProvider))
navigatorProvider.addNavigator(ComposeNavigator())
navigatorProvider.addNavigator(DialogNavigator())
}
/**
* Saver to save and restore the NavController across config change and process death.
*/
private fun NavControllerSaver(
context: Context,
): Saver<NavHostController, *> = Saver<NavHostController, Bundle>(
save = { it.saveState() },
restore = { createNavController(context).apply { restoreState(it) } },
)
@@ -180,7 +180,6 @@ fun WebView(
is WebContent.Url -> {
wv.loadUrl(content.url, content.additionalHttpHeaders)
}
is WebContent.Data -> {
wv.loadDataWithBaseURL(
content.baseUrl,
@@ -190,14 +189,12 @@ fun WebView(
content.historyUrl,
)
}
is WebContent.Post -> {
wv.postUrl(
content.url,
content.postData,
)
}
is WebContent.NavigatorOnly -> {
// NO-OP
}
@@ -502,11 +499,9 @@ class WebViewNavigator(private val coroutineScope: CoroutineScope) {
event.encoding,
event.historyUrl,
)
is NavigationEvent.LoadUrl -> {
loadUrl(event.url, event.additionalHttpHeaders)
}
is NavigationEvent.PostUrl -> {
postUrl(event.url, event.postData)
}
@@ -635,8 +630,7 @@ data class WebViewError(
fun rememberWebViewState(
url: String,
additionalHttpHeaders: Map<String, String> = emptyMap(),
): WebViewState =
// Rather than using .apply {} here we will recreate the state, this prevents
): WebViewState = // Rather than using .apply {} here we will recreate the state, this prevents
// a recomposition loop when the webview updates the url itself.
remember {
WebViewState(
@@ -17,135 +17,137 @@
package com.hippo.ehviewer
import android.app.Application
import android.content.ComponentCallbacks2
import android.content.Context
import android.os.StrictMode
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.content.res.AppCompatResources
import androidx.collection.LruCache
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.Composer
import androidx.compose.runtime.tooling.ComposeStackTraceMode
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.coroutineScope
import coil3.EventListener
import coil3.SingletonImageLoader
import coil3.asCoilImage
import coil3.asImage
import coil3.gif.AnimatedImageDecoder
import coil3.network.NetworkFetcher
import coil3.gif.GifDecoder
import coil3.network.ConnectivityChecker
import coil3.network.ktor3.KtorNetworkFetcherFactory
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import coil3.request.allowRgb565
import coil3.request.crossfade
import coil3.serviceLoaderEnabled
import coil3.util.DebugLogger
import com.hippo.ehviewer.client.EhCookieStore
import com.ehviewer.core.database.SearchDatabase
import com.ehviewer.core.database.roomDb
import com.ehviewer.core.files.deleteContent
import com.ehviewer.core.ui.util.initSETConnection
import com.ehviewer.core.util.isAtLeastO
import com.ehviewer.core.util.isAtLeastP
import com.ehviewer.core.util.isAtLeastS
import com.ehviewer.core.util.launchIO
import com.ehviewer.core.util.logcat
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.coil.AnimatedWebPDecoder
import com.hippo.ehviewer.coil.CropBorderInterceptor
import com.hippo.ehviewer.coil.DetectBorderInterceptor
import com.hippo.ehviewer.coil.DownloadThumbInterceptor
import com.hippo.ehviewer.coil.HardwareBitmapInterceptor
import com.hippo.ehviewer.coil.MapExtraInfoInterceptor
import com.hippo.ehviewer.coil.MergeInterceptor
import com.hippo.ehviewer.coil.QrCodeInterceptor
import com.hippo.ehviewer.dailycheck.checkDawn
import com.hippo.ehviewer.dao.SearchDatabase
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.download.DownloadsFilterMode
import com.hippo.ehviewer.ktbuilder.diskCache
import com.hippo.ehviewer.ktbuilder.imageLoader
import com.hippo.ehviewer.ktor.CronetEngine
import com.hippo.ehviewer.legacy.cleanObsoleteCache
import com.hippo.ehviewer.ktor.Cronet
import com.hippo.ehviewer.ktor.configureClient
import com.hippo.ehviewer.ktor.configureCommon
import com.hippo.ehviewer.ktor.isCronetAvailable
import com.hippo.ehviewer.ui.keepNoMediaFileStatus
import com.hippo.ehviewer.ui.lockObserver
import com.hippo.ehviewer.ui.screen.detailCache
import com.hippo.ehviewer.ui.tools.dataStateFlow
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.Crash
import com.hippo.ehviewer.util.CrashHandler
import com.hippo.ehviewer.util.FavouriteStatusRouter
import com.hippo.ehviewer.util.FileUtils
import com.hippo.ehviewer.util.ReadableTime
import com.hippo.ehviewer.util.delegateLazy
import com.hippo.ehviewer.util.isAtLeastP
import com.hippo.ehviewer.util.isAtLeastQ
import com.hippo.ehviewer.util.isAtLeastS
import com.hippo.ehviewer.util.isCronetAvailable
import com.hippo.ehviewer.util.resettableLazy
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat
import com.hippo.ehviewer.util.OSUtils
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.cookies.HttpCookies
import java.io.File
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import moe.tarsin.coroutines.runSuspendCatching
import okhttp3.AsyncDns
import okhttp3.android.AndroidAsyncDns
import logcat.AndroidLogcatLogger
import logcat.LogPriority
import logcat.LogcatLogger
import logcat.asLog
import okio.Path.Companion.toOkioPath
import splitties.arch.room.roomDb
import splitties.init.appCtx
private val lifecycle = ProcessLifecycleOwner.get().lifecycle
private val lifecycleScope = lifecycle.coroutineScope
class EhApplication : Application(), SingletonImageLoader.Factory {
override fun onCreate() {
override fun onCreate() = with(lifecycleScope) {
initSETConnection()
// Initialize Settings on first access
lifecycleScope.launchIO {
val mode = Settings.theme
launchIO {
val mode = Settings.theme.value
if (!isAtLeastS) {
withUIContext {
AppCompatDelegate.setDefaultNightMode(mode)
}
}
LogcatLogger.loggers += AndroidLogcatLogger(LogPriority.VERBOSE)
Settings.saveCrashLog.valueFlow().collect {
if (it) {
LogcatLogger.install()
} else {
LogcatLogger.uninstall()
}
}
}
lifecycle.addObserver(lockObserver)
val handler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
try {
if (Settings.saveCrashLog) {
Crash.saveCrashLog(e)
}
} catch (ignored: Throwable) {
}
handler?.uncaughtException(t, e)
}
CrashHandler.install()
super.onCreate()
System.loadLibrary("ehviewer")
System.loadLibrary("ehviewer_rust")
ReadableTime.initialize(this)
lifecycleScope.launchIO {
launchIO {
EhTagDatabase
}
launchIO {
EhDB
}
launchIO {
DownloadManager.isIdle
}
launchIO {
runSuspendCatching {
val files = mutableListOf<File>()
AppConfig.externalCrashDir?.listFiles()?.let { files.addAll(it) }
AppConfig.externalParseErrorDir?.listFiles()?.let { files.addAll(it) }
files.sortByDescending { it.lastModified() }
files.forEachIndexed { index, file ->
ensureActive()
if (index > 9) {
file.delete()
}
}
}.onFailure {
it.printStackTrace()
}
}
launchIO {
cleanupDownload()
}
if (Settings.requestNews) {
launchIO {
checkDawn()
launch {
FavouriteStatusRouter.collect { info ->
detailCache[info.gid]?.apply {
favoriteSlot = info.favoriteSlot
favoriteName = info.favoriteName
favoriteNote = info.favoriteNote
}
}
}
cleanObsoleteCache(this)
launchIO {
EhTagDatabase.launchUpdate()
launch { EhDB }
launch { dataStateFlow.value }
launch { OSUtils.totalMemory }
launch {
if (DownloadManager.labelList.isNotEmpty() && Settings.downloadFilterMode !in Settings.snapshot()) {
Settings.downloadFilterMode.value = DownloadsFilterMode.CUSTOM.flag
}
initialized = true
DownloadManager.readMetadataFromLocal()
}
launch {
FileUtils.cleanupDirectory(AppConfig.externalCrashDir)
FileUtils.cleanupDirectory(AppConfig.externalParseErrorDir)
}
launch { cleanupDownload() }
if (Settings.requestNews.value) {
launch { checkDawn() }
}
}
if (BuildConfig.DEBUG) {
StrictMode.enableDefaults()
Snapshot.registerApplyObserver { anies, _ ->
logcat(Log.VERBOSE) { anies.toString() }
}
Composer.setDiagnosticStackTraceMode(ComposeStackTraceMode.SourceInformation)
} else {
Composer.setDiagnosticStackTraceMode(ComposeStackTraceMode.Auto)
}
}
@@ -153,97 +155,94 @@ class EhApplication : Application(), SingletonImageLoader.Factory {
runCatching {
keepNoMediaFileStatus()
}.onFailure {
it.printStackTrace()
logcat(it)
}
runCatching {
clearTempDir()
}.onFailure {
it.printStackTrace()
logcat(it)
}
}
private fun clearTempDir() {
var dir = AppConfig.tempDir
if (null != dir) {
FileUtils.deleteContent(dir)
}
dir = AppConfig.externalTempDir
if (null != dir) {
FileUtils.deleteContent(dir)
}
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
// Keep one entry for GalleryCommentsScreen
galleryDetailCache.trimToSize(1)
}
AppConfig.tempDir.deleteContent()
AppConfig.externalTempDir?.deleteContent()
}
override fun newImageLoader(context: Context) = context.imageLoader {
interceptorCoroutineContext(Dispatchers.Default)
components {
serviceLoaderEnabled(false)
add(NetworkFetcher.Factory(delegateLazy { ktorClient }))
add(
KtorNetworkFetcherFactory(
httpClient = { ktorClient },
connectivityChecker = { ConnectivityChecker.ONLINE },
),
)
add(MergeInterceptor)
add(DownloadThumbInterceptor)
if (isAtLeastO) {
add(HardwareBitmapInterceptor)
} else {
allowRgb565(true)
}
add(CropBorderInterceptor)
add(DetectBorderInterceptor)
add(QrCodeInterceptor)
add(MapExtraInfoInterceptor)
add(AnimatedWebPDecoder.Factory)
if (isAtLeastP) {
add(AnimatedImageDecoder.Factory(false))
} else {
add(GifDecoder.Factory())
}
}
diskCache(imageCache)
diskCache { imageCache }
crossfade(300)
val drawable = AppCompatResources.getDrawable(appCtx, R.drawable.image_failed)
if (drawable != null) error(drawable.asCoilImage(true))
if (BuildConfig.DEBUG) logger(DebugLogger())
if (drawable != null) error(drawable.asImage(true))
if (BuildConfig.DEBUG) {
logger(DebugLogger())
} else {
eventListener(object : EventListener() {
override fun onError(request: ImageRequest, result: ErrorResult) {
logcat("ImageLoader", LogPriority.ERROR) {
"🚨 Failed - ${request.data}\n${result.throwable.asLog()}"
}
}
})
}
}
companion object {
var ktorClient by resettableLazy {
if (Settings.enableCronet && isCronetAvailable) {
HttpClient(CronetEngine) {
install(HttpCookies) {
storage = EhCookieStore
}
@Volatile
var initialized = false
private set
val ktorClient by lazy {
if (isCronetAvailable && Settings.enableCronet.value) {
HttpClient(Cronet) {
engine { configureClient(Settings.enableQuic.value) }
configureCommon()
}
} else {
HttpClient(OkHttp) {
engine {
config {
if (isAtLeastQ) {
dns(AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6))
}
addInterceptor(UncaughtExceptionInterceptor())
}
}
install(HttpCookies) {
storage = EhCookieStore
}
engine { configureClient() }
configureCommon()
}
}
}
var noRedirectKtorClient by resettableLazy {
val noRedirectKtorClient by lazy {
HttpClient(ktorClient.engine) {
followRedirects = false
install(HttpCookies) {
storage = EhCookieStore
}
}
}
val galleryDetailCache by lazy {
LruCache<Long, GalleryDetail>(25).also {
lifecycleScope.launch {
FavouriteStatusRouter.globalFlow.collect { (gid, slot) -> it[gid]?.favoriteSlot = slot }
}
configureCommon(redirect = false)
}
}
val imageCache by lazy {
diskCache {
directory(appCtx.cacheDir.toOkioPath() / "image_cache")
maxSizeBytes(Settings.readCacheSize.coerceIn(320, 5120).toLong() * 1024 * 1024)
maxSizeBytes(Settings.readCacheSize.value.coerceIn(320, 5120).toLong() * 1024 * 1024)
}
}
+119 -100
View File
@@ -15,43 +15,62 @@
*/
package com.hippo.ehviewer
import android.content.Context
import android.net.Uri
import arrow.fx.coroutines.release
import androidx.room.execSQL
import androidx.room.useWriterConnection
import arrow.fx.coroutines.resource
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.dao.DownloadDirname
import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.dao.DownloadLabel
import com.hippo.ehviewer.dao.EhDatabase
import com.hippo.ehviewer.dao.Filter
import com.hippo.ehviewer.dao.HistoryInfo
import com.hippo.ehviewer.dao.LocalFavoriteInfo
import com.hippo.ehviewer.dao.QuickSearch
import arrow.fx.coroutines.resourceScope
import com.ehviewer.core.data.model.asEntity
import com.ehviewer.core.database.EhDatabase
import com.ehviewer.core.database.Schema17to18
import com.ehviewer.core.database.getDatabasePath
import com.ehviewer.core.database.model.DownloadArtist
import com.ehviewer.core.database.model.DownloadDirname
import com.ehviewer.core.database.model.DownloadInfo
import com.ehviewer.core.database.model.DownloadLabel
import com.ehviewer.core.database.model.Filter
import com.ehviewer.core.database.model.GalleryEntity
import com.ehviewer.core.database.model.HistoryInfo
import com.ehviewer.core.database.model.LocalFavoriteInfo
import com.ehviewer.core.database.model.ProgressInfo
import com.ehviewer.core.database.model.QuickSearch
import com.ehviewer.core.database.roomDb
import com.ehviewer.core.files.delete
import com.ehviewer.core.files.sendTo
import com.ehviewer.core.model.GalleryInfo
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.util.sendTo
import com.hippo.unifile.asUniFile
import kotlinx.coroutines.flow.Flow
import splitties.arch.room.roomDb
import okio.Path
object EhDB {
private val db = roomDb<EhDatabase>("eh.db")
private const val DB_NAME = "eh.db"
private val db = roomDb<EhDatabase>(DB_NAME) {
addMigrations(Schema17to18())
}
private suspend fun putGalleryInfo(galleryInfo: BaseGalleryInfo) {
suspend fun putGalleryInfo(galleryInfo: GalleryEntity) {
db.galleryDao().upsert(galleryInfo)
}
private suspend fun deleteGalleryInfo(galleryInfo: BaseGalleryInfo) {
private suspend fun deleteGalleryInfo(galleryInfo: GalleryEntity) {
runCatching { db.galleryDao().delete(galleryInfo) }
}
suspend fun updateGalleryInfo(galleryInfoList: List<GalleryEntity>) {
db.galleryDao().update(galleryInfoList)
}
fun getReadProgressFlow(gid: Long) = db.progressDao().getPageFlow(gid)
suspend fun getReadProgress(gid: Long) = db.progressDao().getPage(gid)
suspend fun putReadProgress(gid: Long, page: Int) = db.progressDao().upsert(ProgressInfo(gid, page))
suspend fun clearProgressInfo() = db.progressDao().deleteAll()
suspend fun getAllDownloadInfo() = db.downloadsDao().joinList().onEach {
if (it.state == DownloadInfo.STATE_WAIT || it.state == DownloadInfo.STATE_DOWNLOAD) {
it.state = DownloadInfo.STATE_NONE
}
}
suspend fun updateDownloadInfo(downloadInfo: List<DownloadInfo>) {
suspend fun updateDownloadInfo(downloadInfo: Collection<DownloadInfo>) {
val dao = db.downloadsDao()
dao.update(downloadInfo.map(DownloadInfo::downloadInfo))
}
@@ -64,17 +83,17 @@ object EhDB {
suspend fun removeDownloadInfo(downloadInfo: DownloadInfo) {
val dao = db.downloadsDao()
dao.delete(downloadInfo.downloadInfo)
dao.fill(downloadInfo.position)
deleteGalleryInfo(downloadInfo.galleryInfo)
}
suspend fun randomLocalFav() = db.localFavoritesDao().random()
suspend fun removeDownloadInfo(downloadInfo: List<DownloadInfo>) {
val dao = db.downloadsDao()
downloadInfo.forEach {
dao.delete(it.downloadInfo)
deleteGalleryInfo(it.galleryInfo)
}
dao.fill(downloadInfo.first().position, downloadInfo.size)
}
suspend fun getDownloadDirname(gid: Long): String? {
@@ -98,6 +117,12 @@ object EhDB {
dao.insertOrIgnore(downloadDirnameList)
}
val downloadsCountByLabel
get() = db.downloadsDao().countByLabel()
val downloadsCountByArtist
get() = db.downloadsDao().countByArtist()
suspend fun getAllDownloadLabelList() = db.downloadLabelDao().list()
suspend fun addDownloadLabel(raw: DownloadLabel): DownloadLabel {
@@ -124,12 +149,22 @@ object EhDB {
dao.fill(raw.position)
}
suspend fun removeLocalFavorites(galleryInfo: BaseGalleryInfo) {
db.localFavoritesDao().deleteByKey(galleryInfo.gid)
deleteGalleryInfo(galleryInfo)
suspend fun searchDownloadLabel(keyword: String, limit: Int) = db.downloadLabelDao().search("%$keyword%", limit)
suspend fun putDownloadArtist(gid: Long, artists: List<DownloadArtist>) {
if (artists.isNotEmpty()) {
val dao = db.downloadArtistDao()
dao.deleteByGid(gid)
dao.insertOrIgnore(artists)
}
}
suspend fun removeLocalFavorites(galleryInfoList: Collection<BaseGalleryInfo>) {
suspend fun removeLocalFavorites(galleryInfo: GalleryInfo) {
db.localFavoritesDao().deleteByKey(galleryInfo.gid)
deleteGalleryInfo(galleryInfo.asEntity())
}
suspend fun removeLocalFavorites(galleryInfoList: Collection<GalleryInfo>) {
galleryInfoList.forEach {
removeLocalFavorites(it)
}
@@ -140,12 +175,12 @@ object EhDB {
return dao.contains(gid)
}
suspend fun putLocalFavorites(galleryInfo: BaseGalleryInfo) {
putGalleryInfo(galleryInfo)
suspend fun putLocalFavorites(galleryInfo: GalleryInfo) {
putGalleryInfo(galleryInfo.asEntity())
db.localFavoritesDao().upsert(LocalFavoriteInfo(galleryInfo.gid))
}
suspend fun putLocalFavorites(galleryInfoList: Collection<BaseGalleryInfo>) {
suspend fun putLocalFavorites(galleryInfoList: Collection<GalleryInfo>) {
galleryInfoList.forEach {
putLocalFavorites(it)
}
@@ -181,16 +216,18 @@ object EhDB {
val historyLazyList
get() = db.historyDao().joinListLazy()
fun searchHistory(keyword: String) = db.historyDao().joinListLazy("*$keyword*")
val localFavLazyList
get() = db.localFavoritesDao().joinListLazy()
val localFavCount: Flow<Int>
get() = db.localFavoritesDao().count()
fun searchLocalFav(keyword: String) = db.localFavoritesDao().joinListLazy("%$keyword%")
fun searchLocalFav(keyword: String) = db.localFavoritesDao().joinListLazy("*$keyword*")
suspend fun putHistoryInfo(galleryInfo: BaseGalleryInfo) {
putGalleryInfo(galleryInfo)
suspend fun putHistoryInfo(galleryInfo: GalleryInfo) {
putGalleryInfo(galleryInfo.asEntity())
db.historyDao().upsert(HistoryInfo(galleryInfo.gid))
}
@@ -207,7 +244,7 @@ object EhDB {
dao.insertOrIgnore(historyInfoList)
}
suspend fun deleteHistoryInfo(galleryInfo: BaseGalleryInfo) {
suspend fun deleteHistoryInfo(galleryInfo: GalleryEntity) {
val dao = db.historyDao()
dao.deleteByKey(galleryInfo.gid)
deleteGalleryInfo(galleryInfo)
@@ -241,79 +278,61 @@ object EhDB {
db.filterDao().update(filter)
}
suspend fun exportDB(context: Context, uri: Uri) {
val ehExportName = "eh.export.db"
resource {
context.deleteDatabase(ehExportName)
roomDb<EhDatabase>(ehExportName)
} release {
it.close()
context.deleteDatabase(ehExportName)
} use { newDb ->
db.galleryDao().list().let { newDb.galleryDao().insertOrIgnore(it) }
db.downloadLabelDao().list().let { newDb.downloadLabelDao().insert(it) }
db.downloadDirnameDao().list().let { newDb.downloadDirnameDao().insertOrIgnore(it) }
db.downloadsDao().list().let { newDb.downloadsDao().insert(it) }
db.historyDao().list().let { newDb.historyDao().insertOrIgnore(it) }
db.quickSearchDao().list().let { newDb.quickSearchDao().insert(it) }
db.localFavoritesDao().list().let { newDb.localFavoritesDao().insertOrIgnore(it) }
db.filterDao().list().let { newDb.filterDao().insert(it) }
newDb.close()
val dbFile = context.getDatabasePath(ehExportName)
dbFile.asUniFile() sendTo uri.asUniFile()
suspend fun exportDB(file: Path) {
db.useWriterConnection { conn ->
conn.execSQL("PRAGMA wal_checkpoint(FULL)")
conn.execSQL("VACUUM")
}
val dbFile = getDatabasePath(DB_NAME)
dbFile sendTo file
}
suspend fun importDB(context: Context, uri: Uri) {
suspend fun importDB(file: Path) = resourceScope {
val tempDBName = "tmp.db"
resource {
context.deleteDatabase(tempDBName)
roomDb<EhDatabase>(tempDBName) { createFromInputStream { context.contentResolver.openInputStream(uri) } }
} release {
it.close()
context.deleteDatabase(tempDBName)
} use { oldDB ->
runCatching {
db.galleryDao().insertOrIgnore(oldDB.galleryDao().list())
}
runCatching {
val downloadLabelList = oldDB.downloadLabelDao().list()
DownloadManager.addDownloadLabel(downloadLabelList)
}
runCatching {
oldDB.downloadDirnameDao().list().let {
importDownloadDirname(it)
}
}
runCatching {
val downloadInfoList = oldDB.downloadsDao().joinList().asReversed()
DownloadManager.addDownload(downloadInfoList)
}
runCatching {
val historyInfoList = oldDB.historyDao().list()
importHistoryInfo(historyInfoList)
}
runCatching {
val quickSearchList = oldDB.quickSearchDao().list()
val currentQuickSearchList = db.quickSearchDao().list()
val offset = currentQuickSearchList.size
val importList = quickSearchList.filter { newQS ->
currentQuickSearchList.none { it.name == newQS.name }
}.onEachIndexed { index, q -> q.position = index + offset }
importQuickSearch(importList)
}
runCatching {
oldDB.localFavoritesDao().list().let {
importLocalFavorites(it)
}
}
runCatching {
val filterList = oldDB.filterDao().list()
val currentFilterList = db.filterDao().list()
filterList.forEach {
if (it !in currentFilterList) addFilter(it)
}
val dbFile = getDatabasePath(tempDBName)
file sendTo dbFile
val oldDB = resource {
roomDb<EhDatabase>(tempDBName) {
addMigrations(Schema17to18())
}
} release { db ->
db.close()
dbFile.delete()
}
db.galleryDao().insertOrIgnore(oldDB.galleryDao().list())
db.progressDao().insertOrIgnore(oldDB.progressDao().list())
val downloadLabelList = oldDB.downloadLabelDao().list()
DownloadManager.addDownloadLabel(downloadLabelList)
oldDB.downloadDirnameDao().list().let {
importDownloadDirname(it)
}
val downloadInfoList = oldDB.downloadsDao().joinList().asReversed()
DownloadManager.addDownload(downloadInfoList)
val historyInfoList = oldDB.historyDao().list()
importHistoryInfo(historyInfoList)
val quickSearchList = oldDB.quickSearchDao().list()
val currentQuickSearchList = db.quickSearchDao().list()
val offset = currentQuickSearchList.size
val importList = quickSearchList.filter { newQS ->
currentQuickSearchList.none { it.name == newQS.name }
}.onEachIndexed { index, q ->
q.id = null
q.position = index + offset
}
importQuickSearch(importList)
oldDB.localFavoritesDao().list().let {
importLocalFavorites(it)
}
oldDB.filterDao().list().forEach {
addFilter(it)
}
}
}
+128 -120
View File
@@ -9,9 +9,14 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import com.hippo.ehviewer.client.CHROME_USER_AGENT
import com.hippo.ehviewer.client.EhUtils
import com.ehviewer.core.network.EhCookieStore
import com.ehviewer.core.preferences.DataStorePreferences
import com.ehviewer.core.preferences.PrefDelegate
import com.ehviewer.core.preferences.edit
import com.hippo.ehviewer.client.data.FavListUrlBuilder
import com.hippo.ehviewer.download.DownloadsFilterMode
import com.hippo.ehviewer.download.SortMode
import com.hippo.ehviewer.util.AppConfig
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import java.util.Locale
@@ -23,47 +28,12 @@ import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.map
import splitties.preferences.BoolPref
import splitties.preferences.DataStorePreferences
import splitties.preferences.FloatPref
import splitties.preferences.IntPref
import splitties.preferences.LongPref
import splitties.preferences.PrefDelegate
import splitties.preferences.StringOrNullPref
import splitties.preferences.StringPref
import splitties.preferences.StringSetOrNullPref
import splitties.preferences.StringSetPref
import splitties.preferences.edit
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
fun <T> PrefDelegate<T>.getValue(): T = when (this) {
is BoolPref -> value
is IntPref -> value
is FloatPref -> value
is LongPref -> value
is StringPref -> value
is StringOrNullPref -> value
is StringSetPref -> value
is StringSetOrNullPref -> value
} as T
@Suppress("UNCHECKED_CAST")
fun <T> PrefDelegate<T>.setValue(newValue: T) = when (this) {
is BoolPref -> value = newValue as Boolean
is IntPref -> value = newValue as Int
is FloatPref -> value = newValue as Float
is LongPref -> value = newValue as Long
is StringPref -> value = newValue as String
is StringOrNullPref -> value = newValue as? String
is StringSetPref -> value = newValue as Set<String?>
is StringSetOrNullPref -> value = newValue as? Set<String?>
}
@Stable
@Composable
inline fun <R, T> PrefDelegate<T>.collectAsState(crossinline transform: @DisallowComposableCalls (T) -> R): State<R> {
val flow = remember { valueFlow().map { transform(it) } }
val init = getValue()
val init = value
return flow.collectAsState(transform(init))
}
@@ -71,152 +41,190 @@ inline fun <R, T> PrefDelegate<T>.collectAsState(crossinline transform: @Disallo
@Composable
fun <T> PrefDelegate<T>.collectAsState(): State<T> {
val flow = remember { valueFlow() }
val init = getValue()
val init = value
return flow.collectAsState(init)
}
@Stable
@Composable
fun <T> PrefDelegate<T>.asMutableState(): MutableState<T> {
val flow = remember { valueFlow() }
val init = getValue()
val readOnly = flow.collectAsState(init)
val readOnly = collectAsState()
return remember {
object : MutableState<T> {
override var value: T
get() = readOnly.value
set(value) {
setValue(value)
this@asMutableState.value = value
}
override fun component1() = TODO()
override fun component2() = TODO()
override fun component1() = readOnly.value
override fun component2() = this@asMutableState::value::set
}
}
}
object Settings : DataStorePreferences(null) {
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
@Suppress("ktlint:standard:backing-property-naming")
private val _favFlow = MutableSharedFlow<Unit>()
val favChangesFlow = _favFlow.debounce(1000)
var favCat by stringArrayPref("fav_cat", 10, "Favorites").emitTo(_favFlow)
var favCount by intArrayPref("fav_count", 10).emitTo(_favFlow)
var favCloudCount by intPref("fav_cloud", 0).emitTo(_favFlow)
// Eh
val gallerySite = intPref("gallery_site_2", 0).observed(::updateWhenGallerySiteChanges)
val defaultFavSlot = intPref("default_favorite_slot", -2)
val theme = intPref("theme_2", -1).observed(::updateWhenThemeChanges)
val blackDarkTheme = boolPref("black_dark_theme", false)
val harmonizeCategoryColor = boolPref("harmonize_category_color", true)
val launchPage = intPref("launch_page_2", 0)
val listMode = intPref("list_mode_2", 0)
val listThumbSize = intPref("list_tile_size", 40)
val detailSize = intPref("detail_size_2", 0)
val thumbColumns = intPref("thumb_columns", 3)
val listThumbSize = intPref("list_tile_size", 40)
val languageFilter = intPref("language_filter", -1)
val showGalleryPages = boolPref("show_gallery_pages", true)
val showReadingProgress = boolPref("show_reading_progress", false)
val showVoteStatus = boolPref("show_vote_status", false)
val showComments = boolPref("show_gallery_comments", true)
val commentThreshold = intPref("comment_threshold", -100)
val showTagTranslations = boolPref("show_tag_translations", false).observed(::updateWhenTagTranslationChanges)
val meteredNetworkWarning = boolPref("cellular_network_warning", false)
val predictiveNavAnim = boolPref("predictive_nav_anim_2", false)
val blackDarkTheme = boolPref("black_dark_theme", false)
val showJpnTitle = boolPref("show_jpn_title", false)
val requestNews = boolPref("request_news", false).observed { updateWhenRequestNewsChanges() }
val hideHvEvents = boolPref("hide_hv_events", false)
var downloadScheme by stringOrNullPref("image_scheme", null)
var downloadAuthority by stringOrNullPref("image_authority", null)
var downloadPath by stringOrNullPref("image_path", null)
var downloadQuery by stringOrNullPref("image_query", null)
var downloadFragment by stringOrNullPref("image_fragment", null)
var archivePasswds by stringSetOrNullPref("archive_passwds")
var downloadDelay by intPref("download_delay_3", 1000)
var gallerySite by intPref("gallery_site_2", 0).observed { updateWhenGallerySiteChanges() }
var multiThreadDownload by intPref("download_thread_2", 3)
var preloadImage by intPref("preload_image_2", 5)
var downloadTimeout by intPref("download_timeout", 60)
var theme by intPref("theme_2", -1).observed { updateWhenThemeChanges() }
var thumbResolution by intPref("thumb_resolution_2", 0)
var readCacheSize by intPref("read_cache_size_2", 640)
var launchPage by intPref("launch_page_2", 0)
var commentThreshold by intPref("comment_threshold", -101)
var forceEhThumb by boolPref("force_eh_thumb", false)
var showComments by boolPref("show_gallery_comments", true)
var requestNews by boolPref("request_news", false).observed { updateWhenRequestNewsChanges() }
var hideHvEvents by boolPref("hide_hv_events", false)
var showJpnTitle by boolPref("show_jpn_title", false)
var showGalleryPages by boolPref("show_gallery_pages", true)
var showTagTranslations by boolPref(KEY_SHOW_TAG_TRANSLATIONS, false).observed { updateWhenTagTranslationChanges() }
// Download
val mediaScan = boolPref("media_scan", false).observed(::updateWhenKeepMediaStatusChanges)
val multiThreadDownload = intPref("download_thread_2", 3)
val downloadDelay = intPref("download_delay_3", 1000)
val timeoutSpeed = intPref("timeout_speed_level", 6)
val preloadImage = intPref("preload_image_2", 5)
val downloadOriginImage = boolPref("download_origin_image", false)
val saveAsCbz = boolPref("save_as_cbz", false)
val archiveMetadata = boolPref("archive_metadata", true)
// Privacy
val security = boolPref("require_unlock", false)
val securityDelay = intPref("require_unlock_delay", 0)
val enabledSecurity = boolPref("enable_secure", false)
// Advanced
val saveParseErrorBody = boolPref("save_parse_error_body", true)
val saveCrashLog = boolPref("save_crash_log", true)
val readCacheSize = intPref("read_cache_size_2", 640)
val enableCronet = boolPref("enable_cronet", true)
val enableQuic = boolPref("enable_quic", true)
val hardwareBitmapThreshold = intPref("hardware_bitmap_threshold", 16384)
val preloadThumbAggressively = boolPref("preload_thumb_aggressively", false)
val animateItems = boolPref("animate_items", true)
val desktopSite = boolPref("desktop_site", true)
// About
val backupBeforeUpdate = boolPref("backup_before_update", false)
val useCIUpdateChannel = boolPref("ci_update_channel", AppConfig.isSnapshot)
val updateIntervalDays = intPref("update_interval_days", 7)
// Misc
val languageFilter = intPref("language_filter", -1)
val downloadSortMode = intPref("download_sort_mode", SortMode.Default.flag)
val downloadFilterMode = intPref("download_filter_mode", DownloadsFilterMode.Default.flag)
val hasSignedIn = boolPref("has_signed_in", EhCookieStore.hasSignedIn())
val needSignIn = boolPref("need_sign_in", true)
val gridView = boolPref("grid_view", false)
val qSSaveProgress = boolPref("qs_save_progress", true)
val displayName = stringOrNullPref("display_name")
val avatar = stringOrNullPref("avatar")
val recentDownloadLabel = stringOrNullPref("recent_download_label")
var downloadScheme by stringOrNullPref("image_scheme")
var downloadAuthority by stringOrNullPref("image_authority")
var downloadPath by stringOrNullPref("image_path")
var downloadQuery by stringOrNullPref("image_query")
var downloadFragment by stringOrNullPref("image_fragment")
var archivePasswds by stringSetPref("archive_passwds")
var appLinkVerifyTip by boolPref("app_link_verify_tip", false)
var enabledSecurity by boolPref("enable_secure", false)
var useCIUpdateChannel by boolPref("ci_update_channel", false)
var mediaScan by boolPref("media_scan", false).observed { updateWhenKeepMediaStatusChanges() }
var hasDefaultDownloadLabel by boolPref("has_default_download_label", false)
var qSSaveProgress by boolPref("qs_save_progress", true)
var saveParseErrorBody by boolPref("save_parse_error_body", true)
var saveCrashLog by boolPref("save_crash_log", true)
var security by boolPref("require_unlock", false)
var removeImageFiles by boolPref("include_pic", true)
var needSignIn by boolPref("need_sign_in", true)
var harmonizeCategoryColor by boolPref("harmonize_category_color", true)
var preloadThumbAggressively by boolPref("preload_thumb_aggressively", false)
var downloadOriginImage by boolPref("download_origin_image", false)
var enableCronet by boolPref("enable_cronet", true).observed { updateWhenEnableCronetChanges() }
var recentFavCat by intPref("recent_fav_cat", FavListUrlBuilder.FAV_CAT_LOCAL)
var defaultFavSlot by intPref("default_favorite_slot", -2)
var securityDelay by intPref("require_unlock_delay", 0)
var clipboardTextHashCode by intPref("clipboard_text_hash_code", 0)
var searchCategory by intPref("search_pref", EhUtils.ALL_CATEGORY)
var requestNewsTime by intPref("request_news_time", 0).observed { updateWhenRequestNewsChanges() }
var updateIntervalDays by intPref("update_interval_days", 7)
var lastDawnDays by intPref("last_dawn_days", 0)
var recentToplist by stringPref("recent_toplist", "11")
var userAgent by stringPref("user_agent", CHROME_USER_AGENT)
var recentDownloadLabel by stringOrNullPref("recent_download_label", null)
var defaultDownloadLabel by stringOrNullPref("default_download_label", null)
var displayName by stringOrNullPref("display_name", null)
var avatar by stringOrNullPref("avatar", null)
var language by stringPref("app_language", "system").observed { updateWhenLocaleChanges() }
var lastUpdateTime by longPref("last_update_time", 0)
var favDialogTheta by floatPref("fav_select_dialog_delta", 0F)
var defaultDownloadLabel by stringOrNullPref("default_download_label")
var lastUpdateTime by longPref("last_update_time", BuildConfig.COMMIT_TIME)
// TODO: Remove this after swipe gestures are correctly handled in compose
var touchSlopFactor by intPref("touch_slop", 3)
// Tachiyomi Reader
// Reader
val cropBorder = boolPref("crop_borders", false)
val colorFilter = boolPref("pref_color_filter_key", false)
val colorFilterValue = intPref("color_filter_value", 0)
val colorFilterMode = intPref("color_filter_mode", 0)
val customBrightness = boolPref("pref_custom_brightness_key", false)
val customBrightnessValue = intPref("custom_brightness_value", 0)
val readingMode = intPref("pref_default_reading_mode_key", ReadingModeType.LEFT_TO_RIGHT.flagValue)
val orientationMode = intPref("pref_default_orientation_type_key", OrientationType.FREE.flagValue)
val readingMode = intPref("pref_default_reading_mode_key", ReadingModeType.DEFAULT.prefValue)
val orientationMode = intPref("pref_default_orientation_type_key", OrientationType.DEFAULT.prefValue)
val showReaderSeekbar = boolPref("pref_show_reader_seekbar", true)
val showPageNumber = boolPref("pref_show_page_number_key", true)
val readerTheme = intPref("pref_reader_theme_key", 1)
val doubleTapToZoom = boolPref("pref_double_tap_to_zoom", true)
val fullscreen = boolPref("fullscreen", true)
val cutoutShort = boolPref("cutout_short", true)
val keepScreenOn = boolPref("pref_keep_screen_on_key", true)
val readerLongTapAction = boolPref("reader_long_tap", true)
val pageTransitions = boolPref("pref_enable_transitions_key", true)
val readWithVolumeKeys = boolPref("reader_volume_keys", false)
val readWithVolumeKeysInverted = boolPref("reader_volume_keys_inverted", false)
val grayScale = boolPref("pref_grayscale", false)
val invertedColors = boolPref("pref_inverted_colors", false)
val readerWebtoonNav = intPref("reader_navigation_mode_webtoon", 0)
val readerPagerNav = intPref("reader_navigation_mode_pager", 0)
val readerPagerNavInverted = intPref("reader_tapping_inverted_2", 0)
val readerWebtoonNavInverted = intPref("reader_tapping_inverted_webtoon_2", 0)
val webtoonSidePadding = intPref("webtoon_side_padding", 0)
val navigateToPan = boolPref("navigate_pan", true)
val imageScaleType = intPref("pref_image_scale_type_key", 1)
val landscapeZoom = boolPref("landscape_zoom", false)
val zoomStart = intPref("pref_zoom_start_key", 1)
val showNavigationOverlayNewUser = boolPref("reader_navigation_overlay_new_user", true)
val showNavigationOverlayOnStart = boolPref("reader_navigation_overlay_on_start", false)
val stripExtraneousAds = boolPref("strip_extraneous_ads", false)
init {
if ("CN" == Locale.getDefault().country) {
edit {
if (KEY_SHOW_TAG_TRANSLATIONS !in prefs) showTagTranslations = true
edit { pref ->
if ("CN" == Locale.getDefault().country) {
if (showTagTranslations !in pref) pref[showTagTranslations] = true
}
if (pref[downloadFilterMode] == DownloadsFilterMode.ARTIST.flag && pref[recentDownloadLabel] == null) {
pref[recentDownloadLabel] = ""
}
val orientation = pref[orientationMode]
if (OrientationType.entries.none { it.prefValue == orientation }) {
pref.remove(orientationMode)
}
}
}
interface Delegate<R> {
val flowGetter: () -> Flow<Unit>
fun changesFlow(): Flow<Unit>
operator fun getValue(thisRef: Any?, prop: KProperty<*>?): R
operator fun setValue(thisRef: Any?, prop: KProperty<*>?, value: R)
}
private fun intArrayPref(key: String, count: Int) = object : Delegate<IntArray> {
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
@Suppress("ktlint:standard:property-naming")
private var _value = (0 until count).map { intPref("${key}_$it", 0) }.toTypedArray()
override fun getValue(thisRef: Any?, prop: KProperty<*>?): IntArray = _value.map { it.value }.toIntArray()
private val delegates = Array(count) { intPref("${key}_$it", 0) }
override fun changesFlow(): Flow<Unit> = delegates.asFlow().flatMapMerge { it.changesFlow() }.conflate()
override fun getValue(thisRef: Any?, prop: KProperty<*>?) = IntArray(delegates.size) { delegates[it].value }
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: IntArray) {
check(value.size == count)
edit { value.zip(_value) { v, d -> d.value = v } }
edit { pref -> value.zip(delegates) { v, d -> pref[d] = v } }
}
}
private fun stringArrayPref(key: String, count: Int, defMetaValue: String) = object : Delegate<Array<String>> {
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
@Suppress("ktlint:standard:property-naming")
private var _value = (0 until count).map { stringPref("${key}_$it", "$defMetaValue $it") }.toTypedArray()
override fun getValue(thisRef: Any?, prop: KProperty<*>?): Array<String> = _value.map { it.value }.toTypedArray()
private val delegates = Array(count) { stringPref("${key}_$it", "$defMetaValue $it") }
override fun changesFlow(): Flow<Unit> = delegates.asFlow().flatMapMerge { it.changesFlow() }.conflate()
override fun getValue(thisRef: Any?, prop: KProperty<*>?) = Array(delegates.size) { delegates[it].value }
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Array<String>) {
check(value.size == count)
edit { value.zip(_value) { v, d -> d.value = v } }
edit { pref -> value.zip(delegates) { v, d -> pref[d] = v } }
}
}
}
@@ -2,93 +2,73 @@ package com.hippo.ehviewer
import android.app.UiModeManager
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import com.hippo.ehviewer.EhApplication.Companion.ktorClient
import com.hippo.ehviewer.EhApplication.Companion.noRedirectKtorClient
import com.hippo.ehviewer.client.EhCookieStore
import arrow.core.Either.Companion.catch
import com.ehviewer.core.preferences.PrefDelegate
import com.ehviewer.core.util.isAtLeastS
import com.ehviewer.core.util.logcat
import com.ehviewer.core.util.withIOContext
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.dailycheck.updateDailyCheckWork
import com.hippo.ehviewer.ui.keepNoMediaFileStatus
import com.hippo.ehviewer.util.isAtLeastS
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.launch
import splitties.init.appCtx
import splitties.preferences.PrefDelegate
import splitties.systemservices.uiModeManager
private const val TAG = "SettingsCollector"
private val collectScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun <T, R : PrefDelegate<T>> R.observed(func: (Unit) -> Unit) = apply { collectScope.launch { changesFlow().collect(func) } }
fun <T, R : Settings.Delegate<T>> R.observed(func: (Unit) -> Unit) = apply { collectScope.launch { flowGetter().collect(func) } }
fun <T, R : Settings.Delegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flowGetter().collect { flow.emit(Unit) } } }
fun <T, R : PrefDelegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { changesFlow().collect { flow.emit(Unit) } } }
fun <T, R : PrefDelegate<T>> R.observed(func: suspend (T) -> Unit) = apply { collectScope.launch { valueFlow().drop(1).collectLatest(func) } }
fun <T, R : Settings.Delegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flow.emitAll(changesFlow()) } }
fun <T, R : PrefDelegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flow.emitAll(changesFlow()) } }
fun updateWhenLocaleChanges() {
val newValue = Settings.language
if ("system" == newValue) {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
} else {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue))
}
}
fun updateWhenKeepMediaStatusChanges() {
collectScope.launchIO {
runCatching {
keepNoMediaFileStatus()
suspend fun updateWhenKeepMediaStatusChanges(mediaScan: Boolean) {
withIOContext {
catch {
keepNoMediaFileStatus(mediaScan = mediaScan)
}.onLeft {
logcat(TAG, it)
}
}
}
fun updateWhenEnableCronetChanges() {
collectScope.launchIO {
ktorClient = ktorClient.apply { close() }
noRedirectKtorClient = noRedirectKtorClient.apply { close() }
}
}
fun updateWhenThemeChanges() {
collectScope.launch {
delay(100) // Avoid recompose being cancelled
if (isAtLeastS) {
val mode = when (Settings.theme) {
AppCompatDelegate.MODE_NIGHT_NO -> UiModeManager.MODE_NIGHT_NO
AppCompatDelegate.MODE_NIGHT_YES -> UiModeManager.MODE_NIGHT_YES
else -> UiModeManager.MODE_NIGHT_AUTO
}
uiModeManager.setApplicationNightMode(mode)
suspend fun updateWhenThemeChanges(theme: Int) {
delay(100) // Avoid recompose being cancelled
if (isAtLeastS) {
val mode = when (theme) {
AppCompatDelegate.MODE_NIGHT_NO -> UiModeManager.MODE_NIGHT_NO
AppCompatDelegate.MODE_NIGHT_YES -> UiModeManager.MODE_NIGHT_YES
else -> UiModeManager.MODE_NIGHT_AUTO
}
AppCompatDelegate.setDefaultNightMode(Settings.theme)
uiModeManager.setApplicationNightMode(mode)
}
AppCompatDelegate.setDefaultNightMode(theme)
}
fun updateWhenRequestNewsChanges() {
updateDailyCheckWork(appCtx)
}
fun updateWhenGallerySiteChanges() {
if (!Settings.needSignIn && EhCookieStore.hasSignedIn()) {
collectScope.launchIO {
runCatching {
EhEngine.getUConfig()
}.onFailure {
it.printStackTrace()
}
suspend fun updateWhenGallerySiteChanges(gallerySite: Int) {
if (Settings.hasSignedIn.value) {
catch {
EhEngine.getUConfig(gallerySite)
}.onLeft {
logcat(TAG, it)
}
}
}
fun updateWhenTagTranslationChanges() {
collectScope.launchIO {
runCatching {
EhTagDatabase.update()
}.onFailure {
it.printStackTrace()
}
fun updateWhenTagTranslationChanges(enabled: Boolean) {
if (enabled) {
EhTagDatabase.launchUpdate()
}
}
@@ -15,36 +15,38 @@
*/
package com.hippo.ehviewer.client
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.data.GalleryPreview
import com.hippo.ehviewer.client.data.NormalGalleryPreview
import com.ehviewer.core.model.GalleryInfo
// ExHentai Large Preview: https://s.exhentai.org/t/***
// ExHentai Normal Preview: https://s.exhentai.org/m/***
// EHentai Normal Preview: https://ehgt.org/m/***
// EHentai Large Preview: https://ehgt.org/***
// Normal Preview (v2): https://*.hath.network/c(m|1|2)/[timed token]/[gid]-[index].(jpg|webp)
// ExHentai Large Preview (v1 Cover): https://s.exhentai.org/t/***
// E-Hentai Large Preview (v1 Cover): https://ehgt.org/***
// ExHentai v2 Cover: https://s.exhentai.org/**.webp
// E-Hentai v2 Cover: https://ehgt.org/**.webp
private const val URL_PREFIX_THUMB_E = "https://ehgt.org/"
private const val URL_PREFIX_THUMB_EX = "https://s.exhentai.org/"
private const val URL_PREFIX_LARGE_THUMB_EX = URL_PREFIX_THUMB_EX + "t/"
const val URL_PREFIX_THUMB_E = "https://ehgt.org/"
const val URL_PREFIX_THUMB_EX = "https://s.exhentai.org/"
private const val URL_PREFIX_V1_THUMB_EX = URL_PREFIX_THUMB_EX + "t/"
private val V2PreviewKeyRegex = Regex("/c([m12]/)[^/]+/(\\d+-\\d+)")
fun getImageKey(gid: Long, index: Int): String {
return "image:$gid:$index"
}
fun getImageKey(gid: Long, index: Int): String = "image:$gid:$index"
fun getThumbKey(url: String): String {
return url.removePrefix(URL_PREFIX_THUMB_E).removePrefix(URL_PREFIX_LARGE_THUMB_EX).removePrefix(URL_PREFIX_THUMB_EX)
}
fun getThumbKey(url: String): String = url.removePrefix(URL_PREFIX_THUMB_E).removePrefix(URL_PREFIX_V1_THUMB_EX).removePrefix(URL_PREFIX_THUMB_EX)
val GalleryPreview.url
get() = if (exThumb) {
if (this is NormalGalleryPreview) URL_PREFIX_THUMB_EX else URL_PREFIX_LARGE_THUMB_EX
} else {
URL_PREFIX_THUMB_E
} + imageKey
fun getV2PreviewKey(url: String) = "$".plus(
V2PreviewKeyRegex.find(url)?.let {
it.groupValues[1] + it.groupValues[2]
} ?: url,
)
val GalleryInfo.thumbUrl
get() = (if (exThumb) URL_PREFIX_LARGE_THUMB_EX else URL_PREFIX_THUMB_E) + thumbKey!!
get() = keyToUrl(thumbKey!!)
val exThumb get() = EhUtils.isExHentai && !Settings.forceEhThumb
fun keyToUrl(key: String) = if (key.startsWith("https:")) {
key
} else {
if (key.endsWith("webp")) {
if (EhUtils.isExHentai) URL_PREFIX_THUMB_EX else URL_PREFIX_THUMB_E
} else {
if (EhUtils.isExHentai) URL_PREFIX_V1_THUMB_EX else URL_PREFIX_THUMB_E
} + key
}
@@ -1,83 +0,0 @@
package com.hippo.ehviewer.client
import android.webkit.CookieManager
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastJoinToString
import io.ktor.client.plugins.cookies.CookiesStorage
import io.ktor.http.Cookie
import io.ktor.http.Url
import io.ktor.http.parseClientCookiesHeader
import io.ktor.http.renderSetCookieHeader
object EhCookieStore : CookiesStorage {
private val manager = CookieManager.getInstance()
fun signOut() = manager.removeAllCookies(null)
fun contains(url: String, name: String) = load(Url(url)).fastAny { it.name == name }
fun hasSignedIn(): Boolean {
val url = EhUrl.HOST_E
return contains(url, KEY_IPB_MEMBER_ID) && contains(url, KEY_IPB_PASS_HASH)
}
const val KEY_IPB_MEMBER_ID = "ipb_member_id"
const val KEY_IPB_PASS_HASH = "ipb_pass_hash"
const val KEY_IGNEOUS = "igneous"
private const val KEY_STAR = "star"
private const val KEY_CONTENT_WARNING = "nw"
private const val CONTENT_WARNING_NOT_SHOW = "1"
private const val KEY_UTMP_NAME = "__utmp"
private val sTipsCookie = Cookie(
name = KEY_CONTENT_WARNING,
value = CONTENT_WARNING_NOT_SHOW,
)
fun copyNecessaryCookies() {
val cookies = load(Url(EhUrl.HOST_E))
cookies.fastForEach {
if (it.name == KEY_STAR || it.name == KEY_IPB_MEMBER_ID || it.name == KEY_IPB_PASS_HASH) {
manager.setCookie(EhUrl.HOST_EX, it.copy(maxAge = Int.MAX_VALUE).toString())
}
}
}
fun addCookie(k: String, v: String, domain: String) {
val cookie = Cookie(name = k, value = v, domain = domain, maxAge = Int.MAX_VALUE)
val url = if (EhUrl.DOMAIN_E == cookie.domain) EhUrl.HOST_E else EhUrl.HOST_EX
manager.setCookie(url, renderSetCookieHeader(cookie))
}
fun flush() = manager.flush()
fun getCookieHeader(url: String): String {
return load(Url(url)).fastJoinToString("; ") {
"${it.name}=${it.value}"
}
}
// See https://github.com/Ehviewer-Overhauled/Ehviewer/issues/873
override suspend fun addCookie(requestUrl: Url, cookie: Cookie) {
if (cookie.name != KEY_UTMP_NAME) {
manager.setCookie(requestUrl.toString(), renderSetCookieHeader(cookie))
}
}
override fun close() = Unit
fun load(url: Url): List<Cookie> {
val cookies = manager.getCookie(url.toString())
val checkTips = EhUrl.DOMAIN_E in url.host
return cookies?.let {
parseClientCookiesHeader(cookies)
.map { Cookie(it.key, it.value) }
.filterTo(mutableListOf()) { it.name != KEY_UTMP_NAME }.apply {
if (checkTips) {
removeAll { it.name == KEY_CONTENT_WARNING }
add(sTipsCookie)
}
}
} ?: if (checkTips) listOf(sTipsCookie) else emptyList()
}
override suspend fun get(requestUrl: Url) = load(requestUrl)
}
@@ -17,28 +17,38 @@ package com.hippo.ehviewer.client
import arrow.core.Either
import arrow.core.left
import arrow.core.partially2
import arrow.core.right
import arrow.fx.coroutines.parMap
import arrow.fx.coroutines.parZip
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.model.BaseGalleryInfo
import com.ehviewer.core.model.GalleryInfo
import com.ehviewer.core.network.EhCookieStore
import com.ehviewer.core.util.logcat
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.FavListUrlBuilder
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.data.fillInfo
import com.hippo.ehviewer.client.data.filterComments
import com.hippo.ehviewer.client.exception.CloudflareBypassException
import com.hippo.ehviewer.client.exception.EhError
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.exception.InsufficientFundsException
import com.hippo.ehviewer.client.exception.InsufficientGpException
import com.hippo.ehviewer.client.exception.IpBannedException
import com.hippo.ehviewer.client.exception.NoHathClientException
import com.hippo.ehviewer.client.exception.NoHitsFoundException
import com.hippo.ehviewer.client.exception.NotLoggedInException
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.client.parser.ArchiveParser
import com.hippo.ehviewer.client.parser.EventPaneParser
import com.hippo.ehviewer.client.parser.FavParserResult
import com.hippo.ehviewer.client.parser.FavoritesParser
import com.hippo.ehviewer.client.parser.ForumsParser
import com.hippo.ehviewer.client.parser.GalleryApiParser
import com.hippo.ehviewer.client.parser.GalleryDetailParser
import com.hippo.ehviewer.client.parser.GalleryListParser
import com.hippo.ehviewer.client.parser.GalleryListResult
import com.hippo.ehviewer.client.parser.GalleryNotAvailableParser
import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser
import com.hippo.ehviewer.client.parser.GalleryPageParser
import com.hippo.ehviewer.client.parser.GalleryTokenApiParser
import com.hippo.ehviewer.client.parser.HomeParser
@@ -47,35 +57,40 @@ import com.hippo.ehviewer.client.parser.RateGalleryResult
import com.hippo.ehviewer.client.parser.SignInParser
import com.hippo.ehviewer.client.parser.TorrentParser
import com.hippo.ehviewer.client.parser.TorrentResult
import com.hippo.ehviewer.client.parser.UserConfigParser
import com.hippo.ehviewer.client.parser.VoteCommentResult
import com.hippo.ehviewer.client.parser.VoteTagParser
import com.hippo.ehviewer.dailycheck.showEventNotification
import com.hippo.ehviewer.dailycheck.today
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.ReadableTime
import com.hippo.ehviewer.util.StatusCodeException
import io.ktor.client.request.forms.append
import com.hippo.ehviewer.util.bodyAsUtf8Text
import com.hippo.ehviewer.util.ensureSuccess
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.HttpStatement
import io.ktor.client.statement.bodyAsChannel
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.utils.io.core.writeFully
import io.ktor.client.statement.request
import io.ktor.http.Url
import io.ktor.util.moveToByteArray
import io.ktor.utils.io.pool.DirectByteBufferPool
import io.ktor.utils.io.pool.useInstance
import io.ktor.utils.io.readAvailable
import java.io.File
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import kotlin.math.ceil
import kotlin.system.measureTimeMillis
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.serialization.SerializationException
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.json.add
import kotlinx.serialization.json.addJsonArray
import kotlinx.serialization.json.put
import moe.tarsin.coroutines.removeAllSuspend
import moe.tarsin.coroutines.runSuspendCatching
import org.jsoup.Jsoup
import okio.buffer
import okio.sink
import splitties.init.appCtx
// https://ehwiki.org/wiki/API#Basics
@@ -83,61 +98,67 @@ private const val MAX_REQUEST_SIZE = 25
private const val MAX_SEQUENTIAL_REQUESTS = 5
private const val REQUEST_INTERVAL = 5000L
private const val U_CONFIG_TEXT = "Selected Profile"
private val Url.isLogin get() = segments.firstOrNull() == "bounce_login.php"
fun Either<String, ByteBuffer>.saveParseError(e: Throwable) {
private fun Either<String, ByteBuffer>.saveParseError(e: Throwable) {
val dir = AppConfig.externalParseErrorDir ?: return
val file = File(dir, ReadableTime.getFilenamableTime(System.currentTimeMillis()) + ".txt")
RandomAccessFile(file, "rw").use { randomAccessFile ->
with(randomAccessFile) {
writeUTF(e.message + "\n")
onLeft { writeUTF(it) }
onRight { channel.write(it) }
val file = File(dir, ReadableTime.getFilenamableTime() + ".txt")
file.sink().buffer().use { sink ->
with(sink) {
writeUtf8(e.message + "\n")
onLeft { writeUtf8(it) }
onRight { write(it) }
}
}
}
fun rethrowExactly(code: Int, body: Either<String, ByteBuffer>, e: Throwable): Nothing {
private fun rethrowExactly(response: HttpResponse, body: Either<String, ByteBuffer>, e: Throwable): Nothing {
// Don't translate coroutine cancellation
if (e is CancellationException) throw e
// Check sad panda(without panda)
if (response.headers["cf-mitigated"] == "challenge") throw CloudflareBypassException()
// Check sad panda (without panda)
val empty = body.fold(
{ it.isEmpty() },
{ !it.hasRemaining() },
)
if (empty) {
if (EhUtils.isExHentai) {
throw EhException("Sad Panda\n(without panda)")
val message = if (EhUtils.isExHentai) {
"Sad Panda\n(without panda)"
} else {
throw EhException("IP banned")
appCtx.getString(R.string.error_ip_banned)
}
throw IpBannedException(message)
}
// Check Gallery Not Available
body.onLeft {
if ("Gallery Not Available - " in it) {
val error = GalleryNotAvailableParser.parse(it)
if (!error.isNullOrBlank()) {
throw EhException(error)
}
}
}
// Parse error from rust
val error = body.getOrNull()?.runCatching {
val array = moveToByteArray()
rewind()
Cbor.decodeFromByteArray<EhError>(array)
}?.getOrNull()
// Check Gallery Not Available - 404
if (error is EhError.GalleryUnavailable) throw EhException(error.message)
// Check bad response code
if (code >= 400) {
throw StatusCodeException(code)
response.status.ensureSuccess()
when (error) {
EhError.NoHits -> throw NoHitsFoundException()
EhError.NoWatched -> throw EhException(R.string.gallery_list_empty_hit_subscription)
EhError.NeedLogin -> throw NotLoggedInException()
EhError.NoHathClient -> throw NoHathClientException()
EhError.InsufficientFunds -> throw InsufficientFundsException()
is EhError.IpBanned -> throw IpBannedException(error.message)
is EhError.Error -> throw EhException(error.message)
else -> Unit
}
if (e is ParseException || e is SerializationException) {
body.onLeft { if ("<" !in it) throw EhException(it) }
when (val message = e.cause?.message) {
"No hits found!" -> throw EhException(R.string.gallery_list_empty_hit)
"No watched tags!" -> throw EhException(R.string.gallery_list_empty_hit_subscription)
"Not logged in!" -> throw NotLoggedInException()
is String -> if (message.startsWith("Your IP address")) throw EhException(message)
}
if (Settings.saveParseErrorBody) body.saveParseError(e)
body.onLeft { if ("<" !in it) throw IpBannedException(it) }
if (Settings.saveParseErrorBody.value) body.saveParseError(e)
throw EhException(appCtx.getString(R.string.error_parse_error), e)
}
@@ -145,73 +166,90 @@ fun rethrowExactly(code: Int, body: Either<String, ByteBuffer>, e: Throwable): N
throw e
}
val httpContentPool = DirectByteBufferPool(8, 0x80000)
private val httpContentPool = DirectByteBufferPool(8, 0x80000)
suspend inline fun <T> HttpStatement.fetchUsingAsText(crossinline block: suspend String.() -> T) = execute { response ->
val body = response.bodyAsText()
private suspend inline fun <T> HttpStatement.fetchUsingAsText(crossinline block: suspend String.() -> T) = executeSafely { response ->
if (response.request.url.isLogin) throw NotLoggedInException()
val body = response.bodyAsUtf8Text()
runSuspendCatching {
block(body)
}.onFailure {
rethrowExactly(response.status.value, body.left(), it)
rethrowExactly(response, body.left(), it)
}.getOrThrow()
}
suspend inline fun <T> HttpStatement.fetchUsingAsByteBuffer(crossinline block: suspend ByteBuffer.() -> T) = execute { response ->
private suspend inline fun <T> HttpStatement.fetchUsingAsByteBuffer(crossinline block: suspend ByteBuffer.() -> T) = executeSafely { response ->
if (response.request.url.isLogin) throw NotLoggedInException()
httpContentPool.useInstance { buffer ->
with(response.bodyAsChannel()) { while (!isClosedForRead) readAvailable(buffer) }
buffer.flip()
runSuspendCatching {
block(buffer)
}.onFailure {
rethrowExactly(response.status.value, buffer.right(), it)
buffer.rewind()
rethrowExactly(response, buffer.right(), it)
}.getOrThrow()
}
}
object EhEngine {
suspend fun getOriginalImageUrl(url: String, referer: String?) = noRedirectEhRequest(url, referer).execute { response ->
val location = response.headers["Location"] ?: throw InsufficientFundsException()
location.takeIf { "bounce_login" !in it } ?: throw NotLoggedInException()
suspend fun getOriginalImageUrl(url: String, referer: String?) = noRedirectEhRequest(url, referer).executeSafely { response ->
response.headers["Location"]?.takeUnless { Url(it).isLogin } ?: throw InsufficientGpException()
}
suspend fun getTorrentList(url: String, gid: Long, token: String?): TorrentResult {
suspend fun getPTokenListFromMpv(gid: Long, token: String) = ehRequest(EhUrl.getGalleryMultiPageViewerUrl(gid, token), EhUrl.referer)
.fetchUsingAsText(GalleryMultiPageViewerPTokenParser::parse)
suspend fun getTorrentList(gid: Long, token: String): TorrentResult {
val url = EhUrl.getTorrentUrl(gid, token)
val referer = EhUrl.getGalleryDetailUrl(gid, token)
return ehRequest(url, referer).fetchUsingAsByteBuffer(TorrentParser::parse)
}
suspend fun getArchiveList(url: String, gid: Long, token: String?) = ehRequest(url, EhUrl.getGalleryDetailUrl(gid, token))
.fetchUsingAsText(ArchiveParser::parse)
.apply { funds = funds ?: ehRequest(EhUrl.URL_FUNDS).fetchUsingAsText(HomeParser::parseFunds) }
suspend fun getTorrentKey() = EhCookieStore.getUserId()?.let { id ->
val key = ehRequest(EhUrl.URL_HOME).fetchUsingAsText(HomeParser::parseTorrentKey)
"${id}x$key"
}
suspend fun getArchiveList(gid: Long, token: String): ArchiveParser.Result {
val url = EhUrl.getArchiveUrl(gid, token)
val funds = if (EhUtils.isExHentai) getFunds() else null
return ehRequest(url, EhUrl.getGalleryDetailUrl(gid, token))
.fetchUsingAsByteBuffer(ArchiveParser::parse.partially2(funds))
}
suspend fun getImageLimits() = parZip(
{ ehRequest(EhUrl.URL_HOME).fetchUsingAsByteBuffer(HomeParser::parse) },
{ ehRequest(EhUrl.URL_FUNDS).fetchUsingAsText(HomeParser::parseFunds) },
{ getFunds() },
{ limits, funds -> HomeParser.Result(limits, funds) },
)
private suspend fun getFunds() = ehRequest(EhUrl.URL_FUNDS).fetchUsingAsText(HomeParser::parseFunds)
suspend fun getNews(parse: Boolean) = ehRequest(EhUrl.URL_NEWS, EhUrl.REFERER_E)
.fetchUsingAsText { if (parse) EventPaneParser.parse(this) else null }
.fetchUsingAsByteBuffer { if (parse) EventPaneParser.parse(this) else null }
suspend fun getProfile(): ProfileParser.Result {
val url = ehRequest(EhUrl.URL_FORUMS).fetchUsingAsText(ForumsParser::parse)
return ehRequest(url, EhUrl.URL_FORUMS).fetchUsingAsText(ProfileParser::parse)
val url = ehRequest(EhUrl.URL_FORUMS).fetchUsingAsByteBuffer(ProfileParser::parseProfileUrl)
return ehRequest(url, EhUrl.URL_FORUMS).fetchUsingAsByteBuffer(ProfileParser::parse)
}
suspend fun getUConfig(url: String = EhUrl.uConfigUrl) {
suspend fun getUConfig(gallerySite: Int) {
val url = EhUrl.getUConfigUrl(gallerySite)
runSuspendCatching {
ehRequest(url).fetchUsingAsText { check(U_CONFIG_TEXT in this) { "Unable to load config from $url!" } }
ehRequest(url).fetchUsingAsByteBuffer(UserConfigParser::parse)
}.onFailure { throwable ->
// It may get redirected when accessing ex for the first time
if (url == EhUrl.URL_UCONFIG_EX) {
throwable.printStackTrace()
ehRequest(url).fetchUsingAsText { check(U_CONFIG_TEXT in this) { "Unable to load config from $url!" } }
logcat(throwable)
ehRequest(url).fetchUsingAsByteBuffer(UserConfigParser::parse)
} else {
throw throwable
}
}
}
suspend fun getGalleryPage(url: String, gid: Long, token: String?): GalleryPageParser.Result {
suspend fun getGalleryPage(url: String, gid: Long, token: String): GalleryPageParser.Result {
val referer = EhUrl.getGalleryDetailUrl(gid, token)
return ehRequest(url, referer).fetchUsingAsText(GalleryPageParser::parse)
}
@@ -220,18 +258,19 @@ object EhEngine {
.apply { galleryInfoList.fillInfo(url, true) }
.takeUnless { it.galleryInfoList.isEmpty() } ?: GalleryListParser.emptyResult
suspend fun getGalleryDetail(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsText {
val eventPane = EventPaneParser.parse(this)
if (eventPane != null) {
suspend fun getGalleryDetail(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsByteBuffer(GalleryDetailParser::parse).run {
event?.let {
Settings.lastDawnDays = today
showEventNotification(eventPane)
showEventNotification(it)
}
detail.apply {
// Fill info for database
fillInfo()
filterComments()
}
GalleryDetailParser.parse(this)
}
suspend fun getPreviewList(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsText {
GalleryDetailParser.parsePreviewList(this) to GalleryDetailParser.parsePreviewPages(this)
}
suspend fun getPreviewList(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsByteBuffer(GalleryDetailParser::parsePreviews)
suspend fun getFavorites(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsByteBuffer(FavoritesParser::parse)
.apply { galleryInfoList.fillInfo(url) }
@@ -261,21 +300,14 @@ object EhEngine {
append("edit_comment", id.toString())
}
}
}.execute { response ->
}.executeSafely { response ->
// Ktor does not handle POST redirect, we need to do it manually
// https://youtrack.jetbrains.com/issue/KTOR-478
val location = response.headers["Location"] ?: url
ehRequest(location, url).fetchUsingAsText {
val document = Jsoup.parse(this)
val elements = document.select("#chd + p")
if (elements.size > 0) {
throw EhException(elements[0].text())
}
GalleryDetailParser.parseComments(document)
}
ehRequest(location, url).fetchUsingAsByteBuffer(GalleryDetailParser::parseComments)
}
suspend fun modifyFavorites(gid: Long, token: String?, dstCat: Int = -1, note: String = "") {
suspend fun modifyFavorites(gid: Long, token: String, dstCat: Int = -1, note: String = "") {
val catStr: String = when (dstCat) {
-1 -> "favdel"
in 0..9 -> dstCat.toString()
@@ -290,17 +322,18 @@ object EhEngine {
append("apply", "Apply Changes")
append("update", "1")
}
}.execute { }
}.executeSafely { }
}
suspend fun downloadArchive(gid: Long, token: String?, or: String?, res: String?, isHAtH: Boolean): String? {
if (or.isNullOrEmpty()) throw EhException("Invalid form param or: $or")
if (res.isNullOrEmpty()) throw EhException("Invalid res: $res")
val url = EhUrl.getDownloadArchive(gid, token, or)
suspend fun getFavoriteNote(gid: Long, token: String) = ehRequest(EhUrl.getAddFavorites(gid, token), EhUrl.getGalleryDetailUrl(gid, token))
.fetchUsingAsText(FavoritesParser::parseNote)
suspend fun downloadArchive(gid: Long, token: String, res: String, isHath: Boolean): String? {
val url = EhUrl.getArchiveUrl(gid, token)
val referer = EhUrl.getGalleryDetailUrl(gid, token)
val request = ehRequest(url, referer, EhUrl.origin) {
formBody {
if (isHAtH) {
if (isHath) {
append("hathdl_xres", res)
} else {
append("dltype", res)
@@ -312,12 +345,12 @@ object EhEngine {
}
}
}
var result = request.fetchUsingAsText(ArchiveParser::parseArchiveUrl)
if (!isHAtH) {
var result = request.fetchUsingAsByteBuffer(ArchiveParser::parseArchiveUrl)
if (!isHath) {
if (result == null) {
// Wait for the server to prepare archives
delay(1000)
result = request.fetchUsingAsText(ArchiveParser::parseArchiveUrl)
result = request.fetchUsingAsByteBuffer(ArchiveParser::parseArchiveUrl)
if (result == null) throw EhException("Archive unavailable")
}
return result
@@ -327,7 +360,7 @@ object EhEngine {
suspend fun resetImageLimits() = ehRequest(EhUrl.URL_HOME) {
formBody {
append("reset_imagelimit", "Reset Limit")
append("reset_imagelimit", "Reset Quota")
}
}.fetchUsingAsText(HomeParser::parseResetLimits)
@@ -361,67 +394,64 @@ object EhEngine {
}.fetchUsingAsText { GalleryPageParser.parse(filterNot { it == '\\' }) }
}
suspend fun rateGallery(apiUid: Long, apiKey: String?, gid: Long, token: String?, rating: Float): RateGalleryResult = ehRequest(EhUrl.apiUrl, EhUrl.getGalleryDetailUrl(gid, token), EhUrl.origin) {
suspend fun rateGallery(apiUid: Long, apiKey: String?, gid: Long, token: String, rating: Float): RateGalleryResult = ehRequest(EhUrl.apiUrl, EhUrl.getGalleryDetailUrl(gid, token), EhUrl.origin) {
jsonBody {
put("method", "rategallery")
put("apiuid", apiUid)
put("apikey", apiKey)
put("apikey", requireNotNull(apiKey))
put("gid", gid)
put("token", token)
put("rating", ceil((rating * 2).toDouble()).toInt())
}
}.fetchUsingAsText(String::parseAs)
suspend fun fillGalleryListByApi(galleryInfoList: List<GalleryInfo>, referer: String) =
galleryInfoList.chunked(MAX_REQUEST_SIZE).chunked(MAX_SEQUENTIAL_REQUESTS).forEachIndexed { index, chunk ->
if (index != 0) {
delay(REQUEST_INTERVAL)
}
chunk.parMap {
ehRequest(EhUrl.apiUrl, referer, EhUrl.origin) {
jsonBody {
put("method", "gdata")
array("gidlist") {
it.forEach {
addJsonArray {
add(it.gid)
add(it.token)
}
suspend fun fillGalleryListByApi(galleryInfoList: List<GalleryInfo>, referer: String? = null) = galleryInfoList.chunked(MAX_REQUEST_SIZE).chunked(MAX_SEQUENTIAL_REQUESTS).forEachIndexed { index, chunk ->
if (index != 0) {
delay(REQUEST_INTERVAL)
}
chunk.parMap {
ehRequest(EhUrl.apiUrl, referer, EhUrl.origin) {
jsonBody {
put("method", "gdata")
array("gidlist") {
it.forEach { info ->
addJsonArray {
add(info.gid)
add(info.token)
}
}
put("namespace", 1)
}
}.fetchUsingAsText { GalleryApiParser.parse(this, it) }
}
put("namespace", 1)
}
}.fetchUsingAsText { GalleryApiParser.parse(this, it) }
}
}
suspend fun voteComment(apiUid: Long, apiKey: String?, gid: Long, token: String?, commentId: Long, commentVote: Int): VoteCommentResult =
ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
jsonBody {
put("method", "votecomment")
put("apiuid", apiUid)
put("apikey", apiKey)
put("gid", gid)
put("token", token)
put("comment_id", commentId)
put("comment_vote", commentVote)
}
}.fetchUsingAsText(String::parseAs)
suspend fun voteComment(apiUid: Long, apiKey: String?, gid: Long, token: String, commentId: Long, commentVote: Int): VoteCommentResult = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
jsonBody {
put("method", "votecomment")
put("apiuid", apiUid)
put("apikey", requireNotNull(apiKey))
put("gid", gid)
put("token", token)
put("comment_id", commentId)
put("comment_vote", commentVote)
}
}.fetchUsingAsText(String::parseAs)
suspend fun voteTag(apiUid: Long, apiKey: String?, gid: Long, token: String?, tags: String?, vote: Int) =
ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
jsonBody {
put("method", "taggallery")
put("apiuid", apiUid)
put("apikey", apiKey)
put("gid", gid)
put("token", token)
put("tags", tags)
put("vote", vote)
}
}.fetchUsingAsText(VoteTagParser::parse)
suspend fun voteTag(apiUid: Long, apiKey: String?, gid: Long, token: String, tags: String, vote: Int) = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
jsonBody {
put("method", "taggallery")
put("apiuid", apiUid)
put("apikey", requireNotNull(apiKey))
put("gid", gid)
put("token", token)
put("tags", tags)
put("vote", vote)
}
}.fetchUsingAsByteBuffer(VoteTagParser::parse)
suspend fun getGalleryToken(gid: Long, gtoken: String?, page: Int) = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
suspend fun getGalleryToken(gid: Long, gtoken: String, page: Int) = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
jsonBody {
put("method", "gtoken")
array("pagelist") {
@@ -434,42 +464,30 @@ object EhEngine {
}
}.fetchUsingAsText(GalleryTokenApiParser::parse)
suspend fun imageSearch(jpeg: File, uss: Boolean, osc: Boolean): GalleryListResult {
val location = noRedirectEhRequest(EhUrl.imageSearchUrl, EhUrl.referer, EhUrl.origin) {
multipartBody {
append("sfile", "a.jpg", ContentType.Image.JPEG, jpeg.length()) {
RandomAccessFile(jpeg, "r").use {
writeFully(it.channel.map(FileChannel.MapMode.READ_ONLY, 0, it.length()))
}
}
if (uss) append("fs_similar", "on")
if (osc) append("fs_covers", "on")
append("f_sfile", "File Search")
}
}.execute { it.headers["Location"] ?: error("Failed to search image!!!") }
return ehRequest(location).fetchUsingAsByteBuffer(GalleryListParser::parse).apply {
galleryInfoList.fillInfo(EhUrl.imageSearchUrl)
}
}
private suspend fun MutableList<BaseGalleryInfo>.fillInfo(url: String, filter: Boolean = false) = with(EhFilter) {
if (filter) removeAllSuspend { filterTitle(it) || filterUploader(it) }
val hasTags = any { !it.simpleTags.isNullOrEmpty() }
val hasPages = any { it.pages != 0 }
val hasRated = any { it.rated }
val needApi = filter && needTags() && !hasTags || Settings.showGalleryPages && !hasPages || hasRated
val needApi = filter && needTags() && !hasTags || Settings.showGalleryPages.value && !hasPages || hasRated
if (needApi) fillGalleryListByApi(this@fillInfo, url)
if (filter) removeAllSuspend { filterUploader(it) || filterTag(it) || filterTagNamespace(it) }
forEach {
if (it.favoriteSlot == GalleryInfo.NOT_FAVORITED && EhDB.containLocalFavorites(it.gid)) {
it.favoriteSlot = GalleryInfo.LOCAL_FAVORITED
}
if (!needApi) it.generateSLang()
}
}
suspend fun addFavorites(galleryList: List<Pair<Long, String>>, dstCat: Int) {
galleryList.chunked(MAX_SEQUENTIAL_REQUESTS).forEachIndexed { index, chunk ->
if (index != 0) {
delay(REQUEST_INTERVAL)
}
chunk.parMap { (gid, token) ->
galleryList.forEach { (gid, token) ->
// https://github.com/FooIbar/EhViewer/issues/1190
// Workaround for duplicate items when sorting by favorited time
val timeTaken = measureTimeMillis {
modifyFavorites(gid, token, dstCat)
}
delay(1000 - timeTaken)
}
}
}
@@ -1,10 +1,10 @@
package com.hippo.ehviewer.client
import arrow.core.memoize
import com.ehviewer.core.database.model.Filter
import com.ehviewer.core.database.model.FilterMode
import com.ehviewer.core.model.GalleryInfo
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.dao.Filter
import com.hippo.ehviewer.dao.FilterMode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -55,8 +55,8 @@ object EhFilter : CoroutineScope {
suspend fun needTags() = filters.await().any { it.enable && (it.mode == FilterMode.TAG || it.mode == FilterMode.TAG_NAMESPACE) }
suspend fun filterTitle(info: GalleryInfo) = anyActive(FilterMode.TITLE) { info.title.orEmpty().contains(it.text, true) }
suspend fun filterUploader(info: GalleryInfo) = anyActive(FilterMode.UPLOADER) { it.text == info.uploader }
suspend fun filterTag(info: GalleryInfo) = info.simpleTags?.any { tag -> anyActive(FilterMode.TAG) { matchTag(tag, it.text.lowercase()) } } ?: false
suspend fun filterTagNamespace(info: GalleryInfo) = info.simpleTags?.any { tag -> anyActive(FilterMode.TAG_NAMESPACE) { matchTagNamespace(tag, it.text.lowercase()) } } ?: false
suspend fun filterTag(info: GalleryInfo) = info.simpleTags?.any { tag -> anyActive(FilterMode.TAG) { matchTag(tag, it.text.lowercase()) } } == true
suspend fun filterTagNamespace(info: GalleryInfo) = info.simpleTags?.any { tag -> anyActive(FilterMode.TAG_NAMESPACE) { matchTagNamespace(tag, it.text.lowercase()) } } == true
suspend fun filterCommenter(commenter: String) = anyActive(FilterMode.COMMENTER) { it.text == commenter }
suspend fun filterComment(comment: String) = anyActive(FilterMode.COMMENT) { regex(it).containsMatchIn(comment) }
}
@@ -15,53 +15,49 @@
*/
package com.hippo.ehviewer.client
import com.ehviewer.core.util.logcat
import com.hippo.ehviewer.EhApplication.Companion.ktorClient
import com.hippo.ehviewer.EhApplication.Companion.noRedirectKtorClient
import com.hippo.ehviewer.Settings
import eu.kanade.tachiyomi.util.system.logcat
import com.hippo.ehviewer.util.bodyAsUtf8Text
import com.hippo.ehviewer.util.ensureSuccess
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.FormBuilder
import io.ktor.client.request.forms.FormDataContent
import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData
import io.ktor.client.request.header
import io.ktor.client.request.prepareRequest
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.HttpStatement
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.ParametersBuilder
import io.ktor.http.content.TextContent
import io.ktor.http.userAgent
import kotlinx.coroutines.cancel
import kotlinx.io.Source
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArrayBuilder
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.okio.decodeFromBufferedSource
import okio.BufferedSource
import kotlinx.serialization.json.io.decodeFromSource
inline fun JsonObjectBuilder.array(name: String, builder: JsonArrayBuilder.() -> Unit) = put(name, buildJsonArray(builder))
suspend inline fun <reified T> HttpStatement.executeAndParseAs() = execute { it.bodyAsText().parseAs<T>() }
inline fun <reified T> BufferedSource.parseAs(): T = json.decodeFromBufferedSource(this)
suspend inline fun <reified T> HttpStatement.executeAndParseAs() = executeSafely {
it.status.ensureSuccess()
it.bodyAsUtf8Text().parseAs<T>()
}
inline fun <reified T> Source.parseAs(): T = json.decodeFromSource(this)
inline fun <reified T> String.parseAs(): T = json.decodeFromString(this)
val json = Json { ignoreUnknownKeys = true }
const val CHROME_USER_AGENT = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Mobile Safari/537.36"
const val CHROME_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
const val CHROME_ACCEPT_LANGUAGE = "en-US,en;q=0.5"
val json = Json {
ignoreUnknownKeys = true
explicitNulls = false
}
fun HttpRequestBuilder.applyEhConfig(referer: String?, origin: String?) {
method = HttpMethod.Get
header("Referer", referer)
header("Origin", origin)
userAgent(Settings.userAgent)
header(HttpHeaders.Accept, CHROME_ACCEPT)
header(HttpHeaders.AcceptLanguage, CHROME_ACCEPT_LANGUAGE)
}
suspend inline fun ehRequest(
@@ -85,6 +81,16 @@ suspend inline fun noRedirectEhRequest(
apply(builder)
}.also { logcat("EhRequest") { url } }
suspend inline fun <T> HttpStatement.executeSafely(
crossinline block: suspend (response: HttpResponse) -> T,
) = execute { resp ->
try {
block(resp)
} finally {
resp.coroutineContext.cancel()
}
}
inline fun HttpRequestBuilder.formBody(builder: ParametersBuilder.() -> Unit) {
method = HttpMethod.Post
val parameters = ParametersBuilder().apply(builder).build()
@@ -96,8 +102,3 @@ inline fun HttpRequestBuilder.jsonBody(builder: JsonObjectBuilder.() -> Unit) {
val json = buildJsonObject(builder).toString()
setBody(TextContent(text = json, contentType = ContentType.Application.Json))
}
fun HttpRequestBuilder.multipartBody(builder: FormBuilder.() -> Unit) {
method = HttpMethod.Post
setBody(MultiPartFormDataContent(formData(builder)))
}
@@ -19,35 +19,39 @@ import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import arrow.core.Either
import com.ehviewer.core.files.metadataOrNull
import com.ehviewer.core.files.read
import com.ehviewer.core.files.write
import com.ehviewer.core.model.TagNamespace
import com.ehviewer.core.util.logcat
import com.hippo.ehviewer.EhApplication.Companion.ktorClient
import com.hippo.ehviewer.R
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.FileUtils
import com.hippo.ehviewer.util.StatusCodeException
import com.hippo.ehviewer.util.copyTo
import io.ktor.client.HttpClient
import com.hippo.ehviewer.util.bodyAsUtf8Text
import com.hippo.ehviewer.util.ensureSuccess
import com.hippo.ehviewer.util.utf8
import io.ktor.client.request.prepareGet
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.isSuccess
import java.io.File
import java.nio.charset.StandardCharsets
import kotlin.time.Clock
import kotlin.time.Duration.Companion.days
import kotlin.time.Instant
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.io.Source
import kotlinx.io.writeString
import kotlinx.serialization.json.io.encodeToSink
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import moe.tarsin.coroutines.runSuspendCatching
import okio.BufferedSource
import okio.HashingSink.Companion.sha1
import okio.blackholeSink
import okio.buffer
import okio.source
import splitties.init.appCtx
object EhTagDatabase : CoroutineScope {
override val coroutineContext = Dispatchers.IO + Job()
private const val NAMESPACE_PREFIX = "n"
private val PREFIXES = arrayOf(
"-",
@@ -57,223 +61,140 @@ object EhTagDatabase : CoroutineScope {
)
private lateinit var tagGroups: Map<String, Map<String, String>>
private val updateLock = Mutex()
override val coroutineContext = Dispatchers.IO + Job()
var initialized by mutableStateOf(false)
fun getTranslation(prefix: String? = NAMESPACE_PREFIX, tag: String?): String? {
return tagGroups[prefix]?.get(tag)?.trim()?.takeIf { it.isNotEmpty() }
}
fun getTranslation(prefix: String? = NAMESPACE_PREFIX, tag: String?): String? = tagGroups[prefix]?.get(tag)?.trim()?.ifEmpty { null }
private fun internalSuggestFlow(
tags: Map<String, String>,
keyword: String,
translate: Boolean,
exactly: Boolean,
): Flow<Pair<String?, String>> = flow {
if (exactly) {
tags[keyword]?.let {
emit(Pair(it.takeIf { translate }, keyword))
}
// Copied from https://github.com/EhTagTranslation/EhSyringe/blob/c0cf88da04081072492829673aca34380b14dae6/src/plugin/suggest.ts#L37
// TODO: Optimize
private val nsScore = mapOf(
TagNamespace.Location.prefix to 10f,
TagNamespace.Other.prefix to 10f,
TagNamespace.Female.prefix to 9f,
TagNamespace.Male.prefix to 8.5f,
TagNamespace.Mixed.prefix to 8f,
TagNamespace.Parody.prefix to 3.3f,
TagNamespace.Character.prefix to 2.8f,
TagNamespace.Artist.prefix to 2.5f,
TagNamespace.Cosplayer.prefix to 2.4f,
TagNamespace.Group.prefix to 2.2f,
TagNamespace.Language.prefix to 2f,
)
data class Tag(val tag: String, val hint: String?, val score: Float)
context(_: Context)
fun suggestion(rawKeyword: String, expectTranslate: Boolean): List<Tag> {
val keyword = PREFIXES.fold(rawKeyword) { kwd, pfx -> kwd.removePrefix(pfx) }
if (!initialized || keyword.isEmpty()) return emptyList()
val translate = expectTranslate && translatable
val prefix = rawKeyword.dropLast(keyword.length)
val ns = keyword.substringBefore(':')
val tag = keyword.drop(ns.length + 1)
val nsPrefix = TagNamespace.from(ns)?.prefix ?: ns
val tags = tagGroups[nsPrefix.takeIf { tag.isNotEmpty() && it != NAMESPACE_PREFIX }]
return if (tags != null) {
lookup(tags, tag, translate).map { it.copy(tag = "$prefix$nsPrefix:${it.tag}") }
} else {
if (translate) {
tags.forEach { (tag, hint) ->
if (tag != keyword &&
(tag.containsIgnoreSpace(keyword) || hint.containsIgnoreSpace(keyword))
) {
emit(Pair(hint, tag))
}
}
} else {
tags.keys.forEach { tag ->
if (tag != keyword && tag.containsIgnoreSpace(keyword)) {
emit(Pair(null, tag))
}
}
}
}
}
// Construct a cold flow for tag database suggestions
fun suggestFlow(
keyword: String,
translate: Boolean,
exactly: Boolean = false,
): Flow<Pair<String?, String>> = flow {
var mKeyword = keyword
PREFIXES.forEach { mKeyword = mKeyword.removePrefix(it) }
val prefix = keyword.dropLast(mKeyword.length)
val namespace = mKeyword.substringBefore(':')
val tagKeyword = mKeyword.drop(namespace.length + 1)
val namespacePrefix = namespaceToPrefix(namespace) ?: namespace
val tags = tagGroups[namespacePrefix.takeIf { tagKeyword.isNotEmpty() && it != NAMESPACE_PREFIX }]
tags?.let {
internalSuggestFlow(it, tagKeyword, translate, exactly).collect { (hint, tag) ->
emit(Pair(hint, "$prefix$namespacePrefix:$tag"))
}
} ?: tagGroups.forEach { (namespacePrefix, tags) ->
if (namespacePrefix != NAMESPACE_PREFIX) {
internalSuggestFlow(tags, mKeyword, translate, exactly).collect { (hint, tag) ->
emit(Pair(hint, "$prefix$namespacePrefix:$tag"))
}
} else {
internalSuggestFlow(tags, mKeyword, translate, exactly).collect { (hint, namespacePrefix) ->
emit(Pair(hint, "$prefix$namespacePrefix:"))
}
}
}
}
private fun String.removeSpace(): String = replace(" ", "")
private fun String.containsIgnoreSpace(other: String, ignoreCase: Boolean = true): Boolean =
removeSpace().contains(other.removeSpace(), ignoreCase)
private val NAMESPACE_TO_PREFIX = HashMap<String, String>().also {
it["artist"] = "a"
it["cosplayer"] = "cos"
it["character"] = "c"
it["female"] = "f"
it["group"] = "g"
it["language"] = "l"
it["male"] = "m"
it["mixed"] = "x"
it["other"] = "o"
it["parody"] = "p"
it["reclass"] = "r"
}
fun namespaceToPrefix(namespace: String): String? {
return NAMESPACE_TO_PREFIX[namespace]
}
private fun getMetadata(context: Context): Array<String>? {
return context.resources.getStringArray(R.array.tag_translation_metadata)
.takeIf { it.size == 4 }
}
fun isTranslatable(context: Context): Boolean {
return context.resources.getBoolean(R.bool.tag_translatable)
}
private fun getFileContent(file: File): String? {
return runCatching {
file.source().buffer().use { it.readString(StandardCharsets.UTF_8) }
}.getOrNull()
}
private fun getFileSha1(file: File): String? {
return runCatching {
file.source().buffer().use { source ->
sha1(blackholeSink()).use {
source.readAll(it)
it.hash.hex()
}
}
}.getOrNull()
}
private fun checkData(sha1: String?, data: File): Boolean {
return sha1 != null && sha1 == getFileSha1(data)
}
private suspend fun save(client: HttpClient, url: String, file: File) {
runCatching {
client.prepareGet(url).execute {
if (it.status.isSuccess()) {
it.bodyAsChannel().copyTo(file)
tagGroups.asSequence().flatMap { (nsPrefix, tags) ->
if (nsPrefix != NAMESPACE_PREFIX) {
lookup(tags, keyword, translate).map { it.copy(tag = "$prefix$nsPrefix:${it.tag}", score = it.score / (nsScore[nsPrefix] ?: 1f)) }
} else {
throw StatusCodeException(it.status.value)
lookup(tags, keyword, translate).map { it.copy(tag = "$prefix${it.tag}:", score = 0f) }
}
}
}.onFailure {
file.delete()
}.getOrThrow()
}.toMutableList().apply { sortBy { it.score } }
}
suspend fun update() {
private fun lookup(tags: Map<String, String>, keyword: String, translate: Boolean) = if (translate) {
tags.asSequence().mapNotNull { (tag, hint) ->
val score = if (tag.startsWith(keyword, ignoreCase = true)) {
tag.length
} else {
if (tag.indexOf(" $keyword", ignoreCase = true) != -1) {
tag.length * 2
} else {
when (hint.indexOf(keyword, ignoreCase = true)) {
-1 -> 0
0 -> hint.length
else -> hint.length * 2
}
}
}
if (score != 0) Tag(tag, hint, score.toFloat()) else null
}
} else {
tags.keys.asSequence().mapNotNull { tag ->
val score = if (tag.startsWith(keyword, ignoreCase = true)) {
tag.length
} else {
if (tag.indexOf(" $keyword", ignoreCase = true) != -1) {
tag.length * 2
} else {
0
}
}
if (score != 0) Tag(tag, null, score.toFloat()) else null
}
}
private fun metadata(context: Context): Array<String> = context.resources.getStringArray(R.array.tag_translation_metadata)
context(ctx: Context)
val translatable
get() = ctx.resources.getBoolean(R.bool.tag_translatable)
private suspend fun fetch(url: String) = ktorClient.prepareGet(url).executeSafely {
it.status.ensureSuccess()
it.bodyAsUtf8Text()
}
fun launchUpdate() = launch {
updateLock.withLock {
updateInternal()
}
}
private fun issueUpdateInMemoryData(file: File? = null) {
val dataFile = file ?: getMetadata(appCtx)?.let { metadata ->
val dataName = metadata[2]
val dir = AppConfig.getFilesDir("tag-translations")
File(dir, dataName).takeIf { it.exists() }
} ?: return
tagGroups = dataFile.source().buffer().use(BufferedSource::parseAs)
initialized = true
}
init {
launch {
updateLock.withLock {
runSuspendCatching {
issueUpdateInMemoryData()
}.onFailure {
it.printStackTrace()
}
updateInternal()
}
}
}
private suspend fun updateInternal() {
getMetadata(appCtx)?.let { metadata ->
val sha1Name = metadata[0]
val sha1Url = metadata[1]
val dataName = metadata[2]
val dataUrl = metadata[3]
val dir = AppConfig.getFilesDir("tag-translations")
checkNotNull(dir)
val sha1File = File(dir, sha1Name)
val dataFile = File(dir, dataName)
runSuspendCatching {
// Check current sha1 and current data
val sha1 = getFileContent(sha1File)
if (!checkData(sha1, dataFile)) {
FileUtils.delete(sha1File)
FileUtils.delete(dataFile)
}
// Save new sha1
val tempSha1File = File(dir, "$sha1Name.tmp")
save(ktorClient, sha1Url, tempSha1File)
val tempSha1 = getFileContent(tempSha1File)
// Check new sha1 and current sha1
if (tempSha1 == sha1) {
// The data is the same
FileUtils.delete(tempSha1File)
return
}
// Save new data
val tempDataFile = File(dir, "$dataName.tmp")
save(ktorClient, dataUrl, tempDataFile)
// Check new sha1 and new data
if (!checkData(tempSha1, tempDataFile)) {
FileUtils.delete(tempSha1File)
FileUtils.delete(tempDataFile)
return
}
// Replace current sha1 and current data with new sha1 and new data
FileUtils.delete(sha1File)
FileUtils.delete(dataFile)
tempSha1File.renameTo(sha1File)
tempDataFile.renameTo(dataFile)
// Read new EhTagDatabase
issueUpdateInMemoryData(dataFile)
atomicallyUpdate()
}.onFailure {
it.printStackTrace()
logcat(it)
}
}
}
private suspend fun atomicallyUpdate() {
val (sha1Name, sha1Url, dataName, dataUrl) = metadata(appCtx)
val workdir = AppConfig.tagTranslationsDir
val sha1File = workdir / sha1Name
val dataFile = workdir / dataName
if (!initialized) {
Either.catch {
tagGroups = dataFile.read(Source::parseAs)
initialized = true
// Perform only one automatic update (on app startup) per day
val lastModified = Instant.fromEpochMilliseconds(dataFile.metadataOrNull()!!.lastModifiedAtMillis!!)
if (Clock.System.now() - lastModified < 1.days) return
}.onLeft {
logcat(it)
}
}
val currentSha1 = if (initialized) {
Either.catch { sha1File.utf8() }.getOrNull()
} else {
null
}
val newSha1 = fetch(sha1Url)
if (newSha1 == currentSha1) return
val rawData = fetch(dataUrl)
tagGroups = json.parseToJsonElement(rawData).jsonObject["data"]!!.jsonArray.associate { e ->
val data = e.jsonObject
val namespace = data["namespace"]!!.jsonPrimitive.content
val prefix = if (namespace == "rows") NAMESPACE_PREFIX else TagNamespace.from(namespace)!!.prefix!!
prefix to data["data"]!!.jsonObject.mapValues { it.value.jsonObject["name"]!!.jsonPrimitive.content }
}
initialized = true
sha1File.write { writeString(newSha1) }
dataFile.write { json.encodeToSink(tagGroups, this) }
}
}
@@ -52,106 +52,111 @@ object EhUrl {
const val REFERER_E = "https://$DOMAIN_E"
const val ORIGIN_E = REFERER_E
val host: String
get() = when (Settings.gallerySite) {
SITE_E -> HOST_E
SITE_EX -> HOST_EX
else -> HOST_E
}
val domain: String
get() = when (Settings.gallerySite) {
get() = when (Settings.gallerySite.value) {
SITE_E -> DOMAIN_E
SITE_EX -> DOMAIN_EX
else -> HOST_E
else -> DOMAIN_E
}
val apiUrl: String
get() = if (Settings.forceEhThumb) {
API_E
} else {
when (Settings.gallerySite) {
SITE_E -> API_E
SITE_EX -> API_EX
else -> API_E
}
get() = when (Settings.gallerySite.value) {
SITE_E -> API_E
SITE_EX -> API_EX
else -> API_E
}
val referer: String
get() = when (Settings.gallerySite) {
get() = when (Settings.gallerySite.value) {
SITE_E -> REFERER_E
SITE_EX -> REFERER_EX
else -> REFERER_E
}
val origin: String
get() = when (Settings.gallerySite) {
get() = when (Settings.gallerySite.value) {
SITE_E -> ORIGIN_E
SITE_EX -> ORIGIN_EX
else -> ORIGIN_E
}
val uConfigUrl: String
get() = when (Settings.gallerySite) {
SITE_E -> URL_UCONFIG_E
SITE_EX -> URL_UCONFIG_EX
else -> URL_UCONFIG_E
}
fun getUConfigUrl(gallerySite: Int = Settings.gallerySite.value) = when (gallerySite) {
SITE_E -> URL_UCONFIG_E
SITE_EX -> URL_UCONFIG_EX
else -> URL_UCONFIG_E
}
val myTagsUrl: String
get() = when (Settings.gallerySite) {
get() = when (Settings.gallerySite.value) {
SITE_E -> URL_MY_TAGS_E
SITE_EX -> URL_MY_TAGS_EX
else -> URL_MY_TAGS_E
}
val popularUrl: String
get() = when (Settings.gallerySite) {
get() = when (Settings.gallerySite.value) {
SITE_E -> URL_POPULAR_E
SITE_EX -> URL_POPULAR_EX
else -> URL_POPULAR_E
}
val imageSearchUrl: String
get() = when (Settings.gallerySite) {
get() = when (Settings.gallerySite.value) {
SITE_E -> URL_IMAGE_SEARCH_E
SITE_EX -> URL_IMAGE_SEARCH_EX
else -> URL_IMAGE_SEARCH_E
}
fun getGalleryDetailUrl(gid: Long, token: String?): String {
return getGalleryDetailUrl(gid, token, 0, false)
}
fun getGalleryDetailUrl(gid: Long, token: String?, index: Int, allComment: Boolean) = ehUrl(listOf("g", gid.toString(), token.orEmpty())) {
fun getGalleryDetailUrl(gid: Long, token: String, index: Int = 0, allComment: Boolean = false) = ehUrl(listOf("g", "$gid", token)) {
if (index != 0) addQueryParameter("p", "$index")
if (allComment) addQueryParameter("hc", "1")
}.buildString()
fun getGalleryMultiPageViewerUrl(gid: Long, token: String) = ehUrl(listOf("mpv", gid.toString(), token)).buildString()
fun getGalleryMultiPageViewerUrl(gid: Long, token: String) = ehUrl(listOf("mpv", "$gid", token)).buildString()
fun getPageUrl(gid: Long, index: Int, pToken: String) = host + "s/" + pToken + '/' + gid + '-' + (index + 1)
fun getPageUrl(gid: Long, index: Int, pToken: String, nl: String? = null) = ehUrl(listOf("s", pToken, "$gid-${index + 1}")) {
addQueryParameterIfNotBlank("nl", nl)
}.buildString()
fun getAddFavorites(gid: Long, token: String?) = host + "gallerypopups.php?gid=" + gid + "&t=" + token + "&act=addfav"
fun getAddFavorites(gid: Long, token: String) = ehUrl("gallerypopups.php") {
addQueryParameter("gid", "$gid")
addQueryParameter("t", token)
addQueryParameter("act", "addfav")
}.buildString()
fun getDownloadArchive(gid: Long, token: String?, or: String) = host + "archiver.php?gid=" + gid + "&token=" + token + "&or=" + or
fun getArchiveUrl(gid: Long, token: String) = ehUrl("archiver.php") {
addQueryParameter("gid", "$gid")
addQueryParameter("token", token)
}.buildString()
fun getTorrentUrl(gid: Long, token: String) = ehUrl("gallerytorrents.php") {
addQueryParameter("gid", "$gid")
addQueryParameter("t", token)
}.buildString()
fun getTagDefinitionUrl(tag: String) = "https://ehwiki.org/wiki/" + tag.replace(' ', '_')
fun getTrackerUrl(gid: Long, key: String?) = ehUrl(
path = listOfNotNull("$gid", key, "announce"),
host = "ehtracker.org",
).buildString()
}
inline fun ehUrl(path: String?, builder: ParametersBuilder.() -> Unit) = ehUrl(
inline fun ehUrl(path: String? = null, host: String = EhUrl.domain, builder: ParametersBuilder.() -> Unit) = ehUrl(
path = if (path.isNullOrBlank()) emptyList() else listOf(path),
host = host,
parameters = Parameters.build(builder),
)
inline fun ehUrl(path: List<String>, builder: ParametersBuilder.() -> Unit) = ehUrl(
inline fun ehUrl(path: List<String>, host: String = EhUrl.domain, builder: ParametersBuilder.() -> Unit) = ehUrl(
path = path,
host = host,
parameters = Parameters.build(builder),
)
fun ehUrl(path: List<String>, parameters: Parameters = Parameters.Empty) = URLBuilder(
fun ehUrl(path: List<String>, host: String = EhUrl.domain, parameters: Parameters = Parameters.Empty) = URLBuilder(
protocol = URLProtocol.HTTPS,
host = EhUrl.domain,
host = host,
pathSegments = path,
parameters = parameters,
)
@@ -15,19 +15,36 @@
*/
package com.hippo.ehviewer.client
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Environment
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.core.net.toUri
import arrow.core.memoize
import com.google.android.material.color.utilities.Hct
import com.google.android.material.color.utilities.MathUtils
import com.ehviewer.core.model.GalleryDetail
import com.ehviewer.core.model.GalleryInfo
import com.ehviewer.core.network.EhCookieStore
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.data.GalleryInfo
import java.util.regex.Pattern
import com.hippo.ehviewer.client.parser.Archive
import com.hippo.ehviewer.spider.SpiderDen
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.FileUtils
import com.hippo.ehviewer.util.addTextToClipboard
import com.materialkolor.hct.Hct
import com.materialkolor.ktx.from
import com.materialkolor.ktx.toColor
import com.materialkolor.utils.ColorUtils.lstarFromArgb
import splitties.systemservices.downloadManager
object EhUtils {
const val NONE = -1 // Use it for homepage
@@ -59,14 +76,14 @@ object EhUtils {
private const val BG_COLOR_UNKNOWN = 0xff000000u
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
private val PATTERN_TITLE_PREFIX = Pattern.compile(
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*",
private val PATTERN_TITLE_PREFIX = Regex(
"""^(?:\([^)]*\)|\[[^]]*]|\{[^}]*\}|~[^~]*~|\s+)*""",
)
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
private val PATTERN_TITLE_SUFFIX = Pattern.compile(
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
Pattern.CASE_INSENSITIVE,
private val PATTERN_TITLE_SUFFIX = Regex(
"""(?:\s+ch.[\s\d-]+)?(?:\([^)]*\)|\[[^]]*]|\{[^}]*\}|~[^~]*~|\s+)*$""",
RegexOption.IGNORE_CASE,
)
private val CATEGORY_VALUES = hashMapOf(
@@ -86,106 +103,121 @@ object EhUtils {
private val CATEGORY_STRINGS = CATEGORY_VALUES.entries.map { (k, v) -> v to k }
val isExHentai: Boolean
get() = Settings.gallerySite == EhUrl.SITE_EX
get() = Settings.gallerySite.value == EhUrl.SITE_EX
val isMpvAvailable
get() = EhCookieStore.getHathPerks()?.contains('q') == true
fun getCategory(type: String?): Int {
for (entry in CATEGORY_STRINGS) {
for (str in entry.first)
for (str in entry.first) {
if (str.equals(type, ignoreCase = true)) {
return entry.second
}
}
}
return UNKNOWN
}
fun getCategory(type: Int): String {
return CATEGORY_VALUES.getOrDefault(type, CATEGORY_VALUES[UNKNOWN])!![0]
}
fun getCategory(type: Int): String = CATEGORY_VALUES.getOrDefault(type, CATEGORY_VALUES[UNKNOWN])!![0]
@SuppressLint("RestrictedApi")
val harmonizeWithRole = { primaryContainer: Int, src: Int ->
val fromHct = Hct.fromInt(src)
val toHct = Hct.fromInt(primaryContainer)
val differenceDegrees = MathUtils.differenceDegrees(fromHct.hue, toHct.hue)
val rotationDegrees = minOf(differenceDegrees * 0.5, 15.0)
val outputHue = MathUtils.sanitizeDegreesDouble(fromHct.hue + rotationDegrees * MathUtils.rotationDirection(fromHct.hue, toHct.hue))
Hct.from(outputHue, toHct.chroma, toHct.tone).toInt()
fun invCategory(category: Int): Int = category.inv() and ALL_CATEGORY
val mergeColor = { primaryContainer: Color, src: Color ->
val fromHct = Hct.from(src)
val toHct = Hct.from(primaryContainer)
Hct.from(fromHct.hue, toHct.chroma, toHct.tone).toColor()
}.memoize()
@Stable
@ReadOnlyComposable
@Composable
fun getCategoryColor(category: Int): Color {
val primary = when (category) {
DOUJINSHI -> BG_COLOR_DOUJINSHI
MANGA -> BG_COLOR_MANGA
ARTIST_CG -> BG_COLOR_ARTIST_CG
GAME_CG -> BG_COLOR_GAME_CG
WESTERN -> BG_COLOR_WESTERN
NON_H -> BG_COLOR_NON_H
IMAGE_SET -> BG_COLOR_IMAGE_SET
COSPLAY -> BG_COLOR_COSPLAY
ASIAN_PORN -> BG_COLOR_ASIAN_PORN
MISC -> BG_COLOR_MISC
else -> BG_COLOR_UNKNOWN
}.toInt()
val color = if (Settings.harmonizeCategoryColor) {
val primaryContainer = MaterialTheme.colorScheme.primaryContainer.toArgb()
harmonizeWithRole(primaryContainer, primary)
val primary = Color(
when (category) {
DOUJINSHI -> BG_COLOR_DOUJINSHI
MANGA -> BG_COLOR_MANGA
ARTIST_CG -> BG_COLOR_ARTIST_CG
GAME_CG -> BG_COLOR_GAME_CG
WESTERN -> BG_COLOR_WESTERN
NON_H -> BG_COLOR_NON_H
IMAGE_SET -> BG_COLOR_IMAGE_SET
COSPLAY -> BG_COLOR_COSPLAY
ASIAN_PORN -> BG_COLOR_ASIAN_PORN
MISC -> BG_COLOR_MISC
else -> BG_COLOR_UNKNOWN
}.toInt(),
)
return if (Settings.harmonizeCategoryColor.value) {
val primaryContainer = MaterialTheme.colorScheme.primaryContainer
mergeColor(primaryContainer, primary)
} else {
primary
}
return Color(color)
}
val categoryTextColor = Color(0xffe6e0e9)
@ReadOnlyComposable
@Composable
fun getCategoryTextColor(color: Color) = if (isSystemInDarkTheme() == lstarFromArgb(color.toArgb()) > 70) {
MaterialTheme.colorScheme.surface
} else {
LocalContentColor.current
}
val favoriteIconColor = Color(0xffff3040)
fun signOut() {
EhCookieStore.signOut()
Settings.avatar = null
Settings.displayName = null
Settings.gallerySite = EhUrl.SITE_E
Settings.needSignIn = true
EhCookieStore.removeAllCookies()
Settings.displayName.value = null
Settings.hasSignedIn.value = false
Settings.gallerySite.value = EhUrl.SITE_E
Settings.needSignIn.value = true
}
fun getSuitableTitle(gi: GalleryInfo): String {
return if (Settings.showJpnTitle) {
if (gi.titleJpn.isNullOrEmpty()) gi.title else gi.titleJpn
} else {
if (gi.title.isNullOrEmpty()) gi.titleJpn else gi.title
}.orEmpty()
}
fun getSuitableTitle(gi: GalleryInfo): String = if (Settings.showJpnTitle.value) {
if (gi.titleJpn.isNullOrEmpty()) gi.title else gi.titleJpn
} else {
if (gi.title.isNullOrEmpty()) gi.titleJpn else gi.title
}.orEmpty()
fun extractTitle(fullTitle: String?): String? {
var title: String = fullTitle ?: return null
title = PATTERN_TITLE_PREFIX.matcher(title).replaceFirst("")
title = PATTERN_TITLE_SUFFIX.matcher(title).replaceFirst("")
title = PATTERN_TITLE_PREFIX.replaceFirst(title, "")
title = PATTERN_TITLE_SUFFIX.replaceFirst(title, "")
// Sometimes title is combined by romaji and english translation.
// Only need romaji.
// TODO But not sure every '|' means that
val index = title.indexOf('|')
if (index >= 0) {
title = title.substring(0, index)
}
return title.ifEmpty { null }
return title.substringBeforeLast('|').trim().ifEmpty { null }
}
fun handleThumbUrlResolution(url: String?): String? {
if (null == url) {
return null
}
val resolution = when (Settings.thumbResolution) {
0 -> return url
1 -> "250"
2 -> "300"
else -> return url
}
val index1 = url.lastIndexOf('_')
val index2 = url.lastIndexOf('.')
return if (index1 >= 0 && index2 >= 0 && index1 < index2) {
url.substring(0, index1 + 1) + resolution + url.substring(index2)
} else {
url
context(ctx: Context)
suspend fun downloadArchive(galleryDetail: GalleryDetail, archive: Archive) {
val gid = galleryDetail.gid
EhEngine.downloadArchive(gid, galleryDetail.token, archive.res, archive.isHath)?.let {
val uri = it.toUri()
val intent = Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(uri, "application/zip")
}
val name = "$gid-${getSuitableTitle(galleryDetail)}.zip"
try {
ctx.startActivity(intent)
withUIContext { addTextToClipboard(name, true) }
} catch (_: ActivityNotFoundException) {
val r = DownloadManager.Request(uri)
r.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
AppConfig.APP_DIRNAME + "/" + FileUtils.sanitizeFilename(name),
)
r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
downloadManager.enqueue(r)
}
if (Settings.archiveMetadata.value) {
SpiderDen(galleryDetail).apply {
initDownloadDir()
writeComicInfo()
}
}
}
}
}
@@ -1,14 +0,0 @@
package com.hippo.ehviewer.client.data
interface AbstractDownloadInfo {
var state: Int
var legacy: Int
var time: Long
var label: String?
var position: Int
var speed: Long
var remaining: Long
var finished: Int
var downloaded: Int
var total: Int
}
@@ -1,90 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
@Parcelize
@Serializable
@Entity(tableName = "GALLERIES")
data class BaseGalleryInfo(
@PrimaryKey
@ColumnInfo(name = "GID")
override var gid: Long = 0,
@ColumnInfo(name = "TOKEN")
override var token: String? = null,
@ColumnInfo(name = "TITLE")
override var title: String? = null,
@ColumnInfo(name = "TITLE_JPN")
override var titleJpn: String? = null,
@ColumnInfo(name = "THUMB")
override var thumbKey: String? = null,
@ColumnInfo(name = "CATEGORY")
override var category: Int = 0,
@ColumnInfo(name = "POSTED")
override var posted: String? = null,
@ColumnInfo(name = "UPLOADER")
override var uploader: String? = null,
@Ignore
override var disowned: Boolean = false,
@ColumnInfo(name = "RATING")
override var rating: Float = 0f,
@Ignore
override var rated: Boolean = false,
@Ignore
override var simpleTags: ArrayList<String>? = null,
@Ignore
override var pages: Int = 0,
@Ignore
override var thumbWidth: Int = 0,
@Ignore
override var thumbHeight: Int = 0,
@ColumnInfo(name = "SIMPLE_LANGUAGE")
override var simpleLanguage: String? = null,
@ColumnInfo(name = "FAVORITE_SLOT")
override var favoriteSlot: Int = NOT_FAVORITED,
@Ignore
override var favoriteName: String? = null,
@Ignore
override var favoriteNote: String? = null,
) : GalleryInfo, Parcelable {
constructor() : this(0)
}
@@ -15,24 +15,27 @@
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.addQueryParameter
import com.hippo.ehviewer.client.addQueryParameterIfNotBlank
import com.hippo.ehviewer.client.ehUrl
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
@Parcelize
@Serializable
data class FavListUrlBuilder(
val favCat: Int = FAV_CAT_ALL,
val keyword: String? = null,
var jumpTo: String? = null,
var prev: String? = null,
var next: String? = null,
) : Parcelable {
private var jumpTo: String? = null,
private var prev: String? = null,
private var next: String? = null,
) {
val isLocal
get() = favCat == FAV_CAT_LOCAL
fun setIndex(index: String?, isNext: Boolean) {
next = index.takeIf { isNext }
prev = index.takeUnless { isNext }
jumpTo = null
}
fun build() = ehUrl(EhUrl.FAV_PATH) {
@@ -15,9 +15,14 @@
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.S_LANGS
import kotlinx.parcelize.Parcelize
import androidx.compose.ui.util.fastFlatMap
import com.ehviewer.core.model.GalleryDetail
import com.ehviewer.core.model.GalleryInfo
import com.ehviewer.core.model.GalleryTagGroup
import com.ehviewer.core.model.PowerStatus
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhFilter
private val LANGUAGES = arrayOf(
"English",
@@ -36,28 +41,27 @@ private val LANGUAGES = arrayOf(
"Dutch",
)
@Parcelize
data class GalleryDetail(
val galleryInfo: BaseGalleryInfo = BaseGalleryInfo(),
var apiUid: Long = -1L,
var apiKey: String? = null,
var torrentCount: Int = 0,
var torrentUrl: String? = null,
var archiveUrl: String? = null,
var parent: String? = null,
var newerVersions: ArrayList<BaseGalleryInfo> = ArrayList(),
var visible: String? = null,
var language: String? = null,
var size: String? = null,
var favoriteCount: Int = 0,
var ratingCount: Int = 0,
val tags: List<GalleryTagGroup>,
var comments: GalleryCommentList,
val previewPages: Int,
val previewList: List<GalleryPreview>,
) : GalleryInfo by galleryInfo, Parcelable {
override fun generateSLang() {
val index = LANGUAGES.indexOf(language)
if (index != -1) simpleLanguage = S_LANGS[index]
suspend fun GalleryDetail.fillInfo() {
val index = LANGUAGES.indexOf(language)
if (index != -1) simpleLanguage = GalleryInfo.S_LANGS[index]
simpleTags = tagGroups.fastFlatMap(GalleryTagGroup::tags).map { (text, power, _) ->
if (power == PowerStatus.Weak) "_$text" else text
}
if (favoriteSlot == GalleryInfo.NOT_FAVORITED && EhDB.containLocalFavorites(gid)) {
favoriteSlot = GalleryInfo.LOCAL_FAVORITED
}
}
suspend fun GalleryDetail.filterComments() {
comments = with(comments) {
val scoreThreshold = Settings.commentThreshold.value
copy(
comments = comments.filter {
it.uploader ||
it.score > scoreThreshold &&
!EhFilter.filterCommenter(it.user.orEmpty()) &&
!EhFilter.filterComment(it.comment)
},
)
}
}
@@ -15,49 +15,58 @@
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import androidx.annotation.IntDef
import com.ehviewer.core.database.model.QuickSearch
import com.ehviewer.core.model.GalleryInfo
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.addQueryParameter
import com.hippo.ehviewer.client.addQueryParameterIfNotBlank
import com.hippo.ehviewer.client.ehUrl
import com.hippo.ehviewer.dao.QuickSearch
import com.hippo.ehviewer.ui.main.AdvanceTable
import com.hippo.ehviewer.util.encodeUTF8
import com.hippo.ehviewer.util.toIntOrDefault
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import kotlinx.parcelize.Parcelize
import io.ktor.http.Parameters
import kotlinx.serialization.Serializable
@Parcelize
@Serializable
data class ListUrlBuilder(
@get:Mode @Mode
var mode: Int = MODE_NORMAL,
private var mPrev: String? = null,
var mNext: String? = null,
// Reset to null after initial loading
var mJumpTo: String? = null,
private var prev: String? = null,
private var next: String? = null,
private var jumpTo: String? = null,
private var range: Int = 0,
var category: Int = EhUtils.NONE,
private var mKeyword: String? = null,
var hash: String? = null,
var language: Int = -1,
var advanceSearch: Int = -1,
var minRating: Int = -1,
var pageFrom: Int = -1,
var pageTo: Int = -1,
var imagePath: String? = null,
var isUseSimilarityScan: Boolean = false,
var isOnlySearchCovers: Boolean = false,
) : Parcelable {
) {
fun setIndex(index: String?, isNext: Boolean = true) {
mNext = index?.takeIf { isNext }
mPrev = index?.takeUnless { isNext }
fun setIndex(index: String, isNext: Boolean = true) {
next = index.takeIf { isNext }
prev = index.takeUnless { isNext }
range = 0
jumpTo = null
}
fun setJumpTo(jumpTo: Int) {
mJumpTo = jumpTo.takeUnless { it == 0 }?.toString()
var page: Int
get() = (jumpTo?.toInt() ?: 0) + 1
set(value) {
jumpTo = (value - 1).takeUnless { it == 0 }?.toString()
}
fun setRange(to: Int) {
range = to
prev = null
next = null
jumpTo = null
}
fun setSeek(date: String) {
setIndex("2", true)
jumpTo = date
}
var keyword: String?
@@ -66,29 +75,27 @@ data class ListUrlBuilder(
mKeyword = keyword
}
constructor(q: QuickSearch) : this() {
mode = q.mode
this.category = q.category
mKeyword = q.keyword
advanceSearch = q.advanceSearch
minRating = q.minRating
pageFrom = q.pageFrom
pageTo = q.pageTo
mNext = q.name.substringAfterLast('@', "").takeIf { it.isNotEmpty() }
}
constructor(q: QuickSearch) : this(
mode = q.mode,
category = q.category,
mKeyword = q.keyword,
advanceSearch = q.advanceSearch,
minRating = q.minRating,
pageFrom = q.pageFrom,
pageTo = q.pageTo,
next = q.name.substringAfterLast('@', "").ifEmpty { null },
)
fun toQuickSearch(name: String): QuickSearch {
return QuickSearch(
name = name,
mode = mode,
category = category,
keyword = mKeyword,
advanceSearch = advanceSearch,
minRating = minRating,
pageFrom = pageFrom,
pageTo = pageTo,
)
}
fun toQuickSearch(name: String) = QuickSearch(
name = name,
mode = mode,
category = category,
keyword = mKeyword,
advanceSearch = advanceSearch,
minRating = minRating,
pageFrom = pageFrom,
pageTo = pageTo,
)
fun equalsQuickSearch(q: QuickSearch?): Boolean {
if (null == q) {
@@ -116,262 +123,115 @@ data class ListUrlBuilder(
}
}
/**
* @param query xxx=yyy&mmm=nnn
*/
constructor(query: String?) : this() {
// TODO page
if (query.isNullOrEmpty()) {
return
}
val queries = query.split('&')
var category = 0
var keyword: String? = null
var enableAdvanceSearch = false
var advanceSearch = 0
var enableMinRating = false
var minRating = -1
var enablePage = false
var pageFrom = -1
var pageTo = -1
for (str in queries) {
val index = str.indexOf('=')
if (index < 0) {
continue
constructor(params: Parameters) : this(
prev = params["prev"],
next = params["next"],
jumpTo = params["seek"],
range = params["range"]?.toIntOrNull() ?: 0,
category = params["f_cats"]?.toIntOrNull()?.let(EhUtils::invCategory) ?: EhUtils.NONE,
mKeyword = params["f_search"],
) {
if (params["advsearch"] == "1") {
advanceSearch = 0
if (params["f_sh"] == "on") {
advanceSearch = advanceSearch or AdvanceTable.SH
}
val key = str.substring(0, index)
val value = str.substring(index + 1)
when (key) {
"f_cats" -> {
val cats = value.toIntOrDefault(EhUtils.ALL_CATEGORY)
category = category or (cats.inv() and EhUtils.ALL_CATEGORY)
}
"f_doujinshi" -> if ("1" == value) {
category = category or EhUtils.DOUJINSHI
}
"f_manga" -> if ("1" == value) {
category = category or EhUtils.MANGA
}
"f_artistcg" -> if ("1" == value) {
category = category or EhUtils.ARTIST_CG
}
"f_gamecg" -> if ("1" == value) {
category = category or EhUtils.GAME_CG
}
"f_western" -> if ("1" == value) {
category = category or EhUtils.WESTERN
}
"f_non-h" -> if ("1" == value) {
category = category or EhUtils.NON_H
}
"f_imageset" -> if ("1" == value) {
category = category or EhUtils.IMAGE_SET
}
"f_cosplay" -> if ("1" == value) {
category = category or EhUtils.COSPLAY
}
"f_asianporn" -> if ("1" == value) {
category = category or EhUtils.ASIAN_PORN
}
"f_misc" -> if ("1" == value) {
category = category or EhUtils.MISC
}
"f_search" -> try {
keyword = URLDecoder.decode(value, "utf-8")
} catch (e: UnsupportedEncodingException) {
// Ignore
} catch (_: IllegalArgumentException) {
}
"advsearch" -> if ("1" == value) {
enableAdvanceSearch = true
}
"f_sh" -> if ("on" == value) {
advanceSearch = advanceSearch or AdvanceTable.SH
}
"f_sto" -> if ("on" == value) {
advanceSearch = advanceSearch or AdvanceTable.STO
}
"f_sfl" -> if ("on" == value) {
advanceSearch = advanceSearch or AdvanceTable.SFL
}
"f_sfu" -> if ("on" == value) {
advanceSearch = advanceSearch or AdvanceTable.SFU
}
"f_sft" -> if ("on" == value) {
advanceSearch = advanceSearch or AdvanceTable.SFT
}
"f_sr" -> if ("on" == value) {
enableMinRating = true
}
"f_srdd" -> minRating = value.toIntOrDefault(-1)
"f_sp" -> if ("on" == value) {
enablePage = true
}
"f_spf" -> pageFrom = value.toIntOrDefault(-1)
"f_spt" -> pageTo = value.toIntOrDefault(-1)
"f_shash" -> hash = value
if (params["f_sto"] == "on") {
advanceSearch = advanceSearch or AdvanceTable.STO
}
}
this.category = category
mKeyword = keyword
if (enableAdvanceSearch) {
this.advanceSearch = advanceSearch
if (enableMinRating) {
this.minRating = minRating
} else {
this.minRating = -1
if (params["f_sfl"] == "on") {
advanceSearch = advanceSearch or AdvanceTable.SFL
}
if (enablePage) {
this.pageFrom = pageFrom
this.pageTo = pageTo
} else {
this.pageFrom = -1
this.pageTo = -1
if (params["f_sfu"] == "on") {
advanceSearch = advanceSearch or AdvanceTable.SFU
}
} else {
this.advanceSearch = -1
if (params["f_sft"] == "on") {
advanceSearch = advanceSearch or AdvanceTable.SFT
}
minRating = params["f_srdd"]?.toIntOrNull() ?: -1
pageFrom = params["f_spf"]?.toIntOrNull() ?: -1
pageTo = params["f_spt"]?.toIntOrNull() ?: -1
}
}
fun build(): String {
return when (mode) {
MODE_NORMAL, MODE_SUBSCRIPTION -> ehUrl(EhUrl.WATCHED_PATH.takeIf { mode == MODE_SUBSCRIPTION }) {
if (category > 0) {
addQueryParameter("f_cats", (category.inv() and EhUtils.ALL_CATEGORY).toString())
}
val query = mKeyword?.let {
val index = Settings.languageFilter.value
GalleryInfo.S_LANG_TAGS.getOrNull(index)?.let { lang ->
"$it $lang"
} ?: it
}
addQueryParameterIfNotBlank("f_search", query)
addQueryParameterIfNotBlank("f_shash", hash)
addQueryParameterIfNotBlank("prev", mPrev)
addQueryParameterIfNotBlank("next", mNext)
addQueryParameterIfNotBlank("seek", mJumpTo)
// Advance search
if (advanceSearch > 0 || minRating > 0 || pageFrom > 0 || pageTo > 0) {
addQueryParameter("advsearch", "1")
if (advanceSearch and AdvanceTable.SH != 0) {
addQueryParameter("f_sh", "on")
}
if (advanceSearch and AdvanceTable.STO != 0) {
addQueryParameter("f_sto", "on")
}
if (advanceSearch and AdvanceTable.SFL != 0) {
addQueryParameter("f_sfl", "on")
}
if (advanceSearch and AdvanceTable.SFU != 0) {
addQueryParameter("f_sfu", "on")
}
if (advanceSearch and AdvanceTable.SFT != 0) {
addQueryParameter("f_sft", "on")
}
// Set min star
if (minRating > 0) {
addQueryParameter("f_sr", "on")
addQueryParameter("f_srdd", "$minRating")
}
// Pages
if (pageFrom > 0 || pageTo > 0) {
addQueryParameter("f_sp", "on")
addQueryParameterIfNotBlank(
"f_spf",
pageFrom.takeIf { it > 0 }?.toString(),
)
addQueryParameterIfNotBlank(
"f_spt",
pageTo.takeIf { it > 0 }?.toString(),
)
}
}
}.toString()
MODE_UPLOADER -> {
buildString {
append(EhUrl.host)
mKeyword?.let {
append("uploader/").append(encodeUTF8(it))
}
mPrev?.let {
append("?prev=").append(it)
}
mNext?.let {
append("?next=").append(it)
}
mJumpTo?.let {
append("&seek=").append(it)
fun build(): String = when (mode) {
MODE_NORMAL, MODE_SUBSCRIPTION -> ehUrl(EhUrl.WATCHED_PATH.takeIf { mode == MODE_SUBSCRIPTION }) {
val category = if (!Settings.hasSignedIn.value && category <= 0) EhUtils.NON_H else category
if (category > 0) {
addQueryParameter("f_cats", "${EhUtils.invCategory(category)}")
}
val query = mKeyword?.let { keyword ->
if (language == -1 || "gid:" in keyword || "l:" in keyword || "language:" in keyword) {
keyword
} else {
val tag = GalleryInfo.S_LANG_TAGS[language]
if (keyword.isNotEmpty()) {
"$tag $keyword"
} else {
tag
}
}
}
MODE_TAG -> {
buildString {
append(EhUrl.host)
mKeyword?.let {
append("tag/").append(encodeUTF8(it))
}
mPrev?.let {
append("?prev=").append(it)
}
mNext?.let {
append("?next=").append(it)
}
mJumpTo?.let {
append("&seek=").append(it)
}
addQueryParameterIfNotBlank("f_search", query)
addQueryParameterIfNotBlank("prev", prev)
addQueryParameterIfNotBlank("next", next)
addQueryParameterIfNotBlank("seek", jumpTo)
addQueryParameterIfNotBlank("range", range.takeIf { it > 0 }?.toString())
// Advance search
if (advanceSearch > 0 || minRating > 0 || pageFrom > 0 || pageTo > 0) {
addQueryParameter("advsearch", "1")
if (advanceSearch and AdvanceTable.SH != 0) {
addQueryParameter("f_sh", "on")
}
if (advanceSearch and AdvanceTable.STO != 0) {
addQueryParameter("f_sto", "on")
}
if (advanceSearch and AdvanceTable.SFL != 0) {
addQueryParameter("f_sfl", "on")
}
if (advanceSearch and AdvanceTable.SFU != 0) {
addQueryParameter("f_sfu", "on")
}
if (advanceSearch and AdvanceTable.SFT != 0) {
addQueryParameter("f_sft", "on")
}
// Set min star
if (minRating > 0) {
addQueryParameter("f_srdd", "$minRating")
}
// Pages
if (pageFrom > 0 || pageTo > 0) {
addQueryParameterIfNotBlank(
"f_spf",
pageFrom.takeIf { it > 0 }?.toString(),
)
addQueryParameterIfNotBlank(
"f_spt",
pageTo.takeIf { it > 0 }?.toString(),
)
}
}
MODE_WHATS_HOT -> EhUrl.popularUrl
MODE_IMAGE_SEARCH -> EhUrl.imageSearchUrl
MODE_TOPLIST -> {
buildString {
append(EhUrl.HOST_E)
append("toplist.php?tl=")
append(mKeyword!!)
mJumpTo?.let {
append("&p=").append(it)
}
}
}
else -> throw IllegalStateException("Unexpected value: $mode")
}.buildString()
MODE_UPLOADER, MODE_TAG -> {
val path = if (mode == MODE_UPLOADER) "uploader" else "tag"
ehUrl(listOf(path, requireNotNull(mKeyword))) {
addQueryParameterIfNotBlank("prev", prev)
addQueryParameterIfNotBlank("next", next)
addQueryParameterIfNotBlank("seek", jumpTo)
addQueryParameterIfNotBlank("range", range.takeIf { it > 0 }?.toString())
}.buildString()
}
MODE_WHATS_HOT -> EhUrl.popularUrl
MODE_IMAGE_SEARCH -> ehUrl {
addQueryParameter("f_shash", requireNotNull(hash))
}.buildString()
MODE_TOPLIST -> ehUrl("toplist.php", EhUrl.DOMAIN_E) {
addQueryParameter("tl", requireNotNull(mKeyword))
addQueryParameterIfNotBlank("p", jumpTo)
}.buildString()
else -> throw IllegalStateException("Unexpected value: $mode")
}
@IntDef(
MODE_NORMAL,
MODE_UPLOADER,
MODE_TAG,
MODE_WHATS_HOT,
MODE_IMAGE_SEARCH,
MODE_SUBSCRIPTION,
MODE_TOPLIST,
)
@Retention(AnnotationRetention.SOURCE)
private annotation class Mode
companion object {
const val MODE_NORMAL = 0x0
const val MODE_UPLOADER = 0x1
@@ -0,0 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.ehviewer.core.i18n.R
class CloudflareBypassException : EhException(R.string.cloudflare_bypass_failed)
@@ -0,0 +1,48 @@
package com.hippo.ehviewer.client.exception
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.CborArray
@Serializable
sealed interface EhError {
@CborArray
@Serializable
@SerialName("NoHits")
data object NoHits : EhError
@CborArray
@Serializable
@SerialName("NoWatched")
data object NoWatched : EhError
@CborArray
@Serializable
@SerialName("NeedLogin")
data object NeedLogin : EhError
@CborArray
@Serializable
@SerialName("NoHathClient")
data object NoHathClient : EhError
@CborArray
@Serializable
@SerialName("InsufficientFunds")
data object InsufficientFunds : EhError
@CborArray
@Serializable
@SerialName("GalleryUnavailable")
data class GalleryUnavailable(val message: String) : EhError
@CborArray
@Serializable
@SerialName("IpBanned")
data class IpBanned(val message: String) : EhError
@CborArray
@Serializable
@SerialName("Error")
data class Error(val message: String) : EhError
}
@@ -0,0 +1,13 @@
package com.hippo.ehviewer.client.exception
import androidx.annotation.StringRes
import com.ehviewer.core.i18n.R
sealed class FatalException : EhException {
constructor(message: String) : super(message)
constructor(@StringRes messageId: Int) : super(messageId)
}
class IpBannedException(message: String) : FatalException(message)
class InsufficientGpException : FatalException(R.string.insufficient_gp)
class QuotaExceededException : FatalException(R.string.error_509)
@@ -1,5 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
class InsufficientFundsException : EhException(R.string.insufficient_funds)
@@ -15,4 +15,6 @@
*/
package com.hippo.ehviewer.client.exception
class NoHAtHClientException(detailMessage: String?) : EhException(detailMessage)
import com.ehviewer.core.i18n.R
class NoHathClientException() : EhException(R.string.download_archive_failure_no_hath)
@@ -0,0 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.ehviewer.core.i18n.R
class NoHitsFoundException : EhException(R.string.gallery_list_empty_hit)
@@ -1,5 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
class NotLoggedInException : EhException(R.string.sign_in_first)
@@ -1,5 +0,0 @@
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
class QuotaExceededException : EhException(R.string.error_509)
@@ -15,77 +15,43 @@
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.InsufficientFundsException
import com.hippo.ehviewer.client.exception.NoHAtHClientException
import org.jsoup.Jsoup
import arrow.core.Either.Companion.catch
import arrow.core.getOrElse
import arrow.core.partially3
import com.hippo.ehviewer.client.exception.ParseException
import java.nio.ByteBuffer
import kotlinx.serialization.Serializable
object ArchiveParser {
private val PATTERN_ARCHIVE_URL =
Regex("<strong>(.*)</strong>.*<a href=\"([^\"]*)\">Click Here To Start Downloading</a>")
private val PATTERN_CURRENT_FUNDS =
Regex("<p>([\\d,]+) GP \\[[^]]*] &nbsp; ([\\d,]+) Credits \\[[^]]*]</p>")
private val PATTERN_HATH_FORM =
Regex("<form id=\"hathdl_form\" action=\"[^\"]*?or=([^=\"]*?)\" method=\"post\">")
private val PATTERN_HATH_ARCHIVE =
Regex("<p><a href=\"[^\"]*\" onclick=\"return do_hathdl\\('([0-9]+|org)'\\)\">([^<]+)</a></p>\\s*<p>([\\w. ]+)</p>\\s*<p>([\\w. ]+)</p>")
private const val ERROR_NEED_HATH_CLIENT =
"You must have a H@H client assigned to your account to use this feature."
private const val ERROR_INSUFFICIENT_FUNDS =
"You do not have enough funds to download this archive."
fun parse(body: String): Result {
val m = PATTERN_HATH_FORM.find(body)!!
val paramOr = m.groupValues[1]
val archiveList = ArrayList<Archive>()
Jsoup.parse(body).select("#db>div>div").forEach { element ->
if (element.childrenSize() > 0 && !element.attr("style").contains("color:#CCCCCC")) {
runCatching {
val res = element.selectFirst("form>input")!!.attr("value")
val name = element.selectFirst("form>div>input")!!.attr("value")
val size = element.selectFirst("p>strong")!!.text()
val cost = element.selectFirst("div>strong")!!.text().replace(",", "")
Archive(res, name, size, cost, false)
}.onSuccess {
archiveList.add(it)
}.onFailure {
it.printStackTrace()
}
}
fun parse(body: ByteBuffer, maybeFunds: Funds?) = catch {
if (maybeFunds != null) {
val archives = unmarshalParsingAs<List<Archive>>(body, ::parseArchives)
Result(archives, maybeFunds)
} else {
unmarshalParsingAs<Result>(body, ::parseArchives.partially3(true))
}
PATTERN_HATH_ARCHIVE.findAll(body).forEach { matchResult ->
val (res, name, size, cost) = matchResult.groupValues.slice(1..4)
.map { ParserUtils.trim(it) }
val item = Archive(res, name, size, cost, true)
archiveList.add(item)
}
val result = Result(paramOr, archiveList, null)
PATTERN_CURRENT_FUNDS.find(body)?.groupValues?.run {
val fundsGP = ParserUtils.parseInt(get(1), 0)
val fundsC = ParserUtils.parseInt(get(2), 0)
val funds = HomeParser.Funds(fundsGP, fundsC)
result.funds = funds
}
return result
}.getOrElse {
throw ParseException("Can't parse archive list", it)
}
fun parseArchiveUrl(body: String): String? {
if (body.contains(ERROR_NEED_HATH_CLIENT)) {
throw NoHAtHClientException("No H@H client")
} else if (body.contains(ERROR_INSUFFICIENT_FUNDS)) {
throw InsufficientFundsException()
}
return Jsoup.parse(body).selectFirst("#continue>a[href]")
?.let { it.attr("href") + "?start=1" }
// TODO: Check more errors
fun parseArchiveUrl(body: ByteBuffer) = catch {
unmarshalParsingAs<String?>(body, ::parseArchiveUrl)
}.getOrElse {
throw ParseException("Can't parse archive url", it)
}
class Archive(
val res: String,
val name: String,
val size: String,
val cost: String,
val isHAtH: Boolean,
)
class Result(val paramOr: String?, val archiveList: List<Archive>, var funds: HomeParser.Funds?)
@Serializable
data class Result(val archiveList: List<Archive>, val funds: Funds)
}
@Serializable
class Archive(
val res: String,
val name: String,
val size: String,
val cost: String,
val isHath: Boolean,
)
private external fun parseArchives(body: ByteBuffer, size: Int = body.limit(), parseFunds: Boolean = false): Int
private external fun parseArchiveUrl(body: ByteBuffer, size: Int = body.limit()): Int
@@ -17,10 +17,10 @@
*/
package com.hippo.ehviewer.client.parser
import org.jsoup.Jsoup
import java.nio.ByteBuffer
object EventPaneParser {
fun parse(body: String): String? {
return Jsoup.parse(body).getElementById("eventpane")?.html()
}
private external fun parseEventPane(body: ByteBuffer, size: Int = body.limit()): Int
fun parse(body: ByteBuffer): String? = unmarshalParsingAs(body, ::parseEventPane)
}
@@ -1,5 +1,6 @@
package com.hippo.ehviewer.client.parser
import com.ehviewer.core.util.unescapeXml
import com.hippo.ehviewer.client.exception.ParseException
import java.nio.ByteBuffer
import kotlinx.serialization.Serializable
@@ -17,13 +18,16 @@ data class FavParserResult(
}
object FavoritesParser {
fun parse(body: ByteBuffer): FavParserResult {
return runSuspendCatching {
unmarshalParsingAs<FavParserResult>(body, ::parseFav)
}.getOrElse {
throw ParseException("Parse favorites error", it)
}
private val NoteRegex = Regex("<textarea[^>]*>([^<]*)</textarea>")
fun parse(body: ByteBuffer): FavParserResult = runSuspendCatching {
unmarshalParsingAs<FavParserResult>(body, ::parseFav)
}.getOrElse {
throw ParseException("Parse favorites error", it)
}
fun parseNote(body: String) = NoteRegex.find(body)?.run { groupValues[1].unescapeXml() }
?: throw ParseException("Can't parse favorite note")
}
private external fun parseFav(body: ByteBuffer, limit: Int = body.limit()): Int
@@ -1,35 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.exception.NotLoggedInException
import com.hippo.ehviewer.util.ExceptionUtils
import org.jsoup.Jsoup
object ForumsParser {
fun parse(body: String): String {
return runCatching {
val d = Jsoup.parse(body, EhUrl.URL_FORUMS)
val userlinks = d.getElementById("userlinks")
val child = userlinks!!.child(0).child(0).child(0)
child.attr("href")
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
throw NotLoggedInException()
}
}
}
@@ -15,12 +15,11 @@
*/
package com.hippo.ehviewer.client.parser
import com.ehviewer.core.model.GalleryInfo
import com.ehviewer.core.util.unescapeXml
import com.hippo.ehviewer.client.EhUtils.getCategory
import com.hippo.ehviewer.client.EhUtils.handleThumbUrlResolution
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.getThumbKey
import com.hippo.ehviewer.client.parseAs
import com.hippo.ehviewer.util.unescapeXml
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -32,7 +31,7 @@ object GalleryApiParser {
title = item.title.unescapeXml()
titleJpn = item.titleJpn.unescapeXml()
category = getCategory(item.category)
thumbKey = getThumbKey(handleThumbUrlResolution(item.thumb)!!)
thumbKey = getThumbKey(item.thumb)
uploader = item.uploader?.unescapeXml()
posted = ParserUtils.formatDate(item.posted * 1000)
rating = item.rating
@@ -60,6 +59,6 @@ object GalleryApiParser {
@SerialName("filecount")
val pages: Int,
val rating: Float,
val tags: ArrayList<String>,
val tags: List<String>,
)
}
@@ -15,474 +15,40 @@
*/
package com.hippo.ehviewer.client.parser
import arrow.core.unzip
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhFilter
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.EhUtils.getCategory
import com.hippo.ehviewer.client.EhUtils.handleThumbUrlResolution
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryComment
import com.hippo.ehviewer.client.data.GalleryCommentList
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
import com.hippo.ehviewer.client.data.GalleryPreview
import com.hippo.ehviewer.client.data.GalleryTagGroup
import com.hippo.ehviewer.client.data.LargeGalleryPreview
import com.hippo.ehviewer.client.data.NormalGalleryPreview
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.exception.OffensiveException
import arrow.core.Either
import arrow.core.getOrElse
import com.ehviewer.core.model.GalleryCommentList
import com.ehviewer.core.model.GalleryDetail
import com.ehviewer.core.model.GalleryPreviewList
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.client.exception.PiningException
import com.hippo.ehviewer.client.getThumbKey
import com.hippo.ehviewer.util.ExceptionUtils
import com.hippo.ehviewer.util.toFloatOrDefault
import com.hippo.ehviewer.util.toIntOrDefault
import com.hippo.ehviewer.util.trimAnd
import com.hippo.ehviewer.util.unescapeXml
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.Locale
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.nodes.Node
import org.jsoup.select.Elements
import org.jsoup.select.NodeTraversor
import org.jsoup.select.NodeVisitor
import java.nio.ByteBuffer
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.CborArray
object GalleryDetailParser {
private val PATTERN_ERROR = Regex("<div class=\"d\">\n<p>([^<]+)</p>")
private val PATTERN_DETAIL = Regex(
"var gid = (\\d+);.+?var token = \"([a-f0-9]+)\";.+?var apiuid = ([\\-\\d]+);.+?var apikey = \"([a-f0-9]+)\";",
RegexOption.DOT_MATCHES_ALL,
)
private val PATTERN_TORRENT =
Regex("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Torrent Download[^<]+(\\d+)[^<]+</a")
private val PATTERN_ARCHIVE =
Regex("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Archive Download</a>")
private val PATTERN_COVER = Regex("width:(\\d+)px; height:(\\d+)px.+?url\\((.+?)\\)")
private val PATTERN_PAGES =
Regex("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>")
private val PATTERN_PREVIEW_PAGES =
Regex("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?&gt;(?:</a>)?</td>")
private val PATTERN_NORMAL_PREVIEW =
Regex("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"")
private val PATTERN_LARGE_PREVIEW =
Regex("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"")
private val PATTERN_NEWER_DATE = Regex(", added (.+?)<br />")
private val PATTERN_FAVORITE_SLOT =
Regex("/fav.png\\); background-position:0px -(\\d+)px")
private val EMPTY_GALLERY_COMMENT_LIST = GalleryCommentList(emptyList(), false)
private val WEB_COMMENT_DATE_FORMAT = DateTimeFormatter
.ofPattern("dd MMMM yyyy, HH:mm", Locale.US).withZone(ZoneOffset.UTC)
private const val OFFENSIVE_STRING =
"<p>(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)</p>"
private const val PINING_STRING = "<p>This gallery is pining for the fjords.</p>"
suspend fun parse(body: String): GalleryDetail {
if (body.contains(OFFENSIVE_STRING)) {
throw OffensiveException()
}
if (body.contains(PINING_STRING)) {
throw PiningException()
}
// Error info
PATTERN_ERROR.find(body)?.run { throw EhException(groupValues[1]) }
val document = Jsoup.parse(body)
val galleryDetail = GalleryDetail(
tags = parseTagGroups(document),
comments = parseComments(document),
previewPages = parsePreviewPages(body),
previewList = parsePreviewList(body).first,
)
parseDetail(galleryDetail, document, body)
// Generate simpleLanguage for local favorites
galleryDetail.generateSLang()
return galleryDetail
fun parse(body: ByteBuffer) = Either.catch {
unmarshalParsingAs<Result>(body, ::nativeParse)
}.getOrElse {
throw ParseException("Failed to parse gallery detail", it)
}
private suspend fun parseDetail(gd: GalleryDetail, d: Document, body: String) {
PATTERN_DETAIL.find(body)?.apply {
gd.gid = groupValues[1].toLongOrNull() ?: -1L
gd.token = groupValues[2]
gd.apiUid = groupValues[3].toLongOrNull() ?: -1L
gd.apiKey = groupValues[4]
} ?: throw ParseException("Can't parse gallery detail")
if (gd.gid == -1L) {
throw ParseException("Can't parse gallery detail")
}
PATTERN_TORRENT.find(body)?.run {
gd.torrentUrl = groupValues[1].trim().unescapeXml()
gd.torrentCount = groupValues[2].toIntOrNull() ?: 0
}
PATTERN_ARCHIVE.find(body)?.run {
gd.archiveUrl = groupValues[1].trim().unescapeXml()
}
try {
val gm = d.getElementsByClass("gm")[0]
// Thumb url
gm.getElementById("gd1")?.child(0)?.attr("style")?.trim()?.let {
gd.thumbKey = getThumbKey(
PATTERN_COVER.find(it)?.run {
handleThumbUrlResolution(groupValues[3])
}!!,
)
}
gd.title = gm.getElementById("gn")?.text()?.trim()
gd.titleJpn = gm.getElementById("gj")?.text()?.trim()
// Category
try {
val gdc = gm.getElementById("gdc")!!
var ce = gdc.getElementsByClass("cn").first()
if (ce == null) {
ce = gdc.getElementsByClass("cs").first()
}
gd.category = getCategory(ce!!.text())
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
gd.category = EhUtils.UNKNOWN
}
// Uploader
val gdn = gm.getElementById("gdn")
if (null != gdn) {
gd.disowned = gdn.attr("style").contains("opacity:0.5")
gd.uploader = gdn.text().trim()
} else {
gd.uploader = ""
}
val gdd = gm.getElementById("gdd")
gd.posted = ""
gd.parent = ""
gd.visible = ""
gd.size = ""
gd.pages = 0
gd.favoriteCount = 0
val es = gdd!!.child(0).child(0).children()
es.forEach {
parseDetailInfo(gd, it)
}
// Rating count
val ratingCount = gm.getElementById("rating_count")
if (null != ratingCount) {
gd.ratingCount = ratingCount.text().trim().toIntOrDefault(0)
} else {
gd.ratingCount = 0
}
// Rating
val ratingLabel = gm.getElementById("rating_label")
if (null != ratingLabel) {
val ratingStr = ratingLabel.text().trim()
if ("Not Yet Rated" == ratingStr) {
gd.rating = -1.0f
} else {
val index = ratingStr.indexOf(' ')
if (index == -1 || index >= ratingStr.length) {
gd.rating = 0f
} else {
gd.rating = ratingStr.substring(index + 1).toFloatOrDefault(0f)
}
}
} else {
gd.rating = -1.0f
}
// isFavorited
val gdf = gm.getElementById("gdf")
if (gdf != null) {
val favoriteName = gdf.text().trim()
if (favoriteName != "Add to Favorites") {
gd.favoriteName = gdf.text().trim()
PATTERN_FAVORITE_SLOT.find(body)?.run {
gd.favoriteSlot = ((groupValues[1].toIntOrNull() ?: 2) - 2) / 19
}
}
}
if (gd.favoriteSlot == NOT_FAVORITED && EhDB.containLocalFavorites(gd.gid)) {
gd.favoriteSlot = LOCAL_FAVORITED
}
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
throw ParseException("Can't parse gallery detail")
}
// newer version
d.getElementById("gnd")?.run {
val dates = PATTERN_NEWER_DATE.findAll(body).map { it.groupValues[1] }.toList()
select("a").forEachIndexed { index, element ->
val gi = BaseGalleryInfo()
val result = GalleryDetailUrlParser.parse(element.attr("href"))
if (result != null) {
gi.gid = result.gid
gi.token = result.token
gi.title = element.text().trim()
gi.posted = dates[index]
gd.newerVersions.add(gi)
}
}
}
fun parseComments(body: ByteBuffer) = Either.catch {
unmarshalParsingAs<GalleryCommentList>(body, ::nativeParseComments)
}.getOrElse {
throw ParseException("Failed to parse comments", it)
}
private fun parseDetailInfo(gd: GalleryDetail, e: Element) {
val es = e.children()
if (es.size < 2) {
return
}
val key = es[0].text().trim()
val value = es[1].ownText().trim()
if (key.startsWith("Posted")) {
gd.posted = value
} else if (key.startsWith("Parent")) {
val a = es[1].children().first()
if (a != null) {
gd.parent = a.attr("href")
}
} else if (key.startsWith("Visible")) {
gd.visible = value
} else if (key.startsWith("Language")) {
gd.language = value
} else if (key.startsWith("File Size")) {
gd.size = value
} else if (key.startsWith("Length")) {
val index = value.indexOf(' ')
if (index >= 0) {
gd.pages = value.substring(0, index).toIntOrDefault(1)
} else {
gd.pages = 1
}
} else if (key.startsWith("Favorited")) {
when (value) {
"Never" -> gd.favoriteCount = 0
"Once" -> gd.favoriteCount = 1
else -> {
val index = value.indexOf(' ')
if (index == -1) {
gd.favoriteCount = 0
} else {
gd.favoriteCount = value.substring(0, index).toIntOrDefault(0)
}
}
}
}
fun parsePreviews(body: ByteBuffer) = Either.catch {
unmarshalParsingAs<GalleryPreviewList>(body, ::nativeParsePreviews)
}.getOrElse {
throw ParseException("Failed to parse previews", it)
}
private fun parseTagGroup(element: Element): GalleryTagGroup? {
return try {
var nameSpace = element.child(0).text()
// Remove last ':'
nameSpace = nameSpace.substring(0, nameSpace.length - 1)
val group = GalleryTagGroup(nameSpace)
val tags = element.child(1).children()
tags.forEach {
var tag = it.text()
// Sometimes parody tag is followed with '|' and english translate, just remove them
val index = tag.indexOf('|')
if (index >= 0) {
tag = tag.substring(0, index).trim()
}
if (it.className() == "gtw") {
tag = "_$tag" // weak tag
}
group.add(tag)
}
if (group.size > 0) group else null
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
e.printStackTrace()
null
}
}
@Serializable
@CborArray
class Result(val detail: GalleryDetail, val event: String?)
/**
* Parse tag groups with html parser
*/
private fun parseTagGroups(document: Document): List<GalleryTagGroup> {
return try {
val taglist = document.getElementById("taglist")!!
val tagGroups = taglist.child(0).child(0).children()
parseTagGroups(tagGroups)
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
e.printStackTrace()
emptyList()
}
}
private fun parseTagGroups(trs: Elements): List<GalleryTagGroup> {
return try {
trs.mapNotNull { parseTagGroup(it) }
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
e.printStackTrace()
emptyList()
}
}
private suspend fun parseComment(element: Element): GalleryComment? {
return try {
// Id
val a = element.previousElementSibling()
val name = a!!.attr("name")
val id = name trimAnd { substring(1).toInt().toLong() }
// Editable, vote up and vote down
val c4 = element.getElementsByClass("c4").first()
var voteUpAble = false
var voteUpEd = false
var voteDownAble = false
var voteDownEd = false
var editable = false
var uploader = false
if (null != c4) {
if ("Uploader Comment" == c4.text()) {
uploader = true
}
for (e in c4.children()) {
when (e.text()) {
"Vote+" -> {
voteUpAble = true
voteUpEd = e.attr("style").trim().isNotEmpty()
}
"Vote-" -> {
voteDownAble = true
voteDownEd = e.attr("style").trim().isNotEmpty()
}
"Edit" -> editable = true
}
}
}
// Vote state
val c7 = element.getElementsByClass("c7").first()
val voteState = c7?.text()?.trim()
// Score
val c5 = element.getElementsByClass("c5").first()
val score = c5?.children()?.first()?.text()?.trim()?.toIntOrDefault(0) ?: 0
// time
val c3 = element.getElementsByClass("c3").first()
val temp = c3!!.ownText()
val (timeStr, user) = if (temp.endsWith(':')) {
temp.substring("Posted on ".length, temp.length - " by:".length) to c3.child(0).text()
} else {
temp.substring("Posted on ".length) to null
}
val time = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(timeStr)).toEpochMilli()
// comment
val c6 = element.getElementsByClass("c6").first()
// fix underline support
for (e in c6!!.children()) {
if ("span" == e.tagName() && "text-decoration:underline;" == e.attr("style")) {
e.tagName("u")
}
}
val commentHtml = c6.html()
// filter comment
if (!uploader) {
val sEhFilter = EhFilter
if (score <= Settings.commentThreshold || sEhFilter.filterCommenter(user.orEmpty()) || sEhFilter.filterComment(commentHtml)) {
return null
}
}
// last edited
val c8 = element.getElementsByClass("c8").first()
val lastEdited = c8?.children()?.first()?.run { Instant.from(WEB_COMMENT_DATE_FORMAT.parse(text())).toEpochMilli() } ?: 0
GalleryComment(
id, score, editable, voteUpAble, voteUpEd, voteDownAble,
voteDownEd, uploader, voteState, time, user, commentHtml, lastEdited,
)
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
e.printStackTrace()
null
}
}
/**
* Parse comments with html parser
*/
suspend fun parseComments(document: Document): GalleryCommentList {
// Disable pretty print to get comments in raw html
document.outputSettings().prettyPrint(false)
return try {
val cdiv = document.getElementById("cdiv")!!
val c1s = cdiv.getElementsByClass("c1")
val list = c1s.mapNotNull { parseComment(it) }
val chd = cdiv.getElementById("chd")
var hasMore = false
NodeTraversor.traverse(
object : NodeVisitor {
override fun head(node: Node, depth: Int) {
if (node is Element && node.text() == "click to show all") {
hasMore = true
}
}
override fun tail(node: Node, depth: Int) {}
},
chd!!,
)
GalleryCommentList(list, hasMore)
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
e.printStackTrace()
EMPTY_GALLERY_COMMENT_LIST
}
}
/**
* Parse preview pages with regular expressions
*/
fun parsePreviewPages(body: String): Int {
return PATTERN_PREVIEW_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
?: throw ParseException("Parse preview page count error")
}
/**
* Parse pages with regular expressions
*/
fun parsePages(body: String): Int {
return PATTERN_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
?: throw ParseException("Parse pages error")
}
fun parsePreviewList(body: String): Pair<List<GalleryPreview>, List<String>> {
return runCatching { parseNormalPreview(body) }.getOrElse { parseLargePreview(body) }
}
private fun parseLargePreview(body: String): Pair<List<GalleryPreview>, List<String>> {
check(PATTERN_LARGE_PREVIEW.containsMatchIn(body))
return PATTERN_LARGE_PREVIEW.findAll(body).unzip {
val position = it.groupValues[2].toInt() - 1
val imageKey = getThumbKey(it.groupValues[3].trim())
val pageUrl = it.groupValues[1].trim()
LargeGalleryPreview(imageKey, position) to pageUrl
}.run {
first.toList() to second.toList()
}
}
private fun parseNormalPreview(body: String): Pair<List<GalleryPreview>, List<String>> {
check(PATTERN_NORMAL_PREVIEW.containsMatchIn(body))
return PATTERN_NORMAL_PREVIEW.findAll(body).unzip {
val position = it.groupValues[6].toInt() - 1
val imageKey = getThumbKey(it.groupValues[3].trim())
val xOffset = it.groupValues[4].toIntOrNull() ?: 0
val width = it.groupValues[1].toInt()
val height = it.groupValues[2].toInt()
val pageUrl = it.groupValues[5].trim()
NormalGalleryPreview(imageKey, position, xOffset, width, height) to pageUrl
}.run {
first.toList() to second.toList()
}
}
private external fun nativeParse(body: ByteBuffer, size: Int = body.limit()): Int
private external fun nativeParseComments(body: ByteBuffer, size: Int = body.limit()): Int
private external fun nativeParsePreviews(body: ByteBuffer, size: Int = body.limit()): Int
}
@@ -16,29 +16,23 @@
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.util.toLongOrDefault
import java.util.regex.Pattern
/**
* Like http://exhentai.org/g/1234567/a1b2c3d4e5<br></br>
*/
object GalleryDetailUrlParser {
private val URL_STRICT_PATTERN = Pattern.compile(
private val URL_STRICT_PATTERN = Regex(
"https?://(?:${EhUrl.DOMAIN_EX}|${EhUrl.DOMAIN_E}(?:/lofi)?)/(?:g|mpv)/(\\d+)/([0-9a-f]{10})",
)
private val URL_PATTERN = Pattern.compile("(\\d+)/([0-9a-f]{10})(?:[^0-9a-f]|$)")
private val URL_PATTERN = Regex("(\\d+)/([0-9a-f]{10})(?:[^0-9a-f]|$)")
fun parse(url: String?, strict: Boolean = true): Result? {
url ?: return null
val pattern = if (strict) URL_STRICT_PATTERN else URL_PATTERN
val m = pattern.matcher(url)
return if (m.find()) {
val gid = m.group(1)!!.toLongOrDefault(0).takeIf { it > 0 }
gid?.let { Result(it, m.group(2)!!) }
} else {
null
return pattern.find(url)?.destructured?.let { (gid, token) ->
Result(gid.toLong(), token)
}
}
class Result(val gid: Long, val token: String)
data class Result(val gid: Long, val token: String)
}
@@ -1,26 +1,16 @@
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
import com.ehviewer.core.model.BaseGalleryInfo
import com.hippo.ehviewer.client.exception.ParseException
import java.nio.ByteBuffer
import kotlinx.serialization.Serializable
import moe.tarsin.coroutines.runSuspendCatching
object GalleryListParser {
suspend fun parse(body: ByteBuffer) = runSuspendCatching {
fun parse(body: ByteBuffer) = runSuspendCatching {
unmarshalParsingAs<GalleryListResult>(body, ::parseGalleryInfoList)
}.getOrElse {
throw ParseException("Can't parse gallery list", it)
}.apply {
galleryInfoList.onEach {
if (it.favoriteSlot == NOT_FAVORITED && EhDB.containLocalFavorites(it.gid)) {
it.favoriteSlot = LOCAL_FAVORITED
}
it.generateSLang()
}
}
val emptyResult = GalleryListResult(null, null, arrayListOf())
@@ -15,123 +15,54 @@
*/
package com.hippo.ehviewer.client.parser
import android.net.Uri
import android.os.Build
import arrow.core.Either
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.ListUrlBuilder
import java.io.UnsupportedEncodingException
import java.net.MalformedURLException
import java.net.URL
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import io.ktor.http.Url
import io.ktor.http.decodeURLQueryComponent
object GalleryListUrlParser {
private val VALID_HOSTS = arrayOf(EhUrl.DOMAIN_EX, EhUrl.DOMAIN_E)
private const val PATH_NORMAL = "/"
private const val PATH_UPLOADER = "/uploader/"
private const val PATH_TAG = "/tag/"
private const val PATH_TOPLIST = "/toplist.php"
fun parse(urlStr: String): ListUrlBuilder? {
val url = try {
URL(urlStr)
} catch (e: MalformedURLException) {
return null
fun parse(urlStr: String) = Either.catch {
val url = Url(urlStr)
if (url.host != EhUrl.DOMAIN_E && url.host != EhUrl.DOMAIN_EX) {
return@catch null
}
if (url.host !in VALID_HOSTS) {
return null
}
val path = url.path ?: return null
return if (PATH_NORMAL == path || path.isEmpty()) {
ListUrlBuilder(url.query)
} else if (path.startsWith(PATH_UPLOADER)) {
parseUploader(path)
} else if (path.startsWith(PATH_TAG)) {
parseTag(path)
} else if (path.startsWith(PATH_TOPLIST)) {
parseToplist(urlStr)
} else if (path.startsWith("/")) {
val category = try {
path.substring(1).toInt()
} catch (e: NumberFormatException) {
return null
val segments = url.segments
if (segments.isEmpty()) {
ListUrlBuilder(url.parameters).apply {
url.parameters["f_shash"]?.let {
mode = ListUrlBuilder.MODE_IMAGE_SEARCH
hash = it
}
}
ListUrlBuilder(url.query).also { it.category = category }
} else {
null
}
}
// TODO get page
private fun parseUploader(path: String): ListUrlBuilder? {
var uploader: String?
val prefixLength = PATH_UPLOADER.length
val index = path.indexOf('/', prefixLength)
uploader = if (index < 0) {
path.substring(prefixLength)
} else {
path.substring(prefixLength, index)
}
uploader = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
URLDecoder.decode(uploader, StandardCharsets.UTF_8)
} else {
try {
URLDecoder.decode(uploader, StandardCharsets.UTF_8.displayName())
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
return null
when (val head = segments[0]) {
"uploader", "tag" -> ListUrlBuilder(url.parameters).apply {
mode = if (head == "uploader") ListUrlBuilder.MODE_UPLOADER else ListUrlBuilder.MODE_TAG
keyword = segments[1].decodeURLQueryComponent(plusIsSpace = true)
}
"toplist.php" -> {
val tl = url.parameters["tl"]
if (tl != null && tl in arrayOf("11", "12", "13", "15")) {
ListUrlBuilder(
mode = ListUrlBuilder.MODE_TOPLIST,
jumpTo = url.parameters["p"],
mKeyword = tl,
)
} else {
null
}
}
else -> {
val category = EhUtils.getCategory(head)
if (category != EhUtils.UNKNOWN) {
ListUrlBuilder(url.parameters).also { it.category = category }
} else {
null
}
}
}
}
if (uploader.isNullOrEmpty()) {
return null
}
val builder = ListUrlBuilder()
builder.mode = ListUrlBuilder.MODE_UPLOADER
builder.keyword = uploader
return builder
}
// TODO get page
private fun parseTag(path: String): ListUrlBuilder? {
var tag: String?
val prefixLength = PATH_TAG.length
val index = path.indexOf('/', prefixLength)
tag = if (index < 0) {
path.substring(prefixLength)
} else {
path.substring(prefixLength, index)
}
tag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
URLDecoder.decode(tag, StandardCharsets.UTF_8)
} else {
try {
URLDecoder.decode(tag, StandardCharsets.UTF_8.displayName())
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
return null
}
}
if (tag.isNullOrEmpty()) {
return null
}
val builder = ListUrlBuilder()
builder.mode = ListUrlBuilder.MODE_TAG
builder.keyword = tag
return builder
}
// TODO get page
private fun parseToplist(path: String): ListUrlBuilder? {
val uri = Uri.parse(path)
if (uri == null || uri.getQueryParameter("tl").isNullOrEmpty()) {
return null
}
val tl = uri.getQueryParameter("tl")!!.toInt()
if (tl > 15 || tl < 11) {
return null
}
val builder = ListUrlBuilder()
builder.mode = ListUrlBuilder.MODE_TOPLIST
builder.keyword = tl.toString()
return builder
}
}.getOrNull()
}
@@ -19,22 +19,18 @@ package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.client.parseAs
import com.hippo.ehviewer.util.ExceptionUtils
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
object GalleryMultiPageViewerPTokenParser {
private const val IMAGE_LIST_STRING = "var imagelist = "
fun parse(body: String): List<String> {
fun parse(body: String): List<String> = runCatching {
val index = body.indexOf(IMAGE_LIST_STRING)
val imagelist = body.substring(index + IMAGE_LIST_STRING.length, body.indexOf(";", index))
return runCatching {
imagelist.parseAs<List<Item>>().map(Item::token)
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
throw ParseException("Parse pToken from MPV error", it)
}
imagelist.parseAs<List<Item>>().map(Item::token)
}.getOrElse {
throw ParseException("Parse pToken from MPV error", it)
}
@Serializable
@@ -1,34 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.util.ExceptionUtils
import org.jsoup.Jsoup
object GalleryNotAvailableParser {
fun parse(body: String): String? {
return runCatching {
val e = Jsoup.parse(body).getElementsByClass("d").first()
e!!.child(0).html().replace("<br>", "\n")
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
it.printStackTrace()
null
}
}
}
@@ -15,12 +15,12 @@
*/
package com.hippo.ehviewer.client.parser
import com.ehviewer.core.util.unescapeXml
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.util.unescapeXml
object GalleryPageParser {
private val PATTERN_IMAGE_URL = Regex("<img[^>]*src=\"([^\"]+)\" style")
private val PATTERN_SKIP_HATH_KEY = Regex("onclick=\"return nl\\('([\\d-]+)'\\)")
private val PATTERN_SKIP_HATH_KEY = Regex("onclick=\"return nl\\('([^)]+)'\\)")
private val PATTERN_ORIGIN_IMAGE_URL = Regex("<a href=\"([^\"]+/fullimg/[^\"]+)\">")
// TODO Not sure about the size of show keys
@@ -16,34 +16,21 @@
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.util.toIntOrDefault
import com.hippo.ehviewer.util.toLongOrDefault
import java.util.regex.Pattern
/**
* Like http://exhentai.org/s/91ea4b6d89/901103-12
*/
object GalleryPageUrlParser {
private val URL_STRICT_PATTERN = Pattern.compile(
private val URL_STRICT_PATTERN = Regex(
"https?://(?:${EhUrl.DOMAIN_EX}|${EhUrl.DOMAIN_E}(?:/lofi)?)/s/([0-9a-f]{10})/(\\d+)-(\\d+)",
)
private val URL_PATTERN = Pattern.compile("([0-9a-f]{10})/(\\d+)-(\\d+)")
private val URL_PATTERN = Regex("([0-9a-f]{10})/(\\d+)-(\\d+)")
fun parse(url: String?, strict: Boolean = true): Result? {
url ?: return null
val pattern = if (strict) URL_STRICT_PATTERN else URL_PATTERN
val m = pattern.matcher(url)
return if (m.find()) {
val gid = m.group(2)!!.toLongOrDefault(-1L)
val pToken = m.group(1)!!
val page = m.group(3)!!.toIntOrDefault(0) - 1
if (gid < 0 || page < 0) {
null
} else {
Result(gid, pToken, page)
}
} else {
null
return pattern.find(url)?.destructured?.let { (pToken, gid, page) ->
Result(gid.toLong(), pToken, page.toInt() - 1)
}
}
@@ -15,9 +15,10 @@
*/
package com.hippo.ehviewer.client.parser
import arrow.core.Either
import arrow.core.getOrElse
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.parseAs
import com.hippo.ehviewer.util.ExceptionUtils
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -32,13 +33,10 @@ object GalleryTokenApiParser {
* ]
* }
*/
fun parse(body: String): String {
return runCatching {
body.parseAs<Result>().tokenList[0].token
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
throw EhException(body.parseAs<Error>().error)
}
fun parse(body: String): String = Either.catch {
body.parseAs<Result>().tokenList[0].token
}.getOrElse {
throw EhException(body.parseAs<VoteTagResult>().error)
}
@Serializable
@@ -1,14 +1,14 @@
package com.hippo.ehviewer.client.parser
import android.os.Parcelable
import com.hippo.ehviewer.client.exception.InsufficientFundsException
import com.hippo.ehviewer.client.exception.ParseException
import java.nio.ByteBuffer
import kotlinx.parcelize.Parcelize
import kotlin.random.Random
import kotlinx.serialization.Serializable
object HomeParser {
private val PATTERN_FUNDS = Regex("Available: ([\\d,]+) Credits.*Available: ([\\d,]+) kGP", RegexOption.DOT_MATCHES_ALL)
private val TorrentKeyRegex = Regex("Your current key is: <[^>]*>([^<]*)<")
private val FundsRegex = Regex("Available: ([\\d,]+) Credits.*Available: ([\\d,]+) kGP", RegexOption.DOT_MATCHES_ALL)
private const val INSUFFICIENT_FUNDS = "Insufficient funds."
fun parse(body: ByteBuffer) = runCatching {
@@ -21,22 +21,25 @@ object HomeParser {
}
}
fun parseTorrentKey(body: String) = TorrentKeyRegex.find(body)?.run { groupValues[1] }
?: throw ParseException("Parse torrent key error")
fun parseFunds(body: String): Funds {
PATTERN_FUNDS.find(body)?.groupValues?.run {
val fundsC = ParserUtils.parseInt(get(1), 0)
val fundsGP = ParserUtils.parseInt(get(2), 0) * 1000
FundsRegex.find(body)?.run {
val fundsC = ParserUtils.parseInt(groupValues[1], 0)
val fundsGP = ParserUtils.parseInt(groupValues[2], 0)
return Funds(fundsGP, fundsC)
}
throw ParseException("Parse funds error")
}
@Parcelize
data class Funds(val fundsGP: Int, val fundsC: Int) : Parcelable
class Result(val limits: Limits, val funds: Funds)
data class Result(val limits: Limits, val funds: Funds, private val id: Int = Random.nextInt())
}
@Parcelize
@Serializable
data class Limits(val current: Int, val maximum: Int, val resetCost: Int) : Parcelable
data class Funds(val gp: Int, val credit: Int)
@Serializable
data class Limits(val current: Int, val maximum: Int, val resetCost: Int)
private external fun parseLimit(body: ByteBuffer, limit: Int = body.limit()): Int
@@ -15,32 +15,29 @@
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.util.toIntOrDefault
import com.hippo.ehviewer.util.toLongOrDefault
import com.hippo.ehviewer.util.unescapeXml
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.Locale
import com.ehviewer.core.util.toEpochMillis
import com.ehviewer.core.util.toIntOrDefault
import com.ehviewer.core.util.toLocalDateTime
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.format.char
object ParserUtils {
private val formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", Locale.US).withZone(ZoneOffset.UTC)
@Synchronized
fun formatDate(time: Long): String {
return formatter.format(Instant.ofEpochMilli(time))
// yyyy-MM-dd HH:mm
private val formatter = LocalDateTime.Format {
year()
char('-')
monthNumber()
char('-')
day()
char(' ')
hour()
char(':')
minute()
}
fun trim(str: String?): String {
return str?.unescapeXml()?.trim() ?: ""
}
fun formatDate(time: Long): String = formatter.format(time.toLocalDateTime())
fun parseInt(str: String?, defValue: Int): Int {
return trim(str).replace(",", "").toIntOrDefault(defValue)
}
fun parseDate(str: String): Long = formatter.parse(str).toEpochMillis()
fun parseLong(str: String?, defValue: Long): Long {
return trim(str).replace(",", "").toLongOrDefault(defValue)
}
fun parseInt(str: String, defValue: Int): Int = str.replace(",", "").toIntOrDefault(defValue)
}
@@ -15,40 +15,28 @@
*/
package com.hippo.ehviewer.client.parser
import android.util.Log
import com.hippo.ehviewer.client.EhUrl
import arrow.core.Either.Companion.catch
import arrow.core.getOrElse
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.util.ExceptionUtils
import org.jsoup.Jsoup
import java.nio.ByteBuffer
import kotlinx.serialization.Serializable
object ProfileParser {
private val TAG = ProfileParser::class.java.simpleName
fun parse(body: String): Result {
return runCatching {
val d = Jsoup.parse(body)
val profilename = d.getElementById("profilename")
val displayName = profilename!!.child(0).text()
val avatar = runCatching {
val avatar =
profilename.nextElementSibling()!!.nextElementSibling()!!.child(0).attr("src")
if (avatar.isEmpty()) {
null
} else if (!avatar.startsWith("http")) {
EhUrl.URL_FORUMS + avatar
} else {
avatar
}
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
Log.i(TAG, "No avatar")
null
}
Result(displayName, avatar)
}.getOrElse {
ExceptionUtils.throwIfFatal(it)
throw ParseException("Parse forums error")
}
fun parse(body: ByteBuffer) = catch {
unmarshalParsingAs<Result>(body, ::parseProfile)
}.getOrElse {
throw ParseException("Failed to parse profile", it)
}
class Result(val displayName: String?, val avatar: String?)
fun parseProfileUrl(body: ByteBuffer) = catch {
unmarshalParsingAs<String>(body, ::parseProfileUrl)
}.getOrElse {
throw ParseException("Failed to parse profile url", it)
}
@Serializable
data class Result(val displayName: String, val avatar: String?)
private external fun parseProfile(body: ByteBuffer, size: Int = body.limit()): Int
private external fun parseProfileUrl(body: ByteBuffer, size: Int = body.limit()): Int
}
@@ -17,26 +17,17 @@ package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.exception.ParseException
import java.util.regex.Pattern
object SignInParser {
private val NAME_PATTERN = Pattern.compile("<p>You are now logged in as: (.+?)<")
private val ERROR_PATTERN = Pattern.compile(
private val NAME_PATTERN = Regex("<p>You are now logged in as: (.+?)<")
private val ERROR_PATTERN = Regex(
"<h4>The error returned was:</h4>\\s*<p>(.+?)</p>" +
"|<span class=\"postcolor\">(.+?)</span>",
)
fun parse(body: String): String {
var m = NAME_PATTERN.matcher(body)
return if (m.find()) {
m.group(1)!!
} else {
m = ERROR_PATTERN.matcher(body)
if (m.find()) {
throw EhException(m.group(1) ?: m.group(2))
} else {
throw ParseException("Can't parse sign in")
}
}
}
fun parse(body: String): String = NAME_PATTERN.find(body)?.let {
it.groupValues[1]
} ?: ERROR_PATTERN.find(body)?.let {
throw EhException(it.groupValues[1].ifEmpty { it.groupValues[2] })
} ?: throw ParseException("Can't parse sign in")
}
@@ -14,16 +14,18 @@ object TorrentParser {
@Serializable
data class Torrent(
val outdated: Boolean,
val posted: String,
val size: String,
val seeds: Int,
val peers: Int,
val downloads: Int,
val uploader: String,
val url: String,
val name: String,
)
fun Torrent.format() = "[$posted] $name [$size] [↑$seeds ↓$peers ✓$downloads]"
fun Torrent.format() = "↑$seeds ↓$peers ✓$downloads"
typealias TorrentResult = List<Torrent>
@@ -0,0 +1,16 @@
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.exception.ParseException
import java.nio.ByteBuffer
object UserConfigParser {
private external fun parseFavCat(body: ByteBuffer, size: Int = body.limit()): Int
fun parse(body: ByteBuffer) = runCatching {
val favCat = unmarshalParsingAs<Array<String>>(body, ::parseFavCat)
Settings.favCat = favCat
}.onFailure {
throw ParseException("Can't parse user config", it)
}
}
@@ -17,11 +17,18 @@
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.parseAs
import arrow.core.Either.Companion.catch
import arrow.core.getOrElse
import com.ehviewer.core.model.GalleryTagGroup
import com.hippo.ehviewer.client.exception.ParseException
import java.nio.ByteBuffer
object VoteTagParser {
// {"error":"The tag \"neko\" is not allowed. Use character:neko or artist:neko"}
fun parse(body: String): String? {
return runCatching { body.parseAs<Error>().error }.getOrNull()
fun parse(body: ByteBuffer) = catch {
unmarshalParsingAs<List<GalleryTagGroup>>(body, ::nativeParse)
}.getOrElse {
throw ParseException("Failed to parse vote tag result", it)
}
private external fun nativeParse(body: ByteBuffer, size: Int = body.limit()): Int
}
@@ -3,4 +3,7 @@ package com.hippo.ehviewer.client.parser
import kotlinx.serialization.Serializable
@Serializable
data class Error(val error: String)
data class VoteTagResult(
val error: String?,
val tagpane: String?,
)
@@ -0,0 +1,49 @@
package com.hippo.ehviewer.coil
import coil3.ImageLoader
import coil3.asImage
import coil3.decode.ByteBufferMetadata
import coil3.decode.ContentMetadata
import coil3.decode.DecodeResult
import coil3.decode.DecodeUtils
import coil3.decode.Decoder
import coil3.decode.ImageSource
import coil3.fetch.SourceFetchResult
import coil3.gif.isAnimatedWebP
import coil3.request.Options
import java.io.FileInputStream
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import okio.FileSystem
class AnimatedWebPDecoder(private val source: ByteBuffer) : Decoder {
override suspend fun decode() = DecodeResult(AnimatedWebPDrawable(source).asImage(), false)
object Factory : Decoder.Factory {
override fun create(
result: SourceFetchResult,
options: Options,
imageLoader: ImageLoader,
) = if (DecodeUtils.isAnimatedWebP(result.source.source())) {
result.source.toByteBufferOrNull()?.let { AnimatedWebPDecoder(it) }
} else {
null
}
}
}
private fun ImageSource.toByteBufferOrNull(): ByteBuffer? {
if (fileSystem === FileSystem.SYSTEM) {
val file = fileOrNull()
if (file != null) {
return file.toFile().inputStream().mapReadOnly()
}
}
return when (val metadata = metadata) {
is ContentMetadata -> metadata.assetFileDescriptor.createInputStream().mapReadOnly()
is ByteBufferMetadata -> metadata.byteBuffer
else -> null
}
}
private fun FileInputStream.mapReadOnly(): ByteBuffer = channel.use { it.map(FileChannel.MapMode.READ_ONLY, 0, it.size()) }
@@ -0,0 +1,151 @@
package com.hippo.ehviewer.coil
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.os.SystemClock
import androidx.core.graphics.createBitmap
import com.ehviewer.core.util.logcat
import java.nio.ByteBuffer
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
// Hold a reference to the buffer as it's used by the decoder
@Suppress("CanBeParameter")
class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animatable {
private val decodeScope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))
private val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG or Paint.FILTER_BITMAP_FLAG)
private val decoder = nativeCreateDecoder(source)
private val width: Int
private val height: Int
private val loopCount: Int
private var decodeJob: Job? = null
private var loopsCompleted = 0
private var timeToShowNextFrame = 0L
private var currentFrame: Frame
private var nextFrame: Frame
init {
check(decoder != 0L) { "Failed to create decoder" }
val packed = nativeGetImageInfo(decoder)
width = (packed shr 40).toInt()
height = (packed shr 16 and 0xFFFFFF).toInt()
loopCount = (packed and 0xFFFF).toInt()
val bitmap = createBitmap(width, height)
val timestamp = nativeDecodeNextFrame(decoder, false, bitmap)
check(timestamp != 0) {
nativeDestroyDecoder(decoder)
"Failed to decode first frame"
}
currentFrame = Frame(bitmap, timestamp)
nextFrame = Frame(createBitmap(width, height), 0)
}
override fun getIntrinsicWidth() = width
override fun getIntrinsicHeight() = height
private val runnable = Runnable {
timeToShowNextFrame = SystemClock.uptimeMillis() + nextFrame.timestamp - currentFrame.timestamp
invalidateSelf()
}
private fun decodeNextFrame(reset: Boolean) = decodeScope.launch {
val timestamp = nativeDecodeNextFrame(decoder, reset, nextFrame.bitmap)
ensureActive()
check(timestamp != 0) {
decodeJob = null
"Failed to decode next frame"
}
if (timestamp <= currentFrame.timestamp) {
if (reset) loopsCompleted = 0 else loopsCompleted++
currentFrame.timestamp = 0
}
nextFrame.timestamp = timestamp
nextFrame.bitmap.prepareToDraw()
}.apply {
invokeOnCompletion { cause ->
when (cause) {
null -> if (reset) {
runnable.run()
} else {
scheduleSelf(runnable, timeToShowNextFrame)
}
!is CancellationException -> logcat(cause)
}
}
}
override fun draw(canvas: Canvas) {
if (decodeJob?.isCompleted == true && isVisible) {
decodeJob = if (loopCount == 0 || loopsCompleted < loopCount) {
currentFrame = nextFrame.also { nextFrame = currentFrame }
decodeNextFrame(false)
} else {
null
}
}
canvas.drawBitmap(currentFrame.bitmap, null, bounds, paint)
}
@Deprecated("Deprecated in Java")
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun setAlpha(alpha: Int) {
if (alpha != paint.alpha) {
paint.alpha = alpha
invalidateSelf()
}
}
override fun getAlpha(): Int = paint.alpha
override fun setColorFilter(colorFilter: ColorFilter?) {
if (colorFilter != paint.colorFilter) {
paint.colorFilter = colorFilter
invalidateSelf()
}
}
override fun getColorFilter(): ColorFilter? = paint.colorFilter
override fun isRunning() = decodeJob != null
override fun start() {
if (decodeJob == null) {
decodeJob = decodeNextFrame(true)
}
}
override fun stop() {
decodeJob?.cancel()
decodeJob = null
unscheduleSelf(runnable)
}
fun dispose() {
runBlocking {
decodeScope.coroutineContext.job.cancelAndJoin()
}
nativeDestroyDecoder(decoder)
}
}
private class Frame(val bitmap: Bitmap, var timestamp: Int)
private external fun nativeCreateDecoder(source: ByteBuffer): Long
private external fun nativeGetImageInfo(decoder: Long): Long
private external fun nativeDecodeNextFrame(decoder: Long, reset: Boolean, bitmap: Bitmap): Int
private external fun nativeDestroyDecoder(decoder: Long)
@@ -0,0 +1,64 @@
package com.hippo.ehviewer.coil
import android.graphics.Bitmap
import android.hardware.HardwareBuffer
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toIntRect
import arrow.fx.coroutines.autoCloseable
import arrow.fx.coroutines.resourceScope
import coil3.asImage
import coil3.intercept.Interceptor
import coil3.request.ImageResult
import coil3.request.SuccessResult
import com.ehviewer.core.util.isAtLeastQ
import com.ehviewer.core.util.logcat
import com.hippo.ehviewer.image.copyBitmapToAHB
import moe.tarsin.coroutines.runSuspendCatching
@RequiresApi(Build.VERSION_CODES.O)
private const val USAGE = HardwareBuffer.USAGE_CPU_WRITE_RARELY or HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
object CropBorderInterceptor : Interceptor {
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val result = chain.proceed()
if (result is SuccessResult) {
val image = result.image
if (image is BitmapImageWithExtraInfo) {
// Copy with cropped region
val srcSize = IntSize(image.width, image.height)
if (image.rect.size != srcSize) {
val (x, y) = image.rect.topLeft
val (w, h) = image.rect.size
val src = image.image.bitmap
// Large hardware bitmaps have rendering issues (e.g. crash, empty) on some devices.
// This is not ideal but I haven't figured out how to probe the threshold.
// All we know is that it's less than the maximum texture size.
val meetHardwareThreshold = maxOf(w, h) <= chain.request.hardwareThreshold
val bitmap = when {
isAtLeastQ && meetHardwareThreshold -> runSuspendCatching {
val format = when (val config = src.config) {
Bitmap.Config.ARGB_8888 -> HardwareBuffer.RGBA_8888
Bitmap.Config.RGB_565 -> HardwareBuffer.RGB_565
Bitmap.Config.RGBA_F16 -> HardwareBuffer.RGBA_FP16
else -> error("Unsupported bitmap config: $config")
}
resourceScope {
val buffer = autoCloseable { HardwareBuffer.create(w, h, format, 1, USAGE) }
copyBitmapToAHB(src, buffer, x, y)
Bitmap.wrapHardwareBuffer(buffer, src.colorSpace)
}
}.onFailure { logcat(it) }.getOrNull()
else -> null
} ?: Bitmap.createBitmap(src, x, y, w, h)
src.recycle()
return result.copy(image = image.copy(image = bitmap.asImage(), rect = image.rect.size.toIntRect()))
}
}
}
return result
}
}
@@ -0,0 +1,51 @@
package com.hippo.ehviewer.coil
import androidx.compose.ui.unit.IntRect
import coil3.Extras
import coil3.getExtra
import coil3.intercept.Interceptor
import coil3.intercept.Interceptor.Chain
import coil3.request.ImageRequest
import coil3.request.ImageResult
import coil3.request.SuccessResult
import com.hippo.ehviewer.image.detectBorder
private const val CROP_THRESHOLD = 0.75f
private const val RATIO_THRESHOLD = 2
private val maybeCropBorderKey = Extras.Key(default = false)
fun ImageRequest.Builder.maybeCropBorder(enable: Boolean) = apply {
extras[maybeCropBorderKey] = enable
}
val ImageRequest.maybeCropBorder: Boolean
get() = getExtra(maybeCropBorderKey)
object DetectBorderInterceptor : Interceptor {
override suspend fun intercept(chain: Chain): ImageResult {
val result = chain.proceed()
if (chain.request.maybeCropBorder && result is SuccessResult) {
val image = result.image
if (image is BitmapImageWithExtraInfo && !image.hasQrCode) {
val bitmap = image.image.bitmap
val ratio = if (image.height > image.width) {
image.height / image.width
} else {
image.width / image.height
}
if (ratio < RATIO_THRESHOLD) {
val array = detectBorder(bitmap)
val minWidth = image.width * CROP_THRESHOLD
val minHeight = image.height * CROP_THRESHOLD
val rect = IntRect(array[0], array[1], array[2], array[3])
if (rect.width > minWidth && rect.height > minHeight) {
val new = image.copy(rect = rect)
return result.copy(image = new)
}
}
}
}
return result
}
}
@@ -1,44 +1,64 @@
package com.hippo.ehviewer.coil
import coil3.Extras
import coil3.getExtra
import coil3.intercept.Interceptor
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import coil3.request.ImageResult
import coil3.request.SuccessResult
import com.ehviewer.core.database.model.DownloadInfo
import com.ehviewer.core.files.delete
import com.ehviewer.core.files.isDirectory
import com.ehviewer.core.files.isFile
import com.ehviewer.core.files.sendTo
import com.ehviewer.core.files.toUri
import com.hippo.ehviewer.EhApplication.Companion.imageCache
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.client.getThumbKey
import com.hippo.ehviewer.download.downloadLocation
import com.hippo.ehviewer.spider.DownloadInfoMagics.decodeMagicRequestOrUrl
import com.hippo.ehviewer.util.sendTo
import com.hippo.unifile.asUniFile
import eu.kanade.tachiyomi.util.lang.withIOContext
private val downloadInfoKey = Extras.Key<DownloadInfo?>(default = null)
fun ImageRequest.Builder.downloadInfo(info: DownloadInfo) = apply {
extras[downloadInfoKey] = info
}
val ImageRequest.downloadInfo: DownloadInfo?
get() = getExtra(downloadInfoKey)
object DownloadThumbInterceptor : Interceptor {
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val magicOrUrl = chain.request.data as? String
if (magicOrUrl != null) {
val (url, location) = decodeMagicRequestOrUrl(magicOrUrl)
if (location != null) {
val thumb = withIOContext { downloadLocation / location / ".thumb" }
if (withIOContext { thumb.isFile }) {
val new = chain.request.newBuilder().data(thumb.uri.toString()).build()
when (val result = chain.withRequest(new).proceed()) {
is SuccessResult -> return result
is ErrorResult -> withIOContext { thumb.delete() }
}
}
val new = chain.request.newBuilder().data(url).build()
return chain.withRequest(new).proceed().also {
if (it is SuccessResult) {
withIOContext {
if (thumb.ensureFile()) {
val key = requireNotNull(chain.request.memoryCacheKey)
imageCache.read(key) {
data.asUniFile() sendTo thumb
}
}
}
}
val info = chain.request.downloadInfo
if (info != null && !info.dirname.isNullOrBlank()) {
val thumbKey = getThumbKey(chain.request.data as String)
if (info.thumbKey != thumbKey) {
info.thumbKey = thumbKey
EhDB.putGalleryInfo(info.galleryInfo)
}
val dir = downloadLocation / info.dirname!!
val format = thumbKey.substringAfterLast('.', "")
check(format.isNotBlank())
val thumb = dir / "thumb.$format"
val v1Thumb = dir / "thumb.jpg"
if (thumb.isFile) {
val new = chain.request.newBuilder().data(thumb.toUri()).build()
val result = chain.withRequest(new).proceed()
if (result is SuccessResult) {
if (thumb != v1Thumb) v1Thumb.delete()
return result
}
}
val result = chain.proceed()
if (result is SuccessResult && dir.isDirectory) {
// Accessing the recreated file immediately after deleting it throws
// FileNotFoundException, so we just overwrite the existing file.
val key = requireNotNull(chain.request.memoryCacheKey)
imageCache.read(key) {
data sendTo thumb
}
if (thumb != v1Thumb) v1Thumb.delete()
}
return result
}
return chain.proceed()
}
@@ -1,52 +1,61 @@
package com.hippo.ehviewer.coil
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import coil3.asCoilImage
import coil3.decode.DecodeResult
import coil3.decode.Decoder
import coil3.network.httpHeaders
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.paging.compose.LazyPagingItems
import coil3.decode.BlackholeDecoder
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.size.Size
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.CHROME_ACCEPT
import com.hippo.ehviewer.client.CHROME_ACCEPT_LANGUAGE
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.data.GalleryPreview
import com.hippo.ehviewer.client.data.NormalGalleryPreview
import com.hippo.ehviewer.client.url
import com.hippo.ehviewer.spider.DownloadInfoMagics.encodeMagicRequestOrUrl
import io.ktor.http.HttpHeaders
import io.ktor.http.headers
private val header = headers {
append(HttpHeaders.UserAgent, Settings.userAgent)
append(HttpHeaders.Accept, CHROME_ACCEPT)
append(HttpHeaders.AcceptLanguage, CHROME_ACCEPT_LANGUAGE)
}
import coil3.size.SizeResolver
import com.ehviewer.core.database.model.DownloadInfo
import com.ehviewer.core.model.GalleryInfo
import com.ehviewer.core.model.GalleryPreview
import com.ehviewer.core.model.V2GalleryPreview
import com.hippo.ehviewer.client.getThumbKey
import com.hippo.ehviewer.client.getV2PreviewKey
import com.hippo.ehviewer.client.thumbUrl
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.ktbuilder.execute
// Load in original size so the memory cache can be reused for preload requests
fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply {
val key = info.thumbKey!!
data(encodeMagicRequestOrUrl(info))
data(info.thumbUrl)
size(SizeResolver.ORIGINAL)
val downloadInfo = (info as? DownloadInfo) ?: DownloadManager.getDownloadInfo(info.gid)
if (downloadInfo != null) {
downloadInfo(downloadInfo)
}
memoryCacheKey(key)
diskCacheKey(key)
httpHeaders(header)
}
// Load in original size so the memory cache can be reused for preload requests
fun ImageRequest.Builder.ehPreview(preview: GalleryPreview) = apply {
data(preview.url)
memoryCacheKey(preview.imageKey)
diskCacheKey(preview.imageKey)
if (preview is NormalGalleryPreview) size(Size.ORIGINAL)
httpHeaders(header)
with(preview) {
val key = if (this is V2GalleryPreview) getV2PreviewKey(url) else getThumbKey(url)
data(url)
size(SizeResolver.ORIGINAL)
memoryCacheKey(key)
diskCacheKey(key)
}
}
private val stubImage = ColorDrawable(Color.BLACK).asCoilImage(true)
private val stubResult = DecodeResult(stubImage, false)
private val stubFactory = Decoder { stubResult }
fun ImageRequest.Builder.justDownload() = apply {
memoryCachePolicy(CachePolicy.DISABLED)
decoderFactory { _, _, _ -> stubFactory }
decoderFactory(BlackholeDecoder.Factory())
}
@Composable
inline fun <T : Any> PrefetchAround(data: LazyPagingItems<T>, index: Int, distance: Int, crossinline f: (T) -> ImageRequest) {
data.peek((index - distance).coerceAtLeast(0))?.let { fetchBefore ->
LaunchedEffect(fetchBefore) {
f(fetchBefore).execute()
}
}
data.peek((index + distance).coerceAtMost(data.itemCount - 1))?.let { fetchAhead ->
LaunchedEffect(fetchAhead) {
f(fetchAhead).execute()
}
}
}
@@ -0,0 +1,48 @@
package com.hippo.ehviewer.coil
import android.graphics.Bitmap
import android.os.Build
import androidx.annotation.RequiresApi
import coil3.Extras
import coil3.asImage
import coil3.getExtra
import coil3.intercept.Interceptor
import coil3.intercept.Interceptor.Chain
import coil3.request.ImageRequest
import coil3.request.ImageResult
import coil3.request.SuccessResult
import coil3.request.allowHardware
private val hardwareThresholdKey = Extras.Key(default = 16384)
fun ImageRequest.Builder.hardwareThreshold(size: Int) = apply {
extras[hardwareThresholdKey] = size
}
val ImageRequest.hardwareThreshold: Int
get() = getExtra(hardwareThresholdKey)
@RequiresApi(Build.VERSION_CODES.O)
object HardwareBitmapInterceptor : Interceptor {
override suspend fun intercept(chain: Chain): ImageResult {
val result = chain.proceed()
val request = result.request
if (!request.allowHardware && result is SuccessResult) {
val image = result.image
if (image is BitmapImageWithExtraInfo) {
val bitmap = image.image.bitmap
val isHardware = bitmap.config == Bitmap.Config.HARDWARE
// Large hardware bitmaps have rendering issues (e.g. crash, empty) on some devices.
// This is not ideal but I haven't figured out how to probe the threshold.
// All we know is that it's less than the maximum texture size.
if (!isHardware && maxOf(bitmap.width, bitmap.height) <= request.hardwareThreshold) {
bitmap.copy(Bitmap.Config.HARDWARE, false)?.let { hwBitmap ->
bitmap.recycle()
return result.copy(image = image.copy(image = hwBitmap.asImage()))
}
}
}
}
return result
}
}
@@ -0,0 +1,29 @@
package com.hippo.ehviewer.coil
import androidx.compose.ui.unit.IntRect
import coil3.BitmapImage
import coil3.Image
import coil3.intercept.Interceptor
import coil3.request.ImageResult
import coil3.request.SuccessResult
import coil3.request.allowHardware
data class BitmapImageWithExtraInfo(
val image: BitmapImage,
val rect: IntRect = IntRect(0, 0, image.width, image.height),
val hasQrCode: Boolean = false,
) : Image by image
object MapExtraInfoInterceptor : Interceptor {
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val result = chain.proceed()
val needMap = with(chain.request) { maybeCropBorder || detectQrCode || !allowHardware }
if (needMap && result is SuccessResult) {
val image = result.image
if (image is BitmapImage) {
return result.copy(image = BitmapImageWithExtraInfo(image))
}
}
return result
}
}
@@ -1,82 +1,67 @@
package com.hippo.ehviewer.coil
import arrow.core.merge
import arrow.fx.coroutines.raceN
import coil3.decode.DataSource
import coil3.imageLoader
import coil3.intercept.Interceptor
import coil3.memory.MemoryCache
import coil3.request.ImageResult
import com.hippo.ehviewer.Settings
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import coil3.request.SuccessResult
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import moe.tarsin.coroutines.Counter
import moe.tarsin.coroutines.Tracker
import moe.tarsin.coroutines.counter
import moe.tarsin.coroutines.runSuspendCatching
import moe.tarsin.coroutines.use
typealias F = suspend () -> Unit
suspend inline fun <T> CancellableContinuation<T>.evalAndResume(f: suspend () -> T) = runSuspendCatching { f() }.takeIf { isActive }?.let(::resumeWith)
class TightRope : CoroutineScope, Counter by counter() {
override val coroutineContext = Dispatchers.IO + Job()
val actions = Channel<F>()
val flow = actions.receiveAsFlow().map(F::invoke).buffer(0).shareIn(this, SharingStarted.WhileSubscribed(stopTimeoutMillis = 200))
suspend inline fun <R> sendAndAwait(crossinline block: suspend () -> R) = raceN(
{ flow.collect {} },
{ suspendCancellableCoroutine { cont -> launch { actions.send { cont.evalAndResume(block) } } } },
).merge()
}
object TightRopeTracker : Tracker<TightRope, String>() {
override fun new() = TightRope()
override fun free(e: TightRope) = e.cancel()
}
object MergeInterceptor : Interceptor {
private val pendingContinuationMap: HashMap<String, MutableList<CancellableContinuation<Unit>>> = hashMapOf()
private val pendingContinuationMapLock = Any()
private val notifyScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val EMPTY_LIST = mutableListOf<CancellableContinuation<Unit>>()
private fun triggerSuccessor(key: String) {
notifyScope.launch {
synchronized(pendingContinuationMapLock) {
// Wake up a pending continuation to continue executing task
val successor = pendingContinuationMap[key]?.removeFirstOrNull()?.apply { resume(Unit) { triggerSuccessor(key) } }
// If no successor, delete this entry from hashmap
successor ?: pendingContinuationMap.remove(key)
}
}
}
private fun triggerFailure(key: String, e: Throwable) {
notifyScope.launch {
synchronized(pendingContinuationMapLock) {
pendingContinuationMap.remove(key)?.forEach { it.resumeWithException(e) }
}
}
}
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val req = chain.request
val key = req.memoryCacheKey?.takeIf { it.startsWith("m/") || Settings.preloadThumbAggressively } ?: return withContext(req.interceptorDispatcher) { chain.proceed() }
suspendCancellableCoroutine { continuation ->
synchronized(pendingContinuationMapLock) {
val existPendingContinuations = pendingContinuationMap[key]
if (existPendingContinuations == null) {
pendingContinuationMap[key] = EMPTY_LIST
continuation.resume(Unit) {
triggerSuccessor(key)
}
} else {
if (existPendingContinuations === EMPTY_LIST) pendingContinuationMap[key] = mutableListOf()
pendingContinuationMap[key]!!.apply {
add(continuation)
continuation.invokeOnCancellation { remove(continuation) }
}
val key = req.memoryCacheKey
return if (key != null) {
val cacheKey = MemoryCache.Key(key, req.memoryCacheKeyExtras)
val cached = req.context.imageLoader.memoryCache!![cacheKey] != null
val result = TightRopeTracker.use(key) { sendAndAwait { chain.proceed() } }
when (result) {
is SuccessResult if !cached && result.dataSource == DataSource.MEMORY_CACHE -> {
result.copy(dataSource = DataSource.MEMORY)
}
else -> result
}
}
try {
return withContext(req.interceptorDispatcher) { chain.proceed() }.apply {
// Wake all pending continuations shared with the same memory key since we have written it to memory cache
notifyScope.launch {
synchronized(pendingContinuationMapLock) {
pendingContinuationMap.remove(key)?.forEach { it.resume(Unit) }
}
}
}
} catch (e: CancellationException) {
triggerSuccessor(key)
throw e
} catch (e: Throwable) {
// Wake all pending continuations since this request is to be failed
triggerFailure(key, e)
throw e
} else {
chain.proceed()
}
}
}
Loaded 100 of 645 files, more files were not shown because too many files have changed in this diff. Show more