Compare commits

...
1053 Commits
Author SHA1 Message Date
FooIbar 98d66b3e6c Release 1.8.10.1
Releases / Build (push) Has been cancelled
2023-10-21 00:14:03 +08:00
FooIbar 6e59583dea Fix reader bug on certain orientation settings
Resolve #68
2023-10-21 00:12:52 +08:00
FooIbar 2f0571e384 Implement splash screen 2023-10-20 17:36:59 +08:00
renovate[bot] 64a6074e02 Update dependency org.jsoup:jsoup to v1.16.2 2023-10-20 06:11:31 +00:00
renovate[bot] 0b7cb4cf41 Update dependency com.android.application to v8.3.0-alpha09 2023-10-20 01:48:36 +00:00
renovate[bot] e620832e33 Update dependency com.google.android.material:material to v1.11.0-beta01 2023-10-19 23:03:41 +00:00
renovate[bot] f82e4af0e3 Update dependency com.android.application to v8.3.0-alpha08 2023-10-19 20:36:34 +00:00
FooIbar 02baa793f8 Release 1.8.10.0
Releases / Build (push) Has been cancelled
2023-10-19 21:31:08 +08:00
FooIbar 9311a9e7f9 Implement download timeout 2023-10-19 21:15:55 +08:00
renovate[bot] 00a60ad6ab Update actions/checkout digest to b4ffde6 2023-10-19 11:51:36 +00:00
FooIbar e740c25211 Implement LoadStateAdapter for append/prepend load 2023-10-19 19:51:19 +08:00
FooIbar 79c6b63c56 Refactor image download and improve retry strategy 2023-10-19 14:50:22 +08:00
FooIbar bc914566dd Fix progress not showing after clicking error tip 2023-10-19 14:50:22 +08:00
FooIbar 2c6ccfb23d Enable showGalleryPages by default 2023-10-19 14:50:22 +08:00
FooIbar 646767e712 Add back cronet option for debugging 2023-10-19 14:50:22 +08:00
FooIbar 754b14d349 Simplify Java toolchain configuration 2023-10-19 14:50:22 +08:00
renovate[bot] 38c18149aa Update dependency dev.chrisbanes.compose:compose-bom to v2023.11.00-alpha02 2023-10-19 00:42:42 +00:00
renovate[bot] 35b448a274 Update dependency androidx.recyclerview:recyclerview to v1.3.2 2023-10-18 22:25:52 +00:00
renovate[bot] c1e63b5a5d Update androidx.room to v2.6.0 2023-10-18 18:31:20 +00:00
FooIbar 37f984829a Update docs to explain flavors (#64)
* Update README.md

* Update zh-cn.md
2023-10-18 09:45:33 +00:00
FooIbar 65942021d0 Kotlin 1.9.20-RC and Java 21 2023-10-18 02:06:09 +08:00
FooIbar ecef20d52b Use stable version of ktlint since 1.0.1 is out 2023-10-18 00:31:19 +08:00
FooIbar 1a092bb200 Fix total pages not updating for new archives 2023-10-18 00:22:37 +08:00
FooIbar a880f801fb Skip buildProvider if the activity will restart 2023-10-18 00:22:37 +08:00
FooIbar f00360e81c Fix stable channel update check 2023-10-18 00:06:10 +08:00
FooIbar 1737163364 Use product flavors for OSS and GMS variants (#62)
* Use product flavors for OSS and GMS variants

* Move code to kotlin directory

* Temporarily disable lint

* Fix CI

* Fix release

* Tweak release asset names
2023-10-17 10:11:59 +00:00
FooIbar f9a3c3d266 Try to fix recyclerview disappearing after refresh (#61)
* Try to fix recyclerview disappearing after refresh

* Another attempt

* Workaround: Use RefreshLayout's progress indicator
2023-10-17 04:49:20 +00:00
FooIbar 7db04e4359 Fix unable to show content in cutout area 2023-10-16 00:28:24 +08:00
FooIbar 934629d829 Add lint check for marshmallow (#58)
* Add lint check for marshmallow

* Treat some warnings as errors

* Fix attribute unused on older versions
2023-10-15 09:16:50 +00:00
FooIbar cd65bb057d Fix inlined constants on older versions 2023-10-15 15:45:00 +08:00
FooIbar f2d94f1ed6 Use product flavors to provide marshmallow support (#57)
* Use product flavors to provide marshmallow support

Fix crashing when opening archives

* Fix artifacts upload

* Change artifact names

* Fix app updater
2023-10-14 19:14:19 +00:00
FooIbar e29281ce39 Generate and upload native debug symbols 2023-10-15 01:09:44 +08:00
FooIbar 1e719a8738 Use local variable instead 2023-10-15 00:05:43 +08:00
FooIbar 92926487b6 Check if the image is GIF before doing mmap 2023-10-14 21:30:10 +08:00
FooIbar 69f744131a Use custom decoder factory instead
`ImageDecoderDecoder.Factory` is only applicable for animated images.
2023-10-14 19:59:17 +08:00
FooIbar 83cd9bdd00 Revert "Remove coil-gif library"
This reverts commit 1130f4f114.
2023-10-14 19:59:17 +08:00
FooIbar 1130f4f114 Remove coil-gif library
We only use coil for thumbnails, which are all static, and
`ImageDecoderDecoder` is only applicable for animated images.
2023-10-14 15:25:33 +08:00
FooIbar d99d9008dd Do not modify the original file when rewriting GIF
Make it a decode-time thing.
2023-10-14 15:25:33 +08:00
FooIbar 78f61b2396 Tweak the error message of getting a blank page 2023-10-14 14:48:47 +08:00
FooIbar 32829bbf15 Implement image subsampling on API < 28 2023-10-13 15:36:28 +08:00
FooIbar a2d3046070 Fix unable to hide hentaiverse notification
Resolve #51
2023-10-13 00:10:29 +08:00
FooIbar 95641e398a Refactor download delay to mitigate request flood 2023-10-12 22:30:53 +08:00
FooIbar 97c39d07e8 Update e-hentai / exhentai API URLs 2023-10-12 21:16:10 +08:00
FooIbar 14fa2fab71 Remove GIF support on API < 28 2023-10-12 19:29:57 +08:00
FooIbar ef8c31ae52 Upgrade ndk to 26.1.10909125
Fix ndk-stack
2023-10-12 15:04:29 +08:00
FooIbar 353080867a Build x86_64 apk for emulator 2023-10-12 14:29:39 +08:00
FooIbar 4d09b895d4 Refactor areContentsTheSame to compare entire objects 2023-10-11 01:08:46 +08:00
FooIbar 6c00d66334 Cleanup sad panda check 2023-10-10 22:43:35 +08:00
FooIbar 77de187207 Refine GalleryPageApiParser exception handling 2023-10-10 22:38:35 +08:00
FooIbar 79eec13856 Wrap exceptions thrown by native methods 2023-10-10 21:59:37 +08:00
FooIbar 562c6fa9a4 Fix tag database download on API < 24 2023-10-10 19:06:58 +08:00
FooIbar 9fa3ddd254 Downgrade minSdk to 23 (#48)
* Revert "Drop webp icon since minSdk is 28"

This reverts commit 9f77a4ce0a.

* Upgrade AGP and use K2 lint

* Enable core library desugaring and minSdk 23

* Fix some new API usage

* Add fallback image decoder for API < 28
2023-10-08 10:04:40 +00:00
FooIbar d11a0b9b8a Move AGP version to version section 2023-10-06 23:15:47 +08:00
FooIbar 41cf3fbe89 Cleanup version catalog 2023-10-06 15:54:43 +08:00
FooIbar ac12ab93e5 Add compose compiler to version catalog for renovate 2023-10-06 15:02:20 +08:00
FooIbar 8b4e978cf4 Migrate to version catalog 2023-10-06 14:39:47 +08:00
FooIbar c15f22400e Migrate to auto-mirrored icons 2023-10-05 23:36:07 +08:00
FooIbar 1773116313 Fix spotless not working 2023-10-05 23:23:39 +08:00
renovate[bot] 62424fbad1 Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.0-alpha13 2023-10-05 11:39:49 +00:00
renovate[bot] d07bd1aa29 Update ktor to v2.3.5 2023-10-05 10:25:27 +00:00
FooIbar 8433df09e3 Update compose-bom to v2023.11.00-alpha01 2023-10-05 15:11:35 +08:00
FooIbar 0634898cb6 Update gradle wrapper 2023-10-05 15:11:01 +08:00
renovate[bot] 2ebafc3eeb Update dependency gradle to v8.4 2023-10-05 05:59:33 +00:00
FooIbar 8288559e44 Drop GIF workaround on API 34 and above
https://android.googlesource.com/platform/frameworks/base/+/2be87bb707e2c6d75f668c4aff6697b85fbf5b15
2023-10-05 13:30:33 +08:00
renovate[bot] 2caf3f0174 Update navigation to v2.7.4 2023-10-05 04:36:23 +00:00
renovate[bot] e88e48906d Update dependency androidx.fragment:fragment-ktx to v1.7.0-alpha06 2023-10-05 00:53:42 +00:00
renovate[bot] 8fb5153983 Update dependency androidx.collection:collection-ktx to v1.3.0 2023-10-04 23:43:31 +00:00
renovate[bot] 03725b0ab4 Update dependency androidx.activity:activity-compose to v1.8.0 2023-10-04 19:07:04 +00:00
FooIbar b0896ea465 Scroll to top after refreshing 2023-10-04 21:24:28 +08:00
FooIbar 654e9a79c5 Don't append when all results were filtered out 2023-10-04 21:04:20 +08:00
FooIbar 7f541e2623 Fix side sheet not showing after swiped off 2023-10-04 15:43:26 +08:00
FooIbar 198e905050 Change block uploader icon 2023-10-03 22:42:31 +08:00
FooIbar 3744018af8 Revert "Move PageIndicatorText to Compose"
This reverts commit 7c987ec8
2023-10-03 18:41:16 +08:00
FooIbar a21ba04e95 Initialize Settings as early as possible 2023-10-02 19:50:13 +08:00
FooIbar bf4d39421b Cleanup code 2023-10-02 16:29:04 +08:00
renovate[bot] 2e4935715f Update dependency com.squareup.okio:okio-jvm to v3.6.0 2023-10-02 03:15:22 +00:00
FooIbar cf8ce9642a Make changelog scrollable 2023-10-02 00:02:48 +08:00
FooIbar cac65d9030 Fix insets for pre API 30 2023-09-30 23:19:52 +08:00
FooIbar 4a83829dfd Fix padding for scrollable 2023-09-30 23:19:52 +08:00
FooIbar c0348484fe Fix padding for the IME 2023-09-30 23:19:52 +08:00
FooIbar 213133ab45 Fix SignInScreen drawing behind the system bars 2023-09-30 23:19:52 +08:00
renovate[bot] 8469eac18c Update plugin com.diffplug.spotless to v6.22.0 (#33)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-29 07:35:40 +00:00
FooIbar 5aaa1bb5b3 Fix spotless 2023-09-29 15:20:43 +08:00
renovate[bot] 9f500b85e6 Update plugin com.android.application to v8.1.2 2023-09-28 18:40:08 +00:00
FooIbar 0d6fd8d7ad Workaround for SideSheetDialog (#31) 2023-09-27 14:05:21 +00:00
FooIbar 7e4b6dbf31 Fix memory leak 2023-09-27 19:47:57 +08:00
renovate[bot] 8bf3f0f094 Update plugin com.mikepenz.aboutlibraries.plugin to v10.9.1 2023-09-27 01:41:01 +00:00
renovate[bot] 198acef666 Update dependency com.mikepenz:aboutlibraries-core to v10.9.1 2023-09-26 21:03:06 +00:00
FooIbar 1d0c76b2b2 Fix OOB access when no results found 2023-09-26 19:26:32 +08:00
FooIbar 567875cf00 Apply quick search in place if not in Popular (#30) 2023-09-26 08:50:07 +00:00
FooIbar 58069e8925 Fix cmake cache for CI 2023-09-26 01:35:37 +08:00
FooIbar a90cf3c72b Update issue template 2023-09-26 01:28:01 +08:00
FooIbar 2fbf226ccd Update author summary in about 2023-09-26 01:23:11 +08:00
FooIbar d674672ce2 Update libarchive to v3.7.2 2023-09-26 00:28:54 +08:00
FooIbar dab7968ce3 Update liblzma to v5.4.4 2023-09-26 00:28:54 +08:00
FooIbar 510719f890 Convert libnettle build to FetchContent 2023-09-26 00:27:48 +08:00
renovate[bot] 96b92239b0 Update plugin com.mikepenz.aboutlibraries.plugin to v10.9.1-debug01 2023-09-25 08:06:51 +00:00
FooIbar 363029cbdd Upgrade r8 (#27) 2023-09-24 15:55:51 +00:00
FooIbar 7783a8de15 Cleanup build cache for CI 2023-09-24 17:52:06 +08:00
FooIbar 26a589fc1d Add native build cache for CI (#26)
* Add rust cache for CI

* Add cmake cache for CI

* Use cache for release as well

* Add build cache for CI
2023-09-24 08:10:17 +00:00
FooIbar 4c467c304f NDK will be installed automatically 2023-09-24 03:17:12 +08:00
FooIbar 9496b9396a Upgrade ndk to 26.0.10792818 (#24) 2023-09-23 18:52:46 +00:00
FooIbar 669a9ee1e3 Use spotless for ktlint integration 2023-09-24 02:12:39 +08:00
FooIbar d08b7c2d94 Update README 2023-09-23 22:32:54 +08:00
FooIbar 534f6aa22c Merge pull request workflow to CI 2023-09-23 19:13:43 +08:00
FooIbar 8ea731159f Prevent unnecessary regeneration of BuildConfig 2023-09-23 18:59:29 +08:00
renovate[bot] 7704fa9318 Update plugin com.mikepenz.aboutlibraries.plugin to v10.9.0 2023-09-23 09:34:12 +00:00
renovate[bot] d0e3de7121 Update dependency com.mikepenz:aboutlibraries-core to v10.9.0 2023-09-23 07:37:51 +00:00
renovate[bot] 40af1862a0 Update actions/checkout digest to 8ade135 2023-09-23 05:50:16 +00:00
FooIbar 33e24aa46c Fix CI for renovate 2023-09-23 13:49:34 +08:00
FooIbar 5a97561389 Upgrade gradle wrapper 2023-09-22 16:24:48 +08:00
FooIbar 26701d84f7 Cleanup redundant .gitignore 2023-09-22 15:56:27 +08:00
FooIbar d13b09aaf3 Fix swipe-to-refresh 2023-09-22 15:46:57 +08:00
FooIbar 6a65b61785 Store SearchLayout state in ViewModel 2023-09-22 15:02:18 +08:00
FooIbar 2b8bfab44b Fix minRating advanced search 2023-09-22 13:41:22 +08:00
FooIbar b9e1d79be6 Switch to branch automerging 2023-09-21 21:33:21 +08:00
renovate[bot] 0f9e1819c1 Update room_version to v2.6.0-rc01 2023-09-21 10:23:31 +00:00
renovate[bot] eda97347fc Update nav_version to v2.7.3 2023-09-21 08:09:21 +00:00
renovate[bot] 2bd24b6a8b Update dependency com.google.android.material:material to v1.11.0-alpha03 2023-09-21 04:56:54 +00:00
renovate[bot] 8306342ab6 Update dependency androidx.fragment:fragment-ktx to v1.7.0-alpha05 2023-09-21 00:45:03 +00:00
renovate[bot] 6b6ba91721 Update dependency androidx.activity:activity-compose to v1.8.0-rc01 2023-09-20 22:32:00 +00:00
FooIbar 68155e6a6b Refine modify favorites dialog layout 2023-09-16 18:36:44 +08:00
FooIbar bfbceefef2 Fix respectDefaultWidth not working 2023-09-16 17:08:13 +08:00
FooIbar 21b39135fe Tweak modify favorites dialog layout 2023-09-15 21:10:51 +08:00
FooIbar 6adc7f7221 Add option to disable category color harmonization 2023-09-13 20:21:06 +08:00
FooIbar 6bfa8d1ea4 Fix crashing when reordering downloads 2023-09-10 21:18:23 +08:00
FooIbar 6f22269d93 Disable swiping and dragging when in selection mode 2023-09-10 21:18:05 +08:00
FooIbar b68dc98ae0 Fix drawer lock states saving 2023-09-10 19:58:12 +08:00
FooIbar f9ad1f724b Don't show search fab when search view is expanded 2023-09-10 18:30:46 +08:00
FooIbar afd22635bd Update releases.yml 2023-09-10 16:41:46 +08:00
FooIbar a9f9706be3 Migrate from intFromStrPref to intPref 2023-09-09 17:39:29 +08:00
FooIbar 41f35498fe Update syn to v2.0 2023-09-09 16:06:46 +08:00
FooIbar 58628b6556 Update jni_fn to v0.1.2 2023-09-09 16:05:17 +08:00
FooIbar 875858618e Maintenance mode 2023-09-08 21:39:56 +08:00
FooIbar 70b7bf390a Sync with 1.8.9.x versionCode 2023-09-08 18:24:00 +08:00
FooIbar de3d6de4cf Revert "Sync rootProjName with repo name"
This reverts commit de9fa5be5a.
2023-09-08 18:01:58 +08:00
FooIbar e3b4caa37d Ignore expunged torrents 2023-09-08 17:15:46 +08:00
renovate[bot] f8e19f7772 Update paging to v3.2.1 2023-09-08 16:52:03 +08:00
renovate[bot] ea5dd8b1bf Update nav_version to v2.7.2 2023-09-08 16:51:29 +08:00
renovate[bot] bd7db1a016 Update dependency io.arrow-kt:arrow-stack to v1.2.1 2023-09-08 15:30:07 +08:00
renovate[bot] 02ac048d74 Update dependency androidx.lifecycle:lifecycle-process to v2.6.2 2023-09-08 15:26:41 +08:00
renovate[bot] 9a3c9b9cbd Update dependency androidx.fragment:fragment-ktx to v1.7.0-alpha04 2023-09-08 14:55:29 +08:00
renovate[bot] 80c77c740e Update dependency androidx.core:core-ktx to v1.12.0 2023-09-08 14:55:05 +08:00
renovate[bot] 0f62662963 Update dependency androidx.collection:collection-ktx to v1.3.0-rc01 2023-09-08 14:54:23 +08:00
renovate[bot] 576d6515db Update dependency androidx.activity:activity-compose to v1.8.0-beta01 2023-09-08 14:53:55 +08:00
FooIbar 30d29bfa80 Fix torrent parsing 2023-09-08 14:52:04 +08:00
FooIbar c7c0446f23 Tweak ParseException 2023-09-08 14:49:28 +08:00
FooIbar 134777584d Merge branch 'Ehviewer-Overhauled:1.8.x.x' into 1.8.x.x 2023-09-06 16:52:10 +08:00
renovate[bot] 0b64eadb0c Update actions/checkout action to v4 2023-09-04 16:52:00 +00:00
FooIbar ea93960bf4 Fix pages fields in AdvancedSearch 2023-09-01 19:30:21 +08:00
renovate[bot] 71032e3010 Update ktor to v2.3.4 2023-08-31 21:07:43 +00:00
FooIbar d6a3a351f5 Fix UI not updating after download state changed 2023-08-30 16:37:37 +08:00
FooIbar a574bca48e Kotlin 1.9.10 2023-08-30 16:22:18 +08:00
FooIbar 785faf1bf4 Upgrade ndk to r26 RC 1 2023-08-30 16:20:32 +08:00
FooIbar fbcc32b3ee Merge remote-tracking branch 'upstream/1.8.x.x' into 1.8.x.x
# Conflicts:
#	app/build.gradle.kts
2023-08-25 13:28:07 +08:00
renovate[bot] 46b19a9de5 Update room_version to v2.6.0-beta01 2023-08-24 15:49:05 +00:00
renovate[bot] d2f47d51f9 Update plugin com.google.devtools.ksp to v1.9.10-1.0.13 2023-08-24 10:15:46 +00:00
renovate[bot] db380507f5 Update nav_version to v2.7.1 2023-08-24 07:03:27 +00:00
renovate[bot] 84cf67d21a Update dependency composeOptions to v1.5.2 2023-08-24 03:49:47 +00:00
renovate[bot] 8f53591800 Update dependency androidx.fragment:fragment-ktx to v1.7.0-alpha03 2023-08-24 01:47:24 +00:00
renovate[bot] 65ffd78844 Update dependency androidx.activity:activity-compose to v1.8.0-alpha07 2023-08-23 23:18:25 +00:00
renovate[bot] c00b5e6635 Update serialization to v1.6.0 2023-08-22 23:06:02 +00:00
FooIbar 8160991086 Don't respond to touch events when scrolling 2023-08-22 19:27:44 +08:00
FooIbar f0c933d97a Replace deprecated method 2023-08-22 18:55:39 +08:00
renovate[bot] 3fb8d3e73b Update plugin com.android.application to v8.1.1 2023-08-22 06:12:17 +00:00
renovate[bot] d8efff9921 Update accompanist_version to v0.32.0 2023-08-21 14:07:10 +00:00
FooIbar 32170688b6 Support clearing image cache for specific gallery 2023-08-21 18:39:35 +08:00
FooIbar ca74bfe2e4 Merge branch 'Ehviewer-Overhauled:1.8.x.x' into 1.8.x.x 2023-08-17 22:52:55 +08:00
renovate[bot] 41767f0fd7 Update dependency gradle to v8.3 2023-08-17 14:50:21 +00:00
FooIbar c62ca4dd23 Fix DownloadsScene crash
java.lang.LinkageError: Method com.hippo.ehviewer.databinding.ActivityMainBinding com.hippo.ehviewer.ui.scene.DownloadsScene.getBinding() overrides final method in class Lcom/hippo/ehviewer/ui/scene/SearchBarScene
2023-08-17 20:24:34 +08:00
FooIbar 9c5eddb903 Remove embedded Cronet library 2023-08-17 17:51:44 +08:00
FooIbar 4a0df07805 Set User-Agent in CronetEngine.Builder 2023-08-17 00:03:13 +08:00
FooIbar 37a3edd44b Revert "Add quichint"
This reverts commit 4e427f27
2023-08-16 23:50:20 +08:00
FooIbar f117abd483 Revert "Add option for QUIC support"
This reverts commit 861c05db
2023-08-16 23:27:38 +08:00
FooIbar 4593714298 Cleanup DownloadManager 2023-08-16 23:12:18 +08:00
FooIbar a148fd6ec3 Use flow for DownloadsScene UI updates 2023-08-16 23:12:18 +08:00
FooIbar 23a44e2f3b Migrate DownloadAdapter to ListAdapter 2023-08-16 20:23:57 +08:00
FooIbar 01e03ec0f0 Support search in DownloadsScene 2023-08-15 23:03:52 +08:00
FooIbar 4594ac592c Merge branch 'Ehviewer-Overhauled:1.8.x.x' into 1.8.x.x 2023-08-15 17:44:38 +08:00
FooIbar eee0f2dded Fix FavoritesScene crash 2023-08-15 17:24:21 +08:00
FooIbar b4080302c3 Refine GalleryListParser 2023-08-15 16:22:55 +08:00
renovate[bot] 337e210b5b Update dependency com.google.android.material:material to v1.11.0-alpha02 2023-08-15 05:08:26 +00:00
FooIbar a05b9174d4 Tweak EhScreen layout 2023-08-14 23:12:39 +08:00
FooIbar aa8adfb6f7 Use LazyColumn in showSelectItem() 2023-08-14 23:10:32 +08:00
FooIbar 1034fcd7b0 Fix favorite button color 2023-08-14 21:11:55 +08:00
FooIbar 69a26aeedb Implement swipe-to-remove in Downloads and Favorites 2023-08-14 21:11:55 +08:00
FooIbar dd607b41e9 Show search fab in FavoritesScene 2023-08-13 19:13:08 +08:00
FooIbar fd45319715 Fix toplist 2023-08-13 00:14:44 +08:00
FooIbar c34ba56896 Start a new fragment if search was performed from Popular or Toplist 2023-08-12 22:47:20 +08:00
FooIbar 6e00a4133c Fix nav checked item 2023-08-12 17:51:52 +08:00
FooIbar 9924b6ec0b Cleanup layout 2023-08-11 13:49:16 +08:00
FooIbar 4d09504abc Fix swipe-to-refresh 2023-08-11 13:49:15 +08:00
FooIbar f901bd2336 Fix navigation transitions 2023-08-11 13:49:13 +08:00
FooIbar 187c35fe88 Fix "Copied to clipboard" message not showing in API 32 and lower 2023-08-11 13:48:51 +08:00
FooIbar cc94bed772 Fix error message when no hits found 2023-08-11 13:48:51 +08:00
FooIbar 8c9cdf46db Remove predictive back support
Reland after #1322 resolved
2023-08-11 13:48:51 +08:00
FooIbar 09dd200655 Migrate right drawer to side sheet 2023-08-11 13:38:17 +08:00
FooIbar 38294e8cfc Fix deprecated function 2023-08-11 13:38:17 +08:00
FooIbar 87a42cf53d Revert obsolete Workaround
This reverts commit 576ff1b0

Reason for revert: Fixed in `androidx.core:core:1.12.0-beta01`.
https://android.googlesource.com/platform/frameworks/support/+/8f7e02550620919fafb4c7fef8743f8c20562945
2023-08-11 13:38:15 +08:00
renovate[bot] 2adf2c15fa Update dependency androidx.browser:browser to v1.6.0 2023-08-10 15:31:44 +00:00
renovate[bot] 99ac2a287e Update room_version to v2.6.0-alpha03 2023-08-10 13:08:56 +00:00
renovate[bot] 371ccab596 Update nav_version to v2.7.0 2023-08-10 11:39:12 +00:00
renovate[bot] 590c505f33 Update dependency androidx.fragment:fragment-ktx to v1.7.0-alpha02 2023-08-10 06:27:27 +00:00
renovate[bot] 01c2f3ea69 Update dependency androidx.core:core-ktx to v1.12.0-rc01 2023-08-10 04:29:43 +00:00
renovate[bot] 4c4eec118d Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.0-alpha12 2023-08-10 01:27:55 +00:00
renovate[bot] 310be050d8 Update dependency androidx.collection:collection-ktx to v1.3.0-beta01 2023-08-09 22:24:00 +00:00
FooIbar 181e0301aa Fix onBackPressed callbacks order 2023-08-07 08:48:21 +08:00
FooIbar a86ca2e82c Move favorite button below header 2023-08-06 22:38:34 +08:00
FooIbar 85c911cf35 Tweak GalleryDetailScene button layout 2023-08-06 22:38:34 +08:00
FooIbar db57e2dde6 Use mobile UA for Cloudflare bypass
Allow custom UA in the future?
2023-08-05 18:25:39 +08:00
FooIbar c163ad42a6 Remove experimental status of Cloudflare bypass 2023-08-05 17:14:06 +08:00
FooIbar a2af3a4652 Manually resolve Cloudflare challenge if failed to bypass 2023-08-05 17:14:06 +08:00
FooIbar 59a9448d70 Restore default favorite slot 2023-08-04 17:37:26 +08:00
FooIbar 22b87de29d Don't show cloud favorites when not logged in 2023-08-04 17:37:26 +08:00
FooIbar d98292da1d Refine Cloudflare challenges detection 2023-08-04 05:46:51 +08:00
renovate[bot] 5422c04c53 Update plugin com.android.application to v8.1.0 2023-08-03 21:39:26 +08:00
FooIbar 0a5783de89 Fix compatibility with AGP 8.1.0 2023-08-03 21:39:14 +08:00
renovate[bot] 717e2191b0 Update plugin com.google.devtools.ksp to v1.9.0-1.0.13 2023-08-03 10:20:10 +08:00
FooIbar eedc3ba5eb Drop CheckableComposeView
It was required by EasyRecyclerView
2023-08-03 10:18:22 +08:00
renovate[bot] 30ee1816df Update dependency com.squareup.okio:okio-jvm to v3.5.0 2023-08-02 18:03:57 +00:00
FooIbar 940047de11 Refine Cloudflare challenge detection 2023-08-02 17:21:40 +08:00
tarsin c0e2041efe Cleanup code 2023-08-02 10:25:09 +08:00
tarsin 8f9d2adcfe Fixup to use compose FontFamily 2023-08-02 10:19:39 +08:00
tarsin 1bfda83665 Introduce DialogState.awaitInputText 2023-08-02 10:14:41 +08:00
tarsin 39b896e1b9 Cleanup dialog api 2023-08-02 09:18:27 +08:00
tarsin cfe7ee7f9c Convert to use tachiyomi fastscroller impl 2023-08-02 08:42:55 +08:00
FooIbar 2a95d6707a Implement Context.createReaderThemeContext() 2023-08-02 07:07:30 +08:00
renovate[bot] f4fe5f1783 Update ktor to v2.3.3 2023-08-01 18:57:09 +00:00
FooIbar 10040b2923 Minor tweaks 2023-08-01 21:04:22 +08:00
tarsin 42c56616fb Apply compose compiler report plugin 2023-08-01 09:55:35 +08:00
tarsin f8c926e1cd Cleanup unused res 2023-08-01 09:38:08 +08:00
tarsin 2adf620059 Let suggestion content padding a navbar 2023-08-01 09:30:31 +08:00
tarsin 4bdc182520 Revert "Optimize suggestion data fetch"
Revert reason: Fix OOB
This reverts commit bc4c7f62a0.
2023-08-01 09:23:40 +08:00
tarsin bc4c7f62a0 Optimize suggestion data fetch 2023-08-01 09:17:41 +08:00
tarsin b2d0c59913 Fixup suggestion color 2023-08-01 09:03:58 +08:00
tarsin ceb5424772 Cleanup SuggestionProvider 2023-08-01 08:54:46 +08:00
tarsin 5a5a5da2d9 Convert SearchBarScene SuggestRV to LazyColumn 2023-08-01 08:42:15 +08:00
tarsin 0ff9b03596 Convert TaskPreferenceDialog to compose 2023-08-01 07:37:30 +08:00
FooIbar acbe4eafa9 Support clearing search history 2023-08-01 06:36:00 +08:00
FooIbar 0a5600d949 Cleanup code 2023-08-01 06:36:00 +08:00
tarsin 63a83257fe Revert category text color to onSurface 2023-07-31 13:07:37 +08:00
tarsin d115e28403 Fix DownloadsScene category 2023-07-31 12:52:08 +08:00
tarsin 3d1ae0acfc Introduce harmonizeWithRole to keep target role chroma and tone 2023-07-31 12:35:38 +08:00
tarsin a3a01065c0 Harmonize category color 2023-07-31 11:13:16 +08:00
tarsin ae33d692bf Clip category a small corner 2023-07-31 10:46:54 +08:00
tarsin 39f55d7db1 Upgrade r8 2023-07-29 20:27:03 +08:00
tarsin a86f8d6096 Upgrade accompanist to v0.31.6rc (#1301)
* Upgrade accompanist to v0.31.6rc

* Update build.gradle.kts
2023-07-29 19:14:39 +08:00
tarsin b28fc3ffc4 Make cronetRequest.execute(..., () -> R): R 2023-07-28 18:28:07 +08:00
tarsin 5d4f544b94 No more content length validation 2023-07-28 18:18:01 +08:00
tarsin b0f0686821 Convert HomeParser to use a bytebuffer 2023-07-28 17:49:41 +08:00
tarsin b7c8afe7d2 Initial fetchCompat okhttp layer 2023-07-28 17:42:17 +08:00
tarsin 02855b68e6 Initial let parser use bytebuffer 2023-07-28 17:24:42 +08:00
tarsin cf8097babc Don't truncate fc, receivedByteCount is unreliable 2023-07-28 16:54:46 +08:00
tarsin 2d5a2022b4 Convert all GalleryListParser to rust 2023-07-28 15:30:57 +08:00
tarsin 2fac22970e Fixup! Cleanup TorrentParser 2023-07-28 12:31:44 +08:00
tarsin 8c10836e6e Cleanup TorrentParser 2023-07-28 12:30:43 +08:00
tarsin 69bba560f0 Convert to use regex-lite 2023-07-28 11:56:16 +08:00
tarsin a9d5c15bd9 Fixup codestyle 2023-07-28 11:37:59 +08:00
tarsin 1d0812ddaf Fixup codestyle 2023-07-28 11:15:48 +08:00
tarsin 02bdc9c1a3 Refine rust glist parser, do not allocate subhtml 2023-07-28 10:41:44 +08:00
tarsin 6a08460e8e Fix thumbnail mode parser 2023-07-28 10:02:04 +08:00
tarsin 091ad5cf0f Cleanup GalleryListParser 2023-07-28 09:25:56 +08:00
tarsin f911e4629c Drop jnix net extension 2023-07-28 07:18:28 +08:00
tarsin 0265ddc6e1 Introduce CronetRequest.copyToChannel 2023-07-27 12:02:30 +08:00
tarsin ead2300d90 Refactor package layout 2023-07-27 11:56:22 +08:00
tarsin b3a8547527 Drop okhttptransport since most file download job use cronet directly now 2023-07-27 11:52:31 +08:00
tarsin 37a5dbd20d Fix animated CoilImageGetter 2023-07-27 11:31:54 +08:00
tarsin c20dc66db3 Fix GalleryDetailScene comment ripple 2023-07-27 10:43:46 +08:00
tarsin 31c32123c9 Drop URLImageGetter 2023-07-27 10:25:08 +08:00
tarsin 14faa38de3 Fix SegmentedButton shape 2023-07-27 07:59:02 +08:00
tarsin 4827b7e077 Convert to use SingleChoiceSegmentedButtonRow 2023-07-27 07:53:55 +08:00
renovate[bot] 2d9a17bd25 Update dependency androidx.core:core-ktx to v1.12.0-beta01 2023-07-27 07:38:36 +08:00
renovate[bot] ce3ad45c09 Update dependency dev.chrisbanes.compose:compose-bom to v2023.07.00-alpha02 2023-07-27 07:38:27 +08:00
renovate[bot] 44fcc21d9c Update paging to v3.2.0 2023-07-27 07:38:17 +08:00
renovate[bot] 5c1a6808d7 Update dependency androidx.recyclerview:recyclerview to v1.3.1 2023-07-27 07:24:58 +08:00
renovate[bot] d461b1b8b6 Update dependency composeOptions to v1.5.1 2023-07-27 07:24:44 +08:00
renovate[bot] d74b0448d5 Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.0-alpha11 2023-07-26 21:24:28 +00:00
renovate[bot] ed2a37c615 Update dependency androidx.navigation:navigation-compose to v2.7.0-rc01 2023-07-26 19:49:59 +00:00
tarsin 34c51d3115 Cleanup CronetHttpUriFetcher 2023-07-26 17:25:41 +08:00
tarsin f85d36d14d Cleanup CronetHttpUriFetcher 2023-07-26 17:17:31 +08:00
tarsin 9818f227d7 Initial Jvmheapless IO for Coil 2023-07-26 17:01:15 +08:00
tarsin 01353e05cc Cleanup CronetRequest 2023-07-26 16:16:32 +08:00
tarsin 02eaedd6a0 Lazy borrow cronet bytebuffer 2023-07-26 15:52:55 +08:00
renovate[bot] c71c872aa2 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.7.3 2023-07-25 21:52:29 +00:00
renovate[bot] d79dc0c630 Update Rust crate quick-xml to 0.30 2023-07-24 07:30:53 +08:00
FooIbar 46cf695bde Fix gallery detail error tip not showing 2023-07-22 21:45:44 +08:00
FooIbar c913a7eb9e Fix NPE in H@H archive download 2023-07-22 18:07:22 +08:00
tarsin 887c46c87f Hide LA symbols 2023-07-22 17:53:00 +08:00
tarsin e4d3501f93 Use fat lto 2023-07-22 17:18:45 +08:00
FooIbar add98bb630 Unescape gallery info fetched from API 2023-07-22 16:36:59 +08:00
tarsin 05651584dc Fixup install ndk 2023-07-22 10:56:00 +08:00
tarsin 37171840d3 Fixup install ndk 2023-07-22 10:55:06 +08:00
tarsin 4ee8fbedbf Fixup install ndk 2023-07-22 10:52:48 +08:00
tarsin 274e6a0666 Switch to ndk r26b1 2023-07-22 10:48:01 +08:00
tarsin b26686823e Do not build libarchive test 2023-07-22 10:27:50 +08:00
tarsin 64354ea511 Xz 5.4.3 2023-07-22 10:16:27 +08:00
tarsin 07cce5bbad Libarchive 3.7.0 2023-07-22 09:59:33 +08:00
FooIbar bbab9522df Fix category badge not showing 2023-07-22 06:28:51 +08:00
FooIbar 1a6d839ca0 Partly revert 0d666a6 to reduce network requests 2023-07-22 06:27:09 +08:00
FooIbar 0d666a633f Add dirname field for DownloadInfo 2023-07-21 20:36:19 +08:00
tarsin b24679adbd Update r8 2023-07-21 09:12:55 +08:00
tarsin 90b4d643d4 Cleanup dependencies 2023-07-21 08:59:18 +08:00
tarsin 469f2871f7 Revert to use quick-xml for url unescape 2023-07-21 08:54:06 +08:00
tarsin c7766de766 Strip unused features 2023-07-21 08:22:28 +08:00
tarsin 314de6a6a7 Set opt-level to 's', we need vectorize html parser 2023-07-21 07:48:01 +08:00
tarsin 9c148dd58d Set rust codegen units to 1 for release profile 2023-07-21 07:34:31 +08:00
tarsin 1ae469facc Drop obsolete workaround (#1274)
* Drop obsolete workaround

* Update build.gradle.kts
2023-07-21 07:05:51 +08:00
renovate[bot] eada6641e4 Update plugin com.google.devtools.ksp to v1.9.0-1.0.12 2023-07-21 06:19:24 +08:00
FooIbar 2eb50803b5 Fix downloads import order 2023-07-21 06:19:10 +08:00
FooIbar 29e3e876c7 Upgrade db, add foreign key for download label 2023-07-21 06:19:10 +08:00
FooIbar 1c38dbaa0b Upgrade db, add unique index for download label 2023-07-21 06:19:10 +08:00
FooIbar 31dcd349df Fix delete download not working 2023-07-20 19:00:57 +08:00
FooIbar ee504e3361 Upgrade db, extract common gallery info 2023-07-20 19:00:57 +08:00
FooIbar 11426e1c4c Upgrade db, add galleries table 2023-07-20 19:00:57 +08:00
tarsin 13346f1124 Drop explicitly copy constructor 2023-07-19 17:28:27 +08:00
FooIbar f38f3989e7 Tweak db migration, cleanup code
Keep the time field in DownloadInfo for future sort by start / complete time function
2023-07-19 07:17:21 +08:00
FooIbar 5c33ca1c9a [DO NOT USE] Refine drag and drop reordering, part 3/3 2023-07-19 07:17:21 +08:00
FooIbar 3599ffb805 [DO NOT USE] Refine drag and drop reordering, part 2/3 2023-07-19 07:17:21 +08:00
FooIbar 344cd1305c [DO NOT USE] Refine drag and drop reordering, part 1/3 2023-07-19 07:17:21 +08:00
renovate[bot] 84fe0b9cb7 Update dependency composeOptions to v1.5.0 2023-07-18 21:56:53 +00:00
tarsin e5963208b1 Fixup codestyle 2023-07-18 18:28:27 +08:00
tarsin 8bc84b404d Reimplement groupByToObserved 2023-07-18 18:23:40 +08:00
tarsin 80d75661c1 Fixup! Refactor EhFilter to fix concurrency modification 2023-07-18 18:14:58 +08:00
tarsin 1c74a422bc Refactor EhFilter to fix concurrency modification 2023-07-18 17:34:51 +08:00
FooIbar f06c2d6805 Adjust default aspect in grid mode 2023-07-18 14:22:41 +08:00
FooIbar fdbe06e8c5 Use handle instead of long press for drag 2023-07-18 07:05:13 +08:00
FooIbar 15a9c4ee45 Move to recyclerview-selection, part 2/2 2023-07-18 07:05:13 +08:00
FooIbar 38c90f35f2 Move to recyclerview-selection, part 1/2 2023-07-18 07:05:13 +08:00
renovate[bot] bf3ee4cce2 Update plugin com.mikepenz.aboutlibraries.plugin to v10.8.3 2023-07-17 15:52:59 +00:00
renovate[bot] 9553491040 Update dependency com.mikepenz:aboutlibraries-core to v10.8.3 2023-07-17 13:29:54 +00:00
tarsin 0253d92c7f Cleanup code 2023-07-16 22:28:07 +08:00
tarsin e2ac340b6f Refine SelectableGalleryInfoRecyclerView 2023-07-16 22:22:29 +08:00
tarsin 44b60185cc Refine SelectableGalleryInfoRecyclerView 2023-07-16 22:12:15 +08:00
tarsin 9b3ffd1f2c Refactor EasyRecyclerView 2023-07-16 21:59:48 +08:00
tarsin 935c8e7d15 Remove easyrv usage in GalleryCommentsScene 2023-07-16 21:15:58 +08:00
tarsin 8f5094efef Cleanup EasyRv 2023-07-16 18:45:12 +08:00
tarsin 91accb6655 Cleanup all unnecessary easyrv usage, prepare to drop easyrv 2023-07-16 18:38:54 +08:00
FooIbar 4f23cf5d8d Fix local favorite count not updating 2023-07-16 16:33:29 +08:00
FooIbar 39f76246c8 Fix favorites move from local to cloud 2023-07-16 16:33:29 +08:00
FooIbar 8ac2b027ec Remove deprecated hosts 2023-07-16 16:33:29 +08:00
FooIbar 919d22ebf1 Gradle configuration cache compatibility 2023-07-16 16:33:29 +08:00
FooIbar 14a28c681a Do db migration, drop history favorite slot trick 2023-07-15 17:39:43 +08:00
FooIbar 388bccc468 Make QuickSearch name non-null 2023-07-15 17:39:43 +08:00
FooIbar 639d94795c Check initialization of mSpiderQueen 2023-07-15 14:20:38 +08:00
FooIbar 51e2f44868 Fix swipe refresh and error tip click refresh 2023-07-15 06:44:59 +08:00
FooIbar 92b9b9879c Fix sad panda check on login 2023-07-14 18:18:58 +08:00
FooIbar c867a74aea Tweak original image download 2023-07-14 18:18:45 +08:00
FooIbar e9a1bf22b1 Tweak archive download and image limit reset 2023-07-14 18:18:45 +08:00
FooIbar 46271fb0d2 Subscribe reading mode and orientation type changes 2023-07-13 07:00:18 +08:00
renovate[bot] 7438b17366 Update dependency io.arrow-kt:arrow-stack to v1.2.0 2023-07-12 18:38:41 +00:00
FooIbar 8306640ed6 Don't intercept on Varnish errors 2023-07-12 18:33:01 +08:00
tarsin 39b7623688 Flatten layout 2023-07-12 17:21:22 +08:00
tarsin 41b46035e4 Fix DownloadsScene leak 2023-07-12 16:22:20 +08:00
tarsin 10045494b8 Fix GalleryCommentsScene binding leak 2023-07-12 16:11:28 +08:00
tarsin ea67a022d8 Fix leaking 2023-07-12 15:56:20 +08:00
tarsin 203c89e3a8 Fixup! Fix a itemTouchHelper rv leak 2023-07-12 15:51:07 +08:00
tarsin 1c88be6f82 Fix a itemTouchHelper rv leak 2023-07-12 15:47:40 +08:00
tarsin 2a19bcd73b Fix a rv adapter leak 2023-07-12 15:34:07 +08:00
tarsin 9f29594ad6 Fix paging3 dependency 2023-07-12 15:22:27 +08:00
tarsin 9677e6ac80 Add leakcanary debug impl 2023-07-12 15:20:43 +08:00
tarsin 1590ca4b80 Convert DownloadService to use a deferred DownloadManager 2023-07-12 15:04:40 +08:00
tarsin effe7530ed Fixup! suspend db query 2023-07-12 12:24:31 +08:00
tarsin 19f270e320 Fixup! suspend db query 2023-07-12 12:22:55 +08:00
tarsin 25d876f8b9 Initial suspend all db query
TODO: Cleanup DownloadManager
2023-07-12 12:21:05 +08:00
tarsin 179e470154 Make DownloadScreen scrollable 2023-07-12 11:08:02 +08:00
tarsin 5cf122c30c Suspend more db 2023-07-12 10:41:56 +08:00
tarsin 36895de83b Introduce runAssertingNotMainThread as workaround 2023-07-12 10:37:00 +08:00
tarsin fed5285a31 Suspend more db query 2023-07-12 10:30:46 +08:00
tarsin b1fbefc09e Cleanup DownloadManager 2023-07-12 10:20:41 +08:00
tarsin 2fd9982c37 Cleanup EHDB nullability 2023-07-12 10:07:26 +08:00
tarsin 89a108574a Suspend DownloadLabelDao delete 2023-07-12 10:05:24 +08:00
tarsin 28ce6ee13e Suspend DownloadDirnameDao 2023-07-12 10:01:26 +08:00
tarsin 4441ea2c5d Suspend DownloadDirnameDao load 2023-07-12 09:58:31 +08:00
tarsin 02489893ec Suspend DownloadDirnameDao update insert 2023-07-12 09:46:09 +08:00
tarsin 62c3e9290e Suspend DownloadDirnameDao deleteByKey 2023-07-12 09:35:32 +08:00
tarsin 3f53710c5b Restore allowMainThreadQueries, prepare to suspendize DownloadManager 2023-07-12 09:31:16 +08:00
tarsin 389f27dfa1 Cleanup unused dao method 2023-07-12 09:30:13 +08:00
tarsin e2d03eb928 Purify SearchDao 2023-07-12 09:26:20 +08:00
tarsin 37ec49ed69 Suspend parser 2023-07-12 09:22:24 +08:00
tarsin 4694245b51 Suspend more db queries, part of #629 2023-07-12 09:15:01 +08:00
tarsin 60b77145a9 Prepare to drop allowMainThreadQueries 2023-07-12 08:37:46 +08:00
tarsin 28a6f8cd2b Refine package layout 2023-07-12 08:34:55 +08:00
tarsin 02a3c1c54b Cleanup kt style builder 2023-07-12 08:32:43 +08:00
tarsin 09df7cc39b Cleanup kt style builder 2023-07-12 08:27:33 +08:00
tarsin a4ce60c849 Refactor package layout 2023-07-12 08:03:45 +08:00
tarsin d74f18012b Refactor package layout 2023-07-12 07:55:54 +08:00
tarsin 3609379284 Refactor package layout 2023-07-12 07:48:21 +08:00
tarsin 19f9105640 Fixup AboutScreen edgeToEdge 2023-07-11 19:43:05 +08:00
tarsin 24664d4e83 Merge EhPreviewItem and its placeholder 2023-07-11 17:42:32 +08:00
tarsin e16b5b0437 Cleanup layout 2023-07-11 16:29:02 +08:00
FooIbar 94747da3d0 Tweak the layout of the favorite dialog 2023-07-11 14:43:37 +08:00
tarsin df8c70d8e6 Move GalleryListScene to paging3 (#1245)
* Initial WIP move GalleryListScene to paging3

* Refine

* Refine

* Refine

* Refine

* Refine

* Refine

* Typesafe State

* Refine

* Refine

* Refine

* Refine

* Cleanup code

* Cleanup code
2023-07-11 10:40:03 +08:00
tarsin 7fd3361608 Refine FavoritesScene 2023-07-11 07:35:13 +08:00
FooIbar d9522e4e89 Implement automatic update checks 2023-07-11 06:53:09 +08:00
FooIbar a8c96fa7df Introduce AppUpdater 2023-07-11 06:53:09 +08:00
renovate[bot] 5570933be4 Update dependency gradle to v8.2.1 2023-07-10 15:27:02 +00:00
FooIbar fb6cb8d3fb Generate repo name at build time 2023-07-10 22:13:55 +08:00
FooIbar 4ca7b6b11c Show changelog for CI build 2023-07-10 22:13:55 +08:00
renovate[bot] 5c377d346e Update dependency com.mikepenz:aboutlibraries-core to v10.8.2 2023-07-10 19:34:30 +08:00
tarsin 1dd0eeb2ed Rename rememberMemorized to rememberInVM 2023-07-10 19:28:42 +08:00
tarsin 3f6abd6380 Cleanup rememberMemorized 2023-07-10 19:27:44 +08:00
tarsin 407c55dbdc Properly store GalleryPreviewScreen data 2023-07-10 18:36:38 +08:00
tarsin 74afa6c764 Cleanup FavoritesScene 2023-07-10 18:27:31 +08:00
tarsin 60ccd988cb Introduce lazyMut 2023-07-10 18:26:15 +08:00
tarsin 505ba6e4e3 Move some persistent data to VM as in-memory storage 2023-07-10 18:02:40 +08:00
tarsin d4d6fe56a9 Move FavouriteScene to paging3 (#1238)
* Initial move favScene to paging3

* Refine

* Refine

* Refine

* Refine

* Implement goto bottom

* Refine

* Implement Jumpto

* Refine

* Refine

* Refine

* Refine

* Refine

* Cleanup code

* Cleanup code

* Refine

* Refine

* Do load in IOContext
2023-07-10 14:00:45 +08:00
tarsin 740fff2178 Add paging3 runtime dependency 2023-07-10 08:34:47 +08:00
tarsin 54a2ea64bc Cleanup GalleryAdapter, prepare migrate to paging3 2023-07-10 08:30:38 +08:00
FooIbar 200882e13c Log details of bad response code error 2023-07-10 06:43:44 +08:00
FooIbar dac28b9b6a Refactor e-hentai API response parsing 2023-07-10 06:43:44 +08:00
renovate[bot] 7a8ae183da Update plugin com.mikepenz.aboutlibraries.plugin to v10.8.2 2023-07-09 18:32:08 +00:00
tarsin c163e24434 Refine ComposeFavScene 2023-07-09 17:14:04 +08:00
tarsin eed30084bc Refine ComposeFavScene 2023-07-09 16:55:39 +08:00
tarsin 137a51dad0 Refine ComposeFavScene SearchBar 2023-07-09 16:29:31 +08:00
tarsin b71d326620 Refine ComposeFavScene 2023-07-09 16:21:57 +08:00
tarsin 371622770b Refine ComposeFavScene 2023-07-09 16:18:23 +08:00
tarsin 2b6bcf6698 Refactor ComposeFavScene 2023-07-09 16:05:45 +08:00
tarsin bc33ea84f2 Initial load Fav source 2023-07-09 16:00:57 +08:00
tarsin 3d7210ec08 Prepare to rewrite FavScene with Compose 2023-07-09 15:40:37 +08:00
tarsin e7bed5f5eb Remove redundant TODO 2023-07-09 15:29:03 +08:00
tarsin a3237a024e Cleanup ComposeFragment 2023-07-09 15:28:29 +08:00
tarsin 662fc6859b Fix GalleryPreviewScreen page load 2023-07-09 13:27:20 +08:00
tarsin 5bb61959bd Add jumping support 2023-07-09 12:52:57 +08:00
tarsin 8ae75101ef Add LazyGridScrollbar for preview jump 2023-07-09 11:54:13 +08:00
tarsin 95fcab0980 Inline LazyColumnScrollbar 2023-07-09 11:36:37 +08:00
tarsin d2b141e218 Fix jump crash 2023-07-09 11:21:10 +08:00
tarsin 73eb91f4b4 Cleanup GalleryPreviewScreen 2023-07-09 10:49:43 +08:00
tarsin 61c51f8882 Fix MergeInterceptor 2023-07-09 10:49:00 +08:00
tarsin cb9f36dd52 Rewrite GalleryPreviewScreen Pager, Resolve #1152 2023-07-09 10:14:13 +08:00
tarsin 80b0203af2 Fix ci failed 2023-07-09 08:16:48 +08:00
tarsin 66155eed1c Fix crash when r8 shrink aboutlibraries json 2023-07-09 07:36:39 +08:00
renovate[bot] 144e8a2951 Update dependency com.mikepenz:aboutlibraries-core to v10.8.1 2023-07-09 06:48:37 +08:00
renovate[bot] 7c1f521960 Update plugin com.mikepenz.aboutlibraries.plugin to v10.8.1 2023-07-08 22:01:29 +00:00
tarsin dbe528269d Fix FavoritesScene crash again 2023-07-08 20:25:33 +08:00
tarsin 9b843e84b2 Fix FavoritesScene crash 2023-07-08 19:49:22 +08:00
tarsin c9ef0e5406 Fix read download button text maxline 2023-07-08 18:49:23 +08:00
tarsin 1a49b3d00b Cleanup unused field 2023-07-08 18:41:08 +08:00
tarsin 33502c88db Fix FavDrawer ListItem style 2023-07-08 18:32:41 +08:00
tarsin 6554735c63 Make Favscene drawer aware of Settings fav changes 2023-07-08 18:07:53 +08:00
tarsin 2360e6376e Convert FavSceneDrawer to Compose 2023-07-08 17:22:20 +08:00
tarsin b1e186a5f1 Bump MCA 2023-07-08 16:24:17 +08:00
tarsin ffd2b5a342 Fixup GalleryInfoBottomSheet impl 2023-07-08 16:21:28 +08:00
tarsin d5a16f50c5 Revert GalleryInfoBottomSheet to BottomSheetDialogFragment impl for predictive back 2023-07-08 16:12:03 +08:00
tarsin 5b03e67108 Drop unused code 2023-07-08 15:39:52 +08:00
tarsin eac5b2641b Fix galleryProvider init crash 2023-07-08 15:36:03 +08:00
tarsin e46008df55 Cleanup: Move ReaderProgressIndicator to compose 2023-07-08 14:36:42 +08:00
tarsin 07b1f39796 Move ReaderProgressIndicator to compose 2023-07-08 14:34:12 +08:00
tarsin 64d3849b62 Fixup PageSlider haptic feedback 2023-07-08 14:22:53 +08:00
tarsin 9d766f47c5 Fixup PageSlider theme 2023-07-08 14:20:22 +08:00
tarsin 64841e6e3b Move PageSlider to Compose 2023-07-08 14:11:47 +08:00
tarsin 571fbabf4f Restore PageIndicator padding 2023-07-08 13:54:38 +08:00
tarsin 12c3ad6d5f Fixup tachiyomi theme 2023-07-08 13:53:21 +08:00
tarsin 7c987ec868 Move PageIndicatorText to Compose 2023-07-08 13:47:39 +08:00
tarsin 27686e952a Resolve #1227 2023-07-08 06:58:24 +08:00
renovate[bot] de06c24924 Update dependency com.squareup.okio:okio-jvm to v3.4.0 2023-07-07 20:11:02 +00:00
tarsin 4e1b4ddf28 Kotlin 1.9.0 2023-07-07 07:12:07 +08:00
FooIbar ca21b5e713 Introduce <T> Request.executeAndParseAs() 2023-07-06 06:22:46 +08:00
FooIbar 5a6a01f813 Use kotlinx.serialization for json handling 2023-07-06 06:22:46 +08:00
FooIbar 16c6353459 Show changelog for release 2023-07-05 19:31:28 +08:00
FooIbar d1623d411a Show confirm dialog before downloading updates 2023-07-05 19:31:28 +08:00
tarsin 4e4b072c16 No more UIContext needed to modifyFav 2023-07-05 11:01:35 +08:00
tarsin f2601fb672 Refine check updates 2023-07-05 10:53:36 +08:00
tarsin 125c467c1b Fixup! Support install CI apk 2023-07-05 10:48:44 +08:00
tarsin 0b95c340d6 Support install CI apk 2023-07-05 10:47:36 +08:00
tarsin 79e0b33aad Extract the apk 2023-07-05 10:19:22 +08:00
tarsin f1f7b8dc88 Add no-op token to download CI 2023-07-05 10:04:47 +08:00
tarsin e97e539f92 Get CI archive url 2023-07-05 09:22:42 +08:00
tarsin 4b562f98f0 Get workflow url 2023-07-05 09:07:04 +08:00
tarsin bd06d76656 Cleanup code 2023-07-05 08:43:02 +08:00
tarsin 38ddca2342 Initial get latest version using github restful api 2023-07-05 08:39:22 +08:00
tarsin 1119798ce8 Initial get latest commitSha using github restful api 2023-07-05 08:32:21 +08:00
tarsin 1eeab2f03d Introduce preference to use CI update channel 2023-07-05 07:51:29 +08:00
FooIbar bfbe6b8e6a Translate tags when saving to quick search 2023-07-05 07:20:50 +08:00
FooIbar 4905c955bc Max characters limit is for ASCII characters 2023-07-05 07:20:36 +08:00
FooIbar 4cf698a10a Cleanup unused res 2023-07-04 21:20:24 +08:00
FooIbar 4b66f58fd3 Support adding favorite notes 2023-07-04 21:20:24 +08:00
FooIbar 325909401c Use named constant instead of magic number 2023-07-04 21:20:24 +08:00
FooIbar 652d6a2f28 Support displaying favorite notes 2023-07-04 21:20:24 +08:00
tarsin 7672a82260 Convert non-close callbackFlow to MutableSharedFlow 2023-07-04 11:22:01 +08:00
tarsin 0f37d17bf3 Cleanup HistoryScene 2023-07-04 10:54:56 +08:00
tarsin c923d74fec Drop explicated recompose 2023-07-04 10:49:30 +08:00
tarsin 2ba8640996 Let FavouriteStatusRouter handle historyDB as well 2023-07-04 10:39:03 +08:00
tarsin 0b1a0cade8 Cleanup FavouriteStatusRouter implementation 2023-07-04 10:24:08 +08:00
tarsin 78aab4f6c7 Cleanup FavouriteStatusRouter stateFlow 2023-07-04 10:18:14 +08:00
tarsin 77502e1a7c No need to query local db fav info 2023-07-04 10:14:30 +08:00
tarsin b90f221ede Drop isFavorited field 2023-07-04 10:11:54 +08:00
tarsin ee68b421d6 Cleanup FavouriteStatusRouter 2023-07-04 10:06:10 +08:00
tarsin 88be232e70 Drop a notifyDataSetChanged 2023-07-04 09:08:29 +08:00
FooIbar da9de7a823 Truncate file to the size actually written 2023-07-04 07:08:18 +08:00
FooIbar f08e124df6 Support downloading original images in reader 2023-07-03 22:55:05 +08:00
FooIbar ce888a0982 Unwrap IOException in getReadableString() 2023-07-03 22:55:05 +08:00
tarsin f59789a11e Fixup! Only emit info being add 2023-07-03 21:04:29 +08:00
tarsin c8a3db8a3e Fixup! Workaround ComposeView in RVView 2023-07-03 20:45:01 +08:00
tarsin 23366c8ea1 Workaround ComposeView in RVView 2023-07-03 20:35:04 +08:00
tarsin 6e65afa7e2 Share DownloadManager callback flow 2023-07-03 20:24:22 +08:00
tarsin 29b81aa90e Fix GalleryInfoListItem to use produceState 2023-07-03 20:13:25 +08:00
tarsin d85090787f Fix GalleryDetailScene DownloadInfoListener callbackFlow 2023-07-03 20:09:42 +08:00
tarsin 0eb3cc9073 Cleanup DownloadInfoListener callbackFlow 2023-07-03 20:03:48 +08:00
tarsin dfd9af8ee3 Convert DownloadInfoListener to callbackFlow 2023-07-03 19:57:28 +08:00
tarsin 1120c23709 Do db migration, make DownloadLabel nonnull 2023-07-03 18:46:43 +08:00
tarsin 975421044c Cleanup toMutable* usage 2023-07-03 18:37:50 +08:00
tarsin 802b63ed5a Cleanup DownloadManager nullability 2023-07-03 18:33:13 +08:00
tarsin d32a0d6b7f Cleanup DownloadManager 2023-07-03 18:32:20 +08:00
tarsin 0c5a1a6533 Cleanup DownloadManager 2023-07-03 18:30:35 +08:00
tarsin 61796f4cd3 Cleanup DownloadManager init code 2023-07-03 18:22:06 +08:00
tarsin 80726a7ff0 Cleanup DownloadManager 2023-07-03 18:04:34 +08:00
tarsin a2fdcc57f8 Cleanup DownloadManager 2023-07-03 17:57:05 +08:00
tarsin bffb32eafd Reduce allocation 2023-07-03 17:40:44 +08:00
tarsin 2c452d236c Cleanup mAllInfoMap init code 2023-07-03 17:37:58 +08:00
tarsin e7e036f7c6 Convert mAllInfoMap to LinkedHashMap and do query in composer thread 2023-07-03 17:25:28 +08:00
tarsin fbb702cacd Fix Deferred implementation, Resolve #1213 2023-07-03 17:13:12 +08:00
FooIbar 598438a5f9 Avoid rust jni log spam 2023-07-03 16:34:05 +08:00
FooIbar 24a1c94f04 Wrap exception as IOException in CloudflareInterceptor 2023-07-03 15:51:51 +08:00
tarsin e4e053a49b Clip CircularLayout item ripple 2023-07-03 10:29:43 +08:00
tarsin 8c0e217117 Refactor showSelectItemWithIcon layout 2023-07-03 10:24:31 +08:00
tarsin 174e899ead Fix cannot remove localFav 2023-07-03 09:23:28 +08:00
tarsin 84cc915d95 Avoid cascade column 2023-07-03 09:01:57 +08:00
tarsin 7668f568f0 Refactor to use ListItem 2023-07-03 08:38:40 +08:00
tarsin 35a7cc2067 Unify isCronetSupported 2023-07-03 08:09:37 +08:00
tarsin efa0823293 Don't respect cache headers, Resolve #1211 2023-07-03 07:56:28 +08:00
FooIbar 1473f6326d Refactor addToFavorites() 2023-07-03 07:16:44 +08:00
FooIbar 861c05db44 Add option for QUIC support 2023-07-03 07:16:44 +08:00
tarsin 09b92cc709 Reduce allocations 2023-07-02 12:46:18 +08:00
tarsin a2d9113be2 Remove unused defensive code 2023-07-02 12:42:02 +08:00
tarsin a64c7d9f22 Make GalleryTagGroup groupName immutable and nonnull 2023-07-02 12:09:09 +08:00
tarsin 97090a4dc6 Refactor out GalleryTag 2023-07-02 12:01:21 +08:00
tarsin cf2c5b7e82 Drop snapshot maven 2023-07-02 11:25:02 +08:00
tarsin 6ee5b636c2 Upgrade dependencies 2023-07-02 11:04:08 +08:00
tarsin de9fa5be5a Sync rootProjName with repo name 2023-07-02 10:51:29 +08:00
FooIbar ffc3693674 Use the same UA for ehRequest and cronetRequest 2023-07-02 07:20:46 +08:00
FooIbar a3abd224e8 Use interceptor to handle cookies instead 2023-07-02 07:20:46 +08:00
FooIbar ac680afad3 Add cookie header and handle redirect for cronetRequest 2023-07-02 07:20:46 +08:00
FooIbar 52c21319d6 Move error tip below header in gallery detail 2023-07-01 20:00:24 +08:00
FooIbar 7b38171a9d Replace cascade if with when 2023-07-01 20:00:24 +08:00
tarsin c9ef60db67 Regen gradle wrapper 2023-07-01 16:28:20 +08:00
FooIbar aff75c2dec Add option for bypassing Cloudflare 2023-07-01 14:56:27 +08:00
FooIbar 63fcca3c7d Bypass Cloudflare with webview 2023-07-01 14:56:27 +08:00
tarsin 2e3a148c7a Convert to typesafe FilterMode 2023-07-01 12:46:03 +08:00
tarsin a7b239f0b9 Drop unused coroutineScope 2023-07-01 12:21:46 +08:00
tarsin 227cbd6fba Cleanup EhDB nullability 2023-07-01 12:08:18 +08:00
tarsin 8d19dffa01 Ensure Filter DB modification never on main thread 2023-07-01 12:07:23 +08:00
tarsin f3e1400d4c Refine EhFilter API 2023-07-01 11:37:00 +08:00
tarsin ef891ea23d Cleanup EhFilter 2023-07-01 11:29:10 +08:00
tarsin 043a1d8fe7 Cleanup with HtmlCompat.parseAsHtml 2023-07-01 11:21:24 +08:00
tarsin e428695052 Cleanup filter 2023-07-01 11:07:29 +08:00
tarsin 459dd50e91 Cleanup filter 2023-07-01 10:58:36 +08:00
tarsin 4c286a279f Fix FilterScreen nullability 2023-07-01 10:53:36 +08:00
tarsin 88c394480e Upgrade DB, Filter nullability changes 2023-07-01 10:51:13 +08:00
tarsin cdd5585124 Cleanup EhFilter 2023-07-01 10:46:34 +08:00
tarsin b0346b4e9d Revert guava 2023-07-01 10:30:09 +08:00
tarsin beecd6b109 Update r8 2023-07-01 10:08:58 +08:00
tarsin 54d7abd880 Update guava 2023-07-01 10:08:14 +08:00
FooIbar 9f77a4ce0a Drop webp icon since minSdk is 28 2023-07-01 07:12:49 +08:00
renovate[bot] 754a20ee3c Update dependency gradle to v8.2 2023-06-30 22:45:38 +00:00
FooIbar b5b096aeae Add option to force e-hentai thumb server 2023-06-30 17:42:30 +08:00
FooIbar d0ecac07aa Lofi domain is deprecated 2023-06-30 17:42:30 +08:00
FooIbar 67e6387fa8 Fix settings profile cookie since 55b3753 2023-06-30 15:47:17 +08:00
FooIbar bc34f25a3c Exclude some libraries from aboutLibraries 2023-06-30 15:47:17 +08:00
FooIbar afc38c2de8 Add back press handler in select site screen 2023-06-30 15:47:17 +08:00
renovate[bot] c020af3083 Update dependency com.pinterest:ktlint to v0.50.0 2023-06-30 00:41:16 +00:00
renovate[bot] 3f509f8c11 Update dependency com.google.android.material:material to v1.10.0-alpha05 2023-06-29 22:14:41 +00:00
renovate[bot] 75eb25db18 Update chunker to v4 2023-06-30 00:24:09 +08:00
renovate[bot] 79d173cb0d Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.7.2 2023-06-29 21:43:44 +08:00
tarsin f3eb2c8c17 Optimize GalleryInfo composer path calculation 2023-06-29 19:02:24 +08:00
FooIbar d15685e1ca Add rustfmt and clippy check to CI 2023-06-29 16:06:54 +08:00
renovate[bot] 11535f7d7b Update nav_version to v2.7.0-beta02 2023-06-29 07:53:43 +08:00
FooIbar 0b526040a3 Add interceptor to store cookies for cronet 2023-06-29 07:11:57 +08:00
FooIbar 15696f4e6e Fix duplicate getUConfig() call 2023-06-29 07:11:57 +08:00
FooIbar 0c560518c8 Clean remaining code from 2d20da7 2023-06-29 07:11:57 +08:00
renovate[bot] 302cba9f61 Update ktor to v2.3.2 2023-06-28 19:41:46 +00:00
tarsin 3a33a2425f Cargo fmt 2023-06-28 18:27:22 +08:00
tarsin a5df907bbe Cargo clippy fix 2023-06-28 18:26:29 +08:00
tarsin 6879a02a63 Fix AdvancedSearch pages behaviour 2023-06-28 13:31:35 +08:00
tarsin d86d72212f Fix AdvancedSearch minrating textfield behaviour 2023-06-28 13:21:00 +08:00
tarsin de528ec277 Fix AdvancedSearch minrating textfield style 2023-06-28 12:43:47 +08:00
FooIbar 6f41ddda51 Don't retry on unrecoverable errors 2023-06-27 23:09:57 +08:00
tarsin ffaf8b9c46 Fix SearchLayout insetPadding 2023-06-27 22:18:49 +08:00
tarsin aab1c12aed Fix SearchLayout padding 2023-06-27 22:10:01 +08:00
tarsin 4e427f2761 Add quichint 2023-06-27 22:04:37 +08:00
tarsin 2a6247a89d Cleanup unused res 2023-06-27 22:04:00 +08:00
tarsin 62c3e9fd99 Restore pageRange advanced options 2023-06-27 22:00:17 +08:00
tarsin 1690651428 Restore three more advanced options 2023-06-27 21:44:05 +08:00
tarsin ea95b1c525 Fixup! Restore minRating advanced search 2023-06-27 19:58:29 +08:00
tarsin f67c80a3a3 Restore minRating advanced search 2023-06-27 19:55:15 +08:00
tarsin 6c1ffeacb2 Fixup! Initial compose advanced search 2023-06-27 19:07:26 +08:00
tarsin 753206c2fb Initial compose advanced search 2023-06-27 19:05:59 +08:00
tarsin 2ae8265d05 Fix SearchLayout title font 2023-06-27 18:37:45 +08:00
tarsin 7df7a783f6 Initial migrate SearchLayout to Compose
TODO: advanced options
2023-06-27 18:33:08 +08:00
tarsin 2829d081cc Cleanup with bundleOf ext 2023-06-27 12:20:05 +08:00
tarsin bb3a88813b Cleanup BaseScene 2023-06-27 12:00:27 +08:00
tarsin 2da8cfde01 Unify MainActivity nav behaviour 2023-06-27 11:54:09 +08:00
tarsin 2e23167e94 Enable httpcache for cronet 2023-06-24 19:03:16 +08:00
tarsin 9131f6f8cb Cleanup ListUrlBuilder 2023-06-24 16:48:19 +08:00
tarsin a77a1fd3e1 Drop UrlBuilder 2023-06-24 16:15:52 +08:00
tarsin 9dfc1324ad Flush cookies after signin 2023-06-24 16:05:35 +08:00
tarsin c84e86a5a1 Cleanup HttpUrl usage 2023-06-24 16:02:18 +08:00
tarsin be867d5d07 Cleanup HttpUrl usage 2023-06-24 15:49:00 +08:00
tarsin 5457044dd5 Cleanup a HttpUrl usage 2023-06-24 15:44:29 +08:00
tarsin be2330f1b6 Prepare to drop UrlBuilder 2023-06-24 15:36:31 +08:00
tarsin eb18e74884 Prepare to drop UrlBuilder 2023-06-24 15:28:37 +08:00
renovate[bot] fd05d03bca Update plugin com.mikepenz.aboutlibraries.plugin to v10.8.0 2023-06-24 07:02:05 +08:00
renovate[bot] 69db5bd400 Update dependency com.mikepenz:aboutlibraries-core to v10.8.0 2023-06-23 22:00:15 +00:00
FooIbar a87d720ae3 Support tag suggestion under operators and qualifiers 2023-06-22 18:51:27 +08:00
FooIbar 7b81a9aa50 Cleanup code 2023-06-22 15:40:20 +08:00
FooIbar b07fd2bc4f Remove all unnecessary jvm annotations 2023-06-22 15:40:20 +08:00
FooIbar 3c64486c3b Convert all unifile classes to kotlin 2023-06-22 15:40:20 +08:00
FooIbar 8cb4045589 Rename .java to .kt 2023-06-22 15:40:20 +08:00
FooIbar 7edac11d53 Rename UniFile.kt to UniFileExtensions.kt 2023-06-22 15:40:20 +08:00
tarsin 838f8ac98c Fix galleryTag layout 2023-06-22 11:39:27 +08:00
tarsin 3a57d43aa4 Fixup! Kotlin 1.9.0-RC 2023-06-22 10:26:42 +08:00
tarsin 69870803f7 Kotlin 1.9.0-RC 2023-06-22 10:23:27 +08:00
renovate[bot] eef5e5485a Update dependency androidx.paging:paging-compose to v3.2.0-rc01 2023-06-22 10:03:31 +08:00
renovate[bot] ebbe26e5e6 Update room_version to v2.6.0-alpha02 2023-06-22 10:01:56 +08:00
FooIbar 0cd83d3e4c Convert all legacy view classes to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 1c07c61307 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar b602fa1204 Convert AddDeleteDrawable, HandlerDrawable and WrapDrawable to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 273bed9ba1 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar 028405ecce Convert MarginItemDecoration, SimpleSmoothScroller and WindowInsetsAnimationHelper to kotlin 2023-06-22 10:01:41 +08:00
FooIbar ada4158c8e Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar fc99c611a6 Convert ViewTransition and BringOutTransition to kotlin 2023-06-22 10:01:41 +08:00
FooIbar fce4a2751a Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar 557818e59f Drop DialogWebChromeClient and SimpleHolder 2023-06-22 10:01:41 +08:00
FooIbar a85f3cbe0b Do ktlint format 2023-06-22 10:01:41 +08:00
FooIbar 5d8acd9577 Drop ViewUtils 2023-06-22 10:01:41 +08:00
FooIbar e2bf887b94 Convert RadioGridGroup to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 4a0893c573 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar 839d074df1 Drop NumberUtils 2023-06-22 10:01:41 +08:00
FooIbar 9be7705050 Convert EasyRecyclerView to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 470a2baa6a Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar fb8de517a0 Drop LayoutUtils 2023-06-22 10:01:41 +08:00
FooIbar 635de44299 Convert AboveSnackerLayout to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 1d4bd9a294 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar a0b48a5cc7 Drop MathUtils 2023-06-22 10:01:41 +08:00
FooIbar eb726ccbf4 Convert SimpleGridLayout to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 3f484a49e8 Drop StringUtils and Utilities 2023-06-22 10:01:41 +08:00
FooIbar 09cd08bfaa Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar e6312a37c7 Convert LayoutManagerUtils to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 862b3e5e46 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar 0042482107 Convert SimpleHandler to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 85bc215e00 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar 2db5a749a0 Drop IOUtils 2023-06-22 10:01:41 +08:00
FooIbar 8c439b1167 Convert OSUtils to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 4eb1a4141f Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar 4f47febc06 Convert AnimationUtils, ConcurrentPool, IntIdGenerator and SimpleAnimatorListener to kotlin 2023-06-22 10:01:41 +08:00
FooIbar c61afc7a04 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar b59966e8a4 Convert FastScroller to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 457cf2e856 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar 4a7626c6f6 Convert FileUtils to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 947452e3f1 Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar b6de84c369 Drop ObjectUtils 2023-06-22 10:01:41 +08:00
FooIbar 0adc43b243 Convert ReadableTime and TextUrl to kotlin 2023-06-22 10:01:41 +08:00
FooIbar 0c9a8624bc Rename .java to .kt 2023-06-22 10:01:41 +08:00
FooIbar 09e9d3ff5a Drop JsoupUtils and LogCat 2023-06-22 10:01:41 +08:00
renovate[bot] 7240ca4eba Update dependency androidx.activity:activity-compose to v1.8.0-alpha06 2023-06-22 00:41:41 +00:00
tarsin 0a2c29a693 Adjust GalleryInfo layout 2023-06-21 10:37:05 +08:00
tarsin bba875238c Rewrite GalleryInfo layout with ConstraintLayout dsl 2023-06-21 10:28:16 +08:00
FooIbar c33248b1bf Use file hash for cover search 2023-06-21 00:52:28 +08:00
FooIbar 24431051ae Make HV event notification clickable 2023-06-20 23:03:59 +08:00
FooIbar 1758e25388 Cleanup code 2023-06-20 23:03:59 +08:00
tarsin a27e8b99d9 Fix parse e-hentai failed 2023-06-20 22:18:19 +08:00
tarsin a06e76e934 Fix proguard and do cargo fmt 2023-06-20 21:53:48 +08:00
FooIbar 14609583b5 Show favorite name in gallery list 2023-06-20 21:49:28 +08:00
FooIbar 751a89c107 Refine rust gallery list parser 2023-06-20 21:49:28 +08:00
tarsin 619c8f6deb Update r8 2023-06-20 19:54:12 +08:00
tarsin c3835b8a65 Drop unused field in GalleryInfo 2023-06-20 19:43:42 +08:00
tarsin 5fd890cf9b Cleanup code 2023-06-20 19:40:32 +08:00
tarsin 40c6135214 Cleanup code 2023-06-20 19:37:06 +08:00
tarsin cfa1ce8ab6 Cleanup code 2023-06-20 19:34:38 +08:00
tarsin 260dafcd79 Fixup bytebuffer usage 2023-06-20 19:27:12 +08:00
tarsin 73b3cd4625 Add annotations 2023-06-20 17:36:44 +08:00
tarsin d895a07583 Cleanup code 2023-06-20 17:33:48 +08:00
tarsin b84872aca2 Format code 2023-06-20 17:16:41 +08:00
tarsin 0ef4bccb7c Intercept okhttp request to cronet 2023-06-20 17:15:48 +08:00
tarsin dfc6c6a030 Let okhttp cronet converter handle cookies 2023-06-20 17:15:26 +08:00
tarsin b62f1d0c9f Add copy of okhttp cronet converter 2023-06-20 17:02:02 +08:00
tarsin 8abb5d9fb4 Add SwitchPref switch padding 2023-06-20 12:24:33 +08:00
tarsin 5f13725173 Handle #1157 [2/2] 2023-06-20 11:55:25 +08:00
tarsin 7f8b41f998 Handle #1157 [1/2] 2023-06-20 11:51:46 +08:00
FooIbar 1319060311 Drop some ViewUtils methods 2023-06-20 00:51:49 +08:00
FooIbar d1f914c2fa Convert SearchLayout to view binding 2023-06-20 00:51:49 +08:00
FooIbar 46cd1013fb Convert GalleryDetailScene to view binding 2023-06-20 00:51:49 +08:00
FooIbar 2ed516cf85 Convert SuggestionHolder to view binding 2023-06-20 00:51:49 +08:00
FooIbar 2ce55b4aef Convert GalleryCommentsScene to view binding 2023-06-20 00:51:49 +08:00
FooIbar df7071a22c Convert FavoritesScene to view binding 2023-06-20 00:51:49 +08:00
tarsin 707529972e Cleanup code 2023-06-19 11:28:54 +08:00
tarsin cf2796ebaa Add cronet temporarily for testing usage
We temporarily add cronet implementation to test the wrapper stability
Drop it when Android 14 release
2023-06-19 10:38:36 +08:00
tarsin 9611d81969 Include ktor proguard file 2023-06-19 01:37:58 +08:00
tarsin fb31f627a1 Update build.gradle.kts 2023-06-18 17:51:15 +08:00
tarsin 4741e4f858 Validate content length 2023-06-18 16:39:04 +08:00
tarsin b44ddf980b Cleanup code 2023-06-18 16:35:55 +08:00
tarsin 47b97243c3 Refine polling recycle 2023-06-18 16:33:54 +08:00
tarsin b695b06507 Pooling the bytebuffer 2023-06-18 16:30:18 +08:00
tarsin 110fb9fb59 Refine HttpEngineCoroutinizer 2023-06-18 16:13:54 +08:00
tarsin fe7c979d78 Cleanup HttpEngineCoroutinizer 2023-06-18 15:25:19 +08:00
tarsin 0d10899417 Initial support Android14 HttpEngine 2023-06-18 15:09:50 +08:00
tarsin ff89afb8c1 Fix fragment predictive back 2023-06-17 23:40:10 +08:00
tarsin 188248c634 Fix searchview predictive back 2023-06-17 23:30:01 +08:00
tarsin 0d3c942984 Fix navigation drawer predictive back 2023-06-17 23:26:22 +08:00
tarsin 2b520be693 Upgrade androidx activity 2023-06-17 23:22:34 +08:00
tarsin 4b831a2ee1 Support fragment predictive back 2023-06-17 23:19:32 +08:00
tarsin 59037d60e2 Revert "Revert "Revert "Update README.md"""
This reverts commit 61917285d5.
2023-06-17 22:01:57 +08:00
tarsin 61917285d5 Revert "Revert "Update README.md""
This reverts commit 6ccb59957d.
2023-06-17 17:56:45 +08:00
tarsin 453f77dd3e Upgrade androidx fragment 2023-06-17 17:42:18 +08:00
tarsin b40004d9ba Fix download crash on Android 14 2023-06-17 17:37:03 +08:00
tarsin f8f2d020eb Upgrade mdc for searchbar predictive back 2023-06-17 17:32:08 +08:00
tarsin 4f09de8a69 Refine warning 2023-06-17 16:51:37 +08:00
tarsin 2d20da70b8 Drop domain fronting and other network hacks since #1147 2023-06-17 16:37:51 +08:00
tarsin cf95e7b034 Upgrade compileSdk to 34 2023-06-17 15:01:33 +08:00
tarsin b5709a49f4 Fix padding 2023-06-16 20:59:46 +08:00
tarsin 75a7143442 Tweak GalleryDetailScene button layout 2023-06-16 20:57:52 +08:00
tarsin 3e47d82713 Spilt galleryList parser 2023-06-16 16:58:03 +08:00
tarsin 62f9361d25 Cleanup code 2023-06-16 16:52:19 +08:00
tarsin 3a97b93b6d Parse gid and token 2023-06-16 16:48:24 +08:00
tarsin 3b9850b90d Trim title 2023-06-16 16:26:30 +08:00
tarsin c0636e93c6 Refine rust gd parser 2023-06-16 16:22:55 +08:00
tarsin 5ff1131dee Improve home parser, avoid iter over whole text 2023-06-16 15:35:34 +08:00
tarsin 8aca589b0f Cleanup rust gd parser 2023-06-16 15:07:00 +08:00
tarsin 10f6bd4bf6 Refine rust gd parser 2023-06-16 15:01:11 +08:00
tarsin dbe84e8bda Refine rust gd parser 2023-06-16 14:29:44 +08:00
tarsin f4de403cdd Refresh libraries 2023-06-16 12:34:03 +08:00
tarsin 9024afed19 Strip unused regex feature 2023-06-16 12:21:51 +08:00
tarsin d8921377b3 Cleanup rust implementation 2023-06-16 01:35:16 +08:00
tarsin b80deafaa8 Cleanup home.rs 2023-06-16 00:22:54 +08:00
tarsin 506c63380a Spilt home.rs 2023-06-16 00:19:22 +08:00
tarsin 9e4fc2e7af Spilt torrent.rs 2023-06-16 00:16:58 +08:00
tarsin 0a8df26549 Add stub rust BaseGalleryInfo binding 2023-06-16 00:09:35 +08:00
tarsin d2bbd1b842 Tweak BaseGalleryInfo layout 2023-06-16 00:07:38 +08:00
tarsin 5974ce11a1 Build arm64 only if debug 2023-06-15 22:47:28 +08:00
tarsin 3ec700da56 Add f32 jni binding support 2023-06-15 20:33:14 +08:00
tarsin cb7b601f58 Revert "Drop fromJava impl and unused std impl"
This reverts commit 8e08a4881f.
2023-06-15 18:25:18 +08:00
tarsin 8e08a4881f Drop fromJava impl and unused std impl 2023-06-15 17:41:43 +08:00
tarsin 2fac1c8e4c Step to globally update to jni-rs 0.20.0 2023-06-15 15:43:37 +08:00
tarsin d240fb7e05 Add jnix for jni 0.21 upgrade 2023-06-15 14:43:50 +08:00
tarsin 326d378979 Cleanup package 2023-06-15 14:36:47 +08:00
tarsin 71f91bf054 Obtain jni_fn locally 2023-06-15 13:50:25 +08:00
tarsin 40e4d40683 Fix dependencies 2023-06-15 13:44:58 +08:00
tarsin c9d439e748 Obtain catch_panic, catch_panic_macros locally 2023-06-15 13:32:33 +08:00
tarsin 0ec90655c0 Specific jni-rs version 2023-06-15 13:13:47 +08:00
tarsin c6949cd09b Update rustup before compile 2023-06-15 13:00:32 +08:00
tarsin 1e47170c3b Refine rust jni parseLimit 2023-06-15 12:22:30 +08:00
tarsin b7d2e4e5da Cleanup code 2023-06-15 12:04:37 +08:00
tarsin 28c7a701ca Use jni signature generator 2023-06-15 11:56:57 +08:00
tarsin 52560b6b95 Catch rust panic 2023-06-15 11:52:02 +08:00
tarsin 1c2c18bff1 Cleanup rust binding 2023-06-15 05:06:12 +08:00
tarsin ed1603eec4 Update proguard-rules.pro 2023-06-15 04:25:36 +08:00
tarsin 2206dd85bc Use jnix 2023-06-15 04:13:01 +08:00
tarsin 62509e1500 Use lazy regex! marco 2023-06-14 20:59:11 +08:00
tarsin 56cad66aec Tweak cargo profile 2023-06-14 16:52:54 +08:00
tarsin 660d917f97 Unescape xml for favcat name 2023-06-14 16:34:10 +08:00
tarsin 0ca5572684 Do rustfmt, unescape xml 2023-06-14 16:29:39 +08:00
tarsin cbebdb542d Move torrent parser to rust 2023-06-14 16:20:18 +08:00
tarsin 84d13fe9c5 Cleanup rust code 2023-06-14 13:05:01 +08:00
tarsin c6919c39c6 Move favParser to rust 2023-06-14 12:41:44 +08:00
tarsin 92b6069654 Cleanup rust parser 2023-06-14 11:36:36 +08:00
tarsin 804dffc81a Cleanup rust parser 2023-06-14 10:30:07 +08:00
tarsin 5831761fc0 Drop logger for now 2023-06-12 15:42:42 +08:00
tarsin c887379b8b Ignore Cargo.lock as well 2023-06-12 15:39:35 +08:00
tarsin 82c92203b6 Initial refactor HomeParser with rust 2023-06-12 15:36:58 +08:00
tarsin 881948554e Cleanup buildscript 2023-06-12 12:05:05 +08:00
tarsin b0357201a5 Downgrade AGP to stable version 2023-06-12 11:58:48 +08:00
tarsin 7ddeaf6d16 Use cdylib and release profile for now 2023-06-12 11:07:11 +08:00
tarsin 942abd7dbb Add support for build rust dylib 2023-06-12 11:01:18 +08:00
tarsin 9f78b69390 Resolve #1136 2023-06-11 12:36:11 +08:00
tarsin 6e561ca9a9 Update r8 2023-06-10 17:15:55 +08:00
tarsin cec86576c1 Singletonize ReaderPreferences 2023-06-10 13:56:18 +08:00
tarsin 9eb8e984b6 Add chunker http inspector for debug usage 2023-06-09 19:00:03 +08:00
renovate[bot] 20798b5d25 Update plugin com.android.application to v8.1.0-beta05 2023-06-09 01:45:54 +00:00
tarsin 29fafdf216 Upgrade paging 2023-06-08 16:53:32 +08:00
tarsin 2fbda42e53 Cleanup CookiesDao 2023-06-08 16:26:49 +08:00
tarsin 285feb5f5b Cleanup EhCookieStore 2023-06-08 16:22:36 +08:00
tarsin cfdb8793c6 Cleanup EhCookieStore 2023-06-08 16:18:32 +08:00
tarsin 55b3753e66 Migrate cookies store to CookieManager backend 2023-06-08 16:11:48 +08:00
renovate[bot] ad04681460 Update dependency androidx.paging:paging-common-ktx to v3.2.0-beta01 2023-06-08 04:07:38 +00:00
renovate[bot] 2e132f06b7 Update nav_version to v2.7.0-beta01 2023-06-08 09:30:00 +08:00
renovate[bot] dc74266927 Update dependency androidx.core:core-ktx to v1.11.0-beta02 2023-06-07 21:39:55 +00:00
tarsin e002e15a66 Fix imagelimits dialog crash 2023-06-07 16:14:10 +08:00
tarsin e149c7065e Upgrade r8 2023-06-07 15:46:50 +08:00
tarsin 107a5ce626 Tweak package layout 2023-06-07 15:39:35 +08:00
tarsin b6d69f7a02 Tweak package layout 2023-06-07 15:36:56 +08:00
tarsin 6de6c89439 Tweak package layout 2023-06-07 15:35:16 +08:00
tarsin c1ca6dcd5d Tweak package layout 2023-06-07 15:31:33 +08:00
tarsin 223ebd6acd Flatten legacy view package 2023-06-07 15:28:47 +08:00
tarsin cc8311e116 Opt-in DataStorePreferencesPreview 2023-06-07 15:18:58 +08:00
tarsin a66f53e562 Trick to fix amoled mode 2023-06-07 15:13:08 +08:00
tarsin de699dacfe Subscribe show_tag_translations changes 2023-06-07 15:04:36 +08:00
tarsin 431007a731 Subscribe gallerysite changes 2023-06-07 15:02:24 +08:00
tarsin 797d511c2c Protect preferences need certain locales 2023-06-07 14:58:22 +08:00
tarsin 0c40be5da8 Protect preferences need signin 2023-06-07 14:55:33 +08:00
tarsin 2fc04caa05 Drop unused res 2023-06-07 14:48:23 +08:00
tarsin 78ffe38f54 Fix securityDelay pref summary 2023-06-07 14:45:19 +08:00
tarsin 8152a04155 Fix thumbResolution pref summary 2023-06-07 14:38:07 +08:00
tarsin 64b39275e2 Restore UConfigScreen applyTip 2023-06-07 14:33:21 +08:00
tarsin 23c6291cc8 Use mutableStateListOf, remove some recompose trigger 2023-06-07 14:29:22 +08:00
tarsin 60fe67b97a Restore filter tip 2023-06-07 14:22:28 +08:00
tarsin 96264a28a6 Fixup! Restore dialog to add filter 2023-06-07 13:51:47 +08:00
tarsin b86cb676d6 Restore dialog to add filter 2023-06-07 13:43:57 +08:00
tarsin a05fc8447d Restore dialog to delete filter 2023-06-07 13:28:45 +08:00
tarsin 68a617b952 FilterScreen optimize recompose 2023-06-06 23:48:14 +08:00
tarsin f18bfab35f FilterScreen appbar liftonScroll 2023-06-06 23:45:11 +08:00
tarsin c04f30a5b7 FilterScreen implementation part 4 2023-06-06 23:43:51 +08:00
tarsin 7a35d60b71 FilterScreen implementation part 3 2023-06-06 23:40:42 +08:00
tarsin cd3fa38161 FilterScreen implementation part 2 2023-06-06 23:34:33 +08:00
tarsin 6a65eabf28 FilterScreen implementation part 1 2023-06-06 23:29:55 +08:00
tarsin 0905a4dfba FilterScreen help message 2023-06-06 23:08:42 +08:00
tarsin c68c011e69 FilterScreen stub 2023-06-06 23:05:51 +08:00
tarsin a57066b6db Implement MyTagsScreen 2023-06-06 23:01:24 +08:00
tarsin 0b19cc71c9 Cleanup UConfigScreen 2023-06-06 22:48:55 +08:00
tarsin 38e6b60005 Cleanup UConfigScreen 2023-06-06 22:36:34 +08:00
tarsin 35a18ecc0f Navback after settings applied 2023-06-06 22:34:12 +08:00
tarsin 5fb2ec2fda Cleanup js apply 2023-06-06 22:31:27 +08:00
tarsin 61a2ae9431 Implement UConfigScreen 2023-06-06 22:23:36 +08:00
tarsin cba05171af Convert to use accompanist Webview 2023-06-06 21:54:25 +08:00
renovate[bot] 816d63e432 Update accompanist_version to v0.31.3-SNAPSHOT 2023-06-06 21:41:40 +08:00
tarsin 56c2e6fcde Import accompanist webview 2023-06-06 21:39:34 +08:00
tarsin d6aee24295 Remove Activity ref 2023-06-06 19:49:42 +08:00
tarsin 13b28c0f38 Introduce FINISH_ROUTE_NAME 2023-06-06 19:47:08 +08:00
tarsin 2f69b942d9 Cleanup SelectSiteScreen 2023-06-06 19:11:07 +08:00
tarsin 5977d77381 Navigate to SIGN_IN_ROUTE directly when signout 2023-06-06 19:04:01 +08:00
tarsin fe336ebf6a Fixup merge 2023-06-06 19:00:12 +08:00
tarsin c44776870e Merge LoginActivity & SettingsActivity 2023-06-06 18:54:29 +08:00
tarsin 67d5942aff Observe updateWhenRequestNewsChanges 2023-06-06 16:36:00 +08:00
tarsin 48e3b44658 Implement compose TimePickerDialog 2023-06-06 16:30:59 +08:00
tarsin 89347768b3 Fix codestyle 2023-06-06 15:50:33 +08:00
tarsin 2ed726427f Add AnimatedVisibility for preference dependency 2023-06-06 15:49:37 +08:00
tarsin 8ee20070b6 Add FilterScreen stub 2023-06-06 15:45:35 +08:00
tarsin ca9e1e93d9 Add MyTagsScreen stub 2023-06-06 15:43:28 +08:00
tarsin d9ea549b51 Add UConfigScreen stub 2023-06-06 15:38:48 +08:00
tarsin 0860bb488f [DO NOT USE] Restore ability to reset image limits 2023-06-06 15:23:53 +08:00
tarsin 8efc5401cb [DO NOT USE] Show Integer in slider 2023-06-06 14:36:29 +08:00
tarsin 13e5649948 [DO NOT USE] Add 2 missing preference 2023-06-06 14:33:08 +08:00
tarsin bb77c4ecfc [DO NOT USE] Introduce LauncherPreference 2023-06-06 14:06:07 +08:00
tarsin 3d16151bd2 [DO NOT USE] Fixup codestyle 2023-06-06 13:40:58 +08:00
tarsin 9053b200b2 [DO NOT USE] Restore copy notify 2023-06-06 01:24:31 +08:00
tarsin a450dd29f9 [DO NOT USE] Cleanup AccountPreference 2023-06-06 01:18:04 +08:00
tarsin 2f309fe9bb [DO NOT USE] Cleanup AccountPreference 2023-06-06 01:16:34 +08:00
tarsin 5f9f7d0ba7 [DO NOT USE] Restore AccountPreference 2023-06-06 01:13:59 +08:00
tarsin 6f1a08f2c7 [DO NOT USE] Introduce WorkPreference 2023-06-06 00:46:33 +08:00
tarsin 563f6810be [DO NOT USE] Merge object PreferenceTokens 2023-06-06 00:39:14 +08:00
tarsin 66ed7a04a4 [DO NOT USE] Cleanup preference default args 2023-06-06 00:38:04 +08:00
tarsin 626475a91f [DO NOT USE] Restore ability to clear redundancy 2023-06-06 00:34:11 +08:00
tarsin f99575771c [DO NOT USE] Restore ability to restore downloads 2023-06-06 00:28:21 +08:00
tarsin bc5a0f9140 [DO NOT USE] Fix to use Transition.None 2023-06-05 23:52:35 +08:00
tarsin 40958179ca [DO NOT USE] Use new navigation anim 2023-06-05 23:45:54 +08:00
tarsin d98006c2cd [DO NOT USE] Upgrade androidx navigation to 2.7.0-alpha01 2023-06-05 21:28:43 +08:00
tarsin 5134c9b378 [DO NOT USE] Observe theme changed 2023-06-05 19:04:31 +08:00
tarsin 1379953a9c [DO NOT USE] Inline setting keys 2023-06-05 18:45:14 +08:00
tarsin cc02b69020 [DO NOT USE] EhScreen implementation part 1 2023-06-05 18:42:16 +08:00
tarsin 55b4edac4d [DO NOT USE] Cleanup unused res and code 2023-06-05 18:18:27 +08:00
tarsin b56f136da0 [DO NOT USE] Introduce MutableState<T>.rememberedAccessor 2023-06-05 17:49:49 +08:00
tarsin d2b5e5ec01 [DO NOT USE] Tweak SimpleMenuPreferenceInt to receive a MutableState 2023-06-05 17:10:39 +08:00
tarsin 623ab63d8d [DO NOT USE] Introduce KMutableProperty0<T>.observed 2023-06-05 16:58:38 +08:00
tarsin 3b1beba570 [DO NOT USE] Implement DownloadScreen part 4 2023-06-05 16:05:38 +08:00
tarsin 4e6210c1f8 [DO NOT USE] Implement DownloadScreen part 3 2023-06-05 16:00:50 +08:00
tarsin 281154be7a [DO NOT USE] Implement DownloadScreen part 2 2023-06-05 15:55:07 +08:00
tarsin 9b4477766c [DO NOT USE] Implement DownloadScreen part 1 2023-06-05 15:45:02 +08:00
tarsin 53bbabab75 [DO NOT USE] Drop in-app proxy support 2023-06-05 15:32:51 +08:00
tarsin b1671eacb7 [DO NOT USE] Restore DOH support 2023-06-05 15:28:00 +08:00
tarsin b41e564411 [DO NOT USE] Import backupFav 2023-06-05 15:23:24 +08:00
tarsin 48e12154c3 [DO NOT USE] Fix db import 2023-06-05 15:17:05 +08:00
tarsin 5c25e2801c [DO NOT USE] Implement DB import 2023-06-05 14:51:12 +08:00
tarsin c2d2c1c4f6 [DO NOT USE] Implement DB export 2023-06-05 14:45:34 +08:00
tarsin 59959bb9ee [DO NOT USE] Migrate to datastore backend 2023-06-05 12:24:08 +08:00
tarsin 3664ca846c [DO NOT USE] Drop androidx preference, drop rikkax preference 2023-06-05 12:17:55 +08:00
tarsin 8c6d28d6e2 Make AndroidPreferenceStore use Settings backend 2023-06-05 12:09:36 +08:00
tarsin b3acd1d603 Implement openByDefault 2023-06-05 11:57:53 +08:00
tarsin d76e056b91 Implement dumpLogcatLauncher 2023-06-05 11:50:16 +08:00
tarsin 5de5dbabef AdvancedScreen placeholder 2023-06-05 11:19:09 +08:00
tarsin 866ea6369e Refine observed usage 2023-06-05 01:14:53 +08:00
tarsin ae2f7204fa Implement Compose SimpleMenuPreference 2 2023-06-05 01:09:41 +08:00
tarsin 5be2b77e42 Implement Compose SimpleMenuPreference 2023-06-05 00:55:49 +08:00
tarsin 8f1f76c3d1 Import DropDownPref 2023-06-05 00:31:56 +08:00
tarsin 6f488c1d00 Implement part of AdvancedScreen 2023-06-05 00:27:00 +08:00
tarsin ee7724e754 Create EhScreen, DownloadScreen stub 2023-06-05 00:19:18 +08:00
tarsin 6c4848b6f9 Apply applyPopAnimationsToPendingTransition globally 2023-06-05 00:15:31 +08:00
tarsin b6c36c20b6 Add stub AdvancedScreen 2023-06-05 00:13:21 +08:00
tarsin a29fc4ce94 Fix SliderPref enable status 2023-06-05 00:10:30 +08:00
tarsin 6490598fc3 Add IntSliderPreference 2023-06-05 00:05:25 +08:00
tarsin f3519f6f8a Import SliderPref 2023-06-04 23:53:42 +08:00
tarsin d44192079d Properly implement Preference 2023-06-04 23:39:33 +08:00
tarsin 140fac4f3b Stupid implementation of SwitchPreference 2023-06-04 17:33:01 +08:00
tarsin 802db256c1 Cleanup LicenseScreen 2023-06-04 16:58:10 +08:00
tarsin c8a9cfa8a3 Add SecurityScreen stub 2023-06-04 16:56:39 +08:00
tarsin 2213969c6e Fixup LicenseScreen padding 2023-06-04 16:51:45 +08:00
tarsin 524a3a94c4 Cleanup AboutScreen 2023-06-04 16:49:16 +08:00
tarsin 4ec58a0f62 Add standalone SpannedHelper 2023-06-04 16:43:01 +08:00
tarsin e25227e39d Add nestedScrollConnection 2023-06-04 16:38:28 +08:00
tarsin 7efaa64562 Implement AboutScreen 2023-06-04 16:30:49 +08:00
tarsin a2b95f6c61 AboutScreen stub 2023-06-04 15:32:32 +08:00
tarsin 9a6aa1a591 Cleanup PreferenceHeader implementation 2023-06-04 15:28:36 +08:00
tarsin de780635e4 Implement BaseScreen 2023-06-04 15:25:30 +08:00
tarsin 3dbb40967f Introduce BaseScreen 2023-06-04 14:54:59 +08:00
tarsin f39d17abc0 Introduce new compose SettingsActivity stub 2023-06-04 14:42:56 +08:00
tarsin a015e54569 Revert "Introduce KTLS based http downoader API stub"
This reverts commit a256864c84.
2023-06-03 22:16:51 +08:00
tarsin a256864c84 Introduce KTLS based http downoader API stub 2023-06-03 17:37:53 +08:00
tarsin d74b5bff7b Konvert AppConfig 2023-06-03 15:40:01 +08:00
tarsin 31e7943d22 Rename .java to .kt 2023-06-03 15:40:01 +08:00
tarsin b2d1660e95 Drop GetText 2023-06-03 15:06:42 +08:00
tarsin 5d13e29300 Cleanup unused res 2023-06-03 15:00:06 +08:00
tarsin aaf9ef06dd Rewrite ProgressScene with Jetpack Compose 2023-06-02 21:23:30 +08:00
tarsin 6725ef992a Drop Settings.initialize 2023-06-02 18:14:00 +08:00
tarsin b5baecf8d0 Singletonize EhProxySelector 2023-06-02 17:58:35 +08:00
tarsin 58d1ace612 Singletonize FavouriteStatusRouter 2023-06-02 17:55:40 +08:00
tarsin a4eca58275 Don't opt-in InternalCoroutinesApi 2023-06-02 15:53:18 +08:00
tarsin c97974eb72 Purify Settings 2023-06-02 15:47:11 +08:00
tarsin c46c386fea Move downloadLocation getter setter companion of DownloadManager 2023-06-02 15:43:10 +08:00
tarsin 3f4ca1bcbf Cleanup Settings code style 2023-06-02 15:35:34 +08:00
tarsin e4e04a4508 Cleanup Settings 2023-06-02 15:34:01 +08:00
tarsin 6d87dc0b1c Continue to cleanup Settings 2023-06-02 15:21:58 +08:00
tarsin db16a0c17c Cleanup Settings 2023-06-02 15:20:41 +08:00
tarsin 8fc411a30b Make Delegate private interface 2023-06-02 15:13:37 +08:00
tarsin c4a15f8e00 Optin ExperimentalSplittiesApi 2023-06-02 15:12:18 +08:00
tarsin 90e6918db0 Cleanup Settings 2023-06-02 15:09:40 +08:00
tarsin 62d7d09886 Inline launchPageGalleryListSceneAction 2023-06-02 15:03:34 +08:00
tarsin d9c933bd0a Inline thumbSize 2023-06-02 14:59:46 +08:00
tarsin 06c3e3c8a0 Fix downloadLocation scheme 2023-06-02 14:54:15 +08:00
tarsin f284ac71b5 Introduce stringArrayPref 2023-06-02 14:47:56 +08:00
tarsin cd969ac33b Introduce intArrayPref 2023-06-02 14:38:20 +08:00
tarsin ac43df5e03 Refine saveFromHttpResponse, avoid a buffer allocation 2023-06-02 14:14:21 +08:00
tarsin 8ea6417ea6 Refactor out interceptSecurityOrReturn 2023-06-02 13:49:49 +08:00
tarsin 19295e1d30 Refactor out lockObserver 2023-06-02 13:46:15 +08:00
tarsin f84b10f2eb Resolve #1124 2023-06-02 12:31:11 +08:00
tarsin 184edf9d4c Use splitties roomDb builder 2023-06-02 12:19:04 +08:00
tarsin 257e247e23 Inline listThumbSize 2023-06-01 21:10:47 +08:00
tarsin a2f0b4a64f Inline putPasswdToArchivePasswds 2023-06-01 21:08:26 +08:00
tarsin 9f1ec2be7e Inline intFrom 2023-06-01 21:05:22 +08:00
tarsin 046ba62256 Introduce intFromStrPref 2023-06-01 21:04:03 +08:00
tarsin c9f18b0b71 Convert to use intFromStrPref 2023-06-01 19:34:05 +08:00
tarsin b61e1bdbe3 Introduce intFromStrPref 2023-06-01 19:17:23 +08:00
tarsin 1a01caee2c Cleanup archivePasswds pref 2023-06-01 18:48:33 +08:00
tarsin 2fed610470 Cleanup listThumbSize pref 2023-06-01 18:39:36 +08:00
tarsin 2026843628 Inline some preference keys 2023-06-01 17:49:27 +08:00
tarsin 9997d31421 Inline some preference keys 2023-06-01 17:46:36 +08:00
tarsin 1079e4a759 Sort preferences by type 2023-06-01 17:40:44 +08:00
tarsin ce5c258cf9 Move some property to delegate 2023-06-01 17:36:06 +08:00
tarsin c1b76b35ec Move some property to delegate 2023-06-01 17:28:22 +08:00
tarsin e9cb083672 Move some property to delegate 2023-06-01 17:16:23 +08:00
tarsin 034a5c2071 Move some property to delegate 2023-06-01 17:07:57 +08:00
tarsin 6c7e3019ff Import splitties preferences 2023-06-01 16:56:23 +08:00
tarsin fc02902ccb Restore LongClick HapticFeedback 2023-06-01 16:17:59 +08:00
tarsin f33ff6d380 Use Splitties systemServices 2023-06-01 16:13:37 +08:00
tarsin 2c30deb298 Use Splitties appCtx 2023-06-01 15:48:45 +08:00
tarsin 6ea635a1e9 Resolve #1122 2023-05-31 23:21:59 +08:00
tarsin 858134927a Resolve #1122 2023-05-31 21:38:52 +08:00
tarsin 0eec51ef8e Revert "Resolve #1122"
This reverts commit de065076df.
2023-05-31 21:28:45 +08:00
tarsin de065076df Resolve #1122 2023-05-31 18:51:22 +08:00
tarsin ad4c418736 Resolve #1121 2023-05-31 18:37:45 +08:00
tarsin 9ab368a760 Workaround download in UIContext 2023-05-31 18:00:50 +08:00
tarsin 59f3cf20a5 Introduce DialogState.doGalleryInfoAction 2023-05-31 17:57:24 +08:00
tarsin 8a58b6ee0f Move to DialogState.showMoveDownloadLabel impl 2023-05-31 17:45:28 +08:00
tarsin d6816c89e5 Refactor compose dialog 2023-05-31 17:02:32 +08:00
tarsin 7ca37ee54a Tweak prefetchDistance 2023-05-31 14:46:32 +08:00
tarsin 4b4304629f Fix previewScene jump 2023-05-31 14:36:32 +08:00
tarsin dba557b942 Fix codestyle 2023-05-31 13:50:37 +08:00
tarsin fe158e7f62 Tweak DialogScope 2023-05-31 13:47:53 +08:00
tarsin ac7fed649a Implement dialogState.confirmRemoveDownload 2023-05-31 13:25:23 +08:00
tarsin cb67a51686 Move DialogState.selectGalleryInfoAction to CommonOperations 2023-05-31 12:42:50 +08:00
tarsin 4d85a2aa41 Resolve #1120 2023-05-31 12:29:29 +08:00
tarsin 0df875d65a Upgrade AGP 2023-05-31 12:17:06 +08:00
tarsin fff1ec0947 Downgrade AGP to stable version 2023-05-31 12:06:14 +08:00
tarsin 250a7ef25e Update build.gradle.kts 2023-05-31 02:16:30 +08:00
tarsin 805d22802c Update build.gradle.kts 2023-05-31 02:14:20 +08:00
tarsin 9745ce7d34 Update build.gradle.kts 2023-05-31 01:55:27 +08:00
tarsin c165c15099 Upgrade AGP 2023-05-31 01:28:03 +08:00
tarsin 351231aab9 Delegate all reader call to Context.navToReader 2023-05-31 01:10:08 +08:00
tarsin 0e78494f39 Cleanup code 2023-05-31 00:52:13 +08:00
tarsin 886ce38543 Drop SelectItemWithIconAdapter 2023-05-31 00:46:52 +08:00
tarsin f542c8a12e Inline NoHistory 2023-05-31 00:27:07 +08:00
tarsin c35cac860f Cleanup showSelectItemWithIcon implementation 2023-05-31 00:25:24 +08:00
tarsin afcdc5ec5b Fix typo 2023-05-30 23:58:04 +08:00
tarsin b9f24001ed Move to showSelectItemWithIcon impl 2023-05-30 23:55:40 +08:00
tarsin 1f0319c242 Tweak showSelectItemWithIcon style 2023-05-30 23:49:29 +08:00
tarsin 5e8e95f716 Introduce DialogScope.showSelectItemWithIcon 2023-05-30 23:41:19 +08:00
tarsin dd73b20b3f Konvert ShortcutsActivity 2023-05-30 21:41:59 +08:00
tarsin c6a83ac6b2 Rename .java to .kt 2023-05-30 21:41:59 +08:00
tarsin 6ea7a1405f Cleanup unused code 2023-05-30 21:37:47 +08:00
tarsin 774db8d3a4 Always merge requests when preloadThumbAggressively 2023-05-30 21:26:19 +08:00
tarsin a7187d62d3 Konvert ImageLoader.Builder 2023-05-30 21:24:36 +08:00
tarsin e5788b9ec5 Cleanup EhApplication 2023-05-30 21:20:36 +08:00
tarsin be96491091 Introduce idiomatic OkHttp builders 2023-05-30 21:17:36 +08:00
tarsin ea0b83476f Konvert FavouriteStatusRouter 2023-05-30 20:41:14 +08:00
tarsin 187f77d32f Rename .java to .kt 2023-05-30 20:41:14 +08:00
tarsin 46655e3326 Move builtInHosts to Standalone EhHostsConfig 2023-05-30 20:35:32 +08:00
tarsin 4f1615b736 Cleanup EhRequestBuilder 2023-05-30 20:30:50 +08:00
tarsin 8b3ab44c1c Cleanup EhSSLSocketFactory 2023-05-30 20:27:45 +08:00
tarsin 10b6258175 Only make large blob downloading sensitive for cancelling 2023-05-30 20:17:40 +08:00
tarsin 13b532c12a Revert "Fix usingCancellable with null ret value"
This reverts commit d464d00067.
2023-05-30 20:16:02 +08:00
tarsin 126f4238d6 Fix DownloadsScene lag 2023-05-30 18:18:30 +08:00
tarsin 9666feac57 Cleanup downloadManager ref in DownloadsScene 2023-05-30 17:43:57 +08:00
tarsin d464d00067 Fix usingCancellable with null ret value 2023-05-29 20:04:15 +08:00
tarsin 6e96571cc8 Cleanup UrlBuilder with HttpUrl 2023-05-29 19:58:56 +08:00
tarsin 2b70c983bf Document Call.usingCancellable 2023-05-29 17:41:59 +08:00
tarsin 3a2c18d8a9 Refine saveFromHttpResponse 2023-05-29 17:24:34 +08:00
tarsin 29fb40eecc Introduce Call.usingCancellable 2023-05-29 17:02:46 +08:00
tarsin 684d25780e Introduce runInterruptibleOkio 2023-05-29 16:03:41 +08:00
tarsin 51a292e5d0 Use runInterruptible for more sensitive cancellation 2023-05-29 15:36:35 +08:00
tarsin 4f99107ac5 Tweak DF configs 2023-05-28 20:33:44 +08:00
tarsin e4685c7018 Log DoH lookup failure 2023-05-28 19:26:17 +08:00
tarsin 6a42793a59 Fixup! Implement custom DoH 2023-05-28 19:08:02 +08:00
tarsin 4a81df4174 Implement custom DoH 2023-05-28 19:04:28 +08:00
tarsin 512e961820 Add DohPreference 2023-05-28 18:37:50 +08:00
tarsin cbccf7e7fa Add dohUrl settings entry 2023-05-28 17:54:26 +08:00
tarsin f1890c2d3e Cleanup Settings 2023-05-28 17:48:56 +08:00
tarsin 0c30b20a66 Add doh module 2023-05-28 17:43:36 +08:00
tarsin 14b3f13338 Use AndroidAsyncDns 2023-05-28 17:34:00 +08:00
tarsin 0f75f5ec69 Tweak hosts config 2023-05-28 13:57:39 +08:00
tarsin bdbc5438aa Fixup code style 2023-05-28 13:56:51 +08:00
tarsin 525131dbaf Update EhDns.kt 2023-05-28 01:29:08 +08:00
tarsin f8938a46bb Fix naming 2023-05-28 01:18:58 +08:00
tarsin 50ce18d201 Fix codestyle 2023-05-28 01:14:56 +08:00
tarsin bba06321c8 Introduce hostDSL 2023-05-28 01:13:22 +08:00
tarsin 3439b684e1 Drop ip parser 2023-05-28 00:37:30 +08:00
tarsin 0ffba596e5 Cleanup Obsolete db 2023-05-27 23:52:25 +08:00
tarsin c112322daf Drop custom hosts 2023-05-27 23:46:00 +08:00
tarsin a942a487a0 Drop buildMainDB 2023-05-27 22:53:24 +08:00
tarsin 7f02c27a3a Rename EhDatabase to Database 2023-05-27 22:51:37 +08:00
tarsin a69edd5c55 Move CookiesDatabase out of EhDatabase 2023-05-27 22:50:52 +08:00
tarsin f8a0d8b9c5 Move searchDatabase out of EhDatabase 2023-05-27 22:49:12 +08:00
tarsin a7214740ec Avoid mainthread db query 2023-05-27 20:01:26 +08:00
tarsin 17fb0f724c Fix SearchDatabase query 2023-05-27 19:56:38 +08:00
tarsin 5e14dad961 Initial migrate SearchDatabase to ROOM 2023-05-27 19:27:01 +08:00
tarsin dc48363398 Dedup dependencies 2023-05-27 13:02:46 +08:00
tarsin df4a8ec15e Cleanup codestyle 2023-05-27 12:53:03 +08:00
tarsin 810e3ff5fe Use suggestion chip 2023-05-27 12:49:54 +08:00
tarsin aef065a057 Allow open license dialog when badge clicked 2023-05-27 12:41:36 +08:00
tarsin 1824786a80 Open website by default when library item clicked 2023-05-27 01:26:34 +08:00
tarsin c8b7fed168 Drop autogenerated json 2023-05-27 01:19:40 +08:00
tarsin 4118390283 Use tertiaryContainer for badge container 2023-05-27 01:09:59 +08:00
tarsin 13a9fd36d3 Sort libraries by name 2023-05-27 01:03:11 +08:00
tarsin 9ad933934f Add LicenseFragment 2023-05-27 00:55:03 +08:00
tarsin 92240c6c2c Improve MergeInterceptor, only merge normal preview requests 2023-05-26 18:43:40 +08:00
tarsin 689a78d2ee Improve MergeInterceptor 2023-05-26 18:01:45 +08:00
tarsin 7ed0b93faa Cleanup code 2023-05-26 17:37:55 +08:00
tarsin 3a004c41f8 Refactor db export with resourceDSL 2023-05-26 17:15:11 +08:00
tarsin 90ed83efd5 Refactor db import with resourceDSL 2023-05-26 16:58:18 +08:00
tarsin 3a2ccf86fd Fix database import 2023-05-26 16:36:39 +08:00
tarsin 4e6f906903 Convert ThumbKeyMigration to AutoMigrationSpec 2023-05-26 16:27:22 +08:00
tarsin c73ca48e35 Add db v4 schemas 2023-05-26 16:12:55 +08:00
tarsin e1d7b3132d Drop BookmarksDao, upgrade db 2023-05-26 16:02:32 +08:00
tarsin 1f4f99079b Sync with 1.8.8.3 VC 2023-05-26 15:48:03 +08:00
tarsin bf495336d9 Fixup aa1e2102fc 2023-05-26 15:44:55 +08:00
tarsin cc636bcae0 Nonnullize and Immutablize DownloadDirname, upgrade db 2023-05-26 15:44:00 +08:00
tarsin cf185a6148 Export dao schemas 2023-05-26 15:29:16 +08:00
tarsin f92ce637bf Fixup unsafe Composable behaviour 2023-05-26 15:22:50 +08:00
tarsin 2a34d6eb8b Workaround aspect is NaN, Resolve #1116 2023-05-26 15:13:45 +08:00
tarsin a8e3f67371 Fix wrong thumb crop behaviour 2023-05-26 14:59:03 +08:00
tarsin 4a7d4dedd0 Sync with 1.8.8.2 VC 2023-05-26 14:36:55 +08:00
tarsin 7d3178015b Fixup! Resolve #1113 2023-05-26 14:34:58 +08:00
tarsin 54079990d0 Sync with 1.8.8.1 versionCode 2023-05-26 13:48:41 +08:00
tarsin a379391862 Resolve #1113 2023-05-26 13:43:07 +08:00
tarsin dfb2fbf88b Hello, K2! 2023-05-25 16:23:05 +08:00
tarsin 68f201980a Upgrade Kotlin 1.9.0-Beta 2023-05-25 16:14:35 +08:00
tarsin 9a079083e9 Prepare next development iteration 2023-05-25 15:39:16 +08:00
renovate[bot] 1ee1b122fe Update dependency dev.chrisbanes.compose:compose-bom to v2023.04.00-SNAPSHOT 2023-05-25 15:37:48 +08:00
651 changed files with 27277 additions and 30136 deletions

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
[*.{kt,kts}]
ktlint_code_style = intellij_idea
ktlint_function_naming_ignore_when_annotated_with = Composable
+4 -7
View File
@@ -6,10 +6,10 @@ body:
- type: markdown
attributes:
value: |
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
感谢您愿意为 EhViewer 做出贡献!
提交问题报告前,还请首先完成文末的自查步骤
Thanks for your contribution on Ehviewer-Overhauled Project!
Thanks for your contribution on EhViewer Project!
Please finish verify steps which list in the end first before create bug report
- type: textarea
@@ -135,14 +135,11 @@ body:
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
required: false
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
required: true
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
- 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
- label: 您确保这个 Issue 只提及一个问题。如果您有多个问题报告,烦请发起多个 Issue / Ensure there is only one bug report in this issue. Please make mutiply issue for mutiply bugs
required: true
- label: 您确保已使用最新 CI 版本测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest CI version
- label: 您确保已使用最新 [CI 版本](https://github.com/FooIbar/EhViewer/actions)测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest [CI version](https://github.com/FooIbar/EhViewer/actions)
required: true
+1 -5
View File
@@ -1,5 +1 @@
blank_issues_enabled: false
contact_links:
- name: 所有其他问题 / All other questions
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
about: 转到 Discussions / Turn to discussions
blank_issues_enabled: false
@@ -1,94 +0,0 @@
name: 功能请求 / Feature request
description: 提出一个功能建议 / Suggest an idea
labels:
- "enhancement"
body:
- type: markdown
attributes:
value: |
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
提交功能建议前,还请首先完成文末的自查步骤
Thanks for your contribution on Ehviewer-Overhauled Project!
Please finish verify steps which list in the end first before suggest an idea
- type: textarea
id: request
attributes:
label: 需求 / Requirement
description: |
在此处描述您的需求。这通常会是一个您想要的功能
Ddescribe what you need here.
placeholder: |
我需要 XXX 功能 ...
I want ABC feature ...
validations:
required: true
- type: textarea
id: impl
attributes:
label: 建议实现 / Suggested implements
description: |
在此处表述您建议的实现方式。如有可能,UI 类功能请求还请尽量附上图示
Describe your suggested implements here, It's recommend to add a photo if you are making a UI feature request.
placeholder: |
建议在 XX 处添加 XX ...
I recommend add ABC feature to DEF...
图片(如果有)/ Photos (if exists)
validations:
required: true
- type: textarea
id: more
attributes:
label: 备注 / Addition details
description: |
在此处写下其他您想说的内容
Describe addition details here
placeholder: |
其他有用的信息与附件
Additional details and attachments
validations:
required: false
- type: input
id: version
attributes:
label: EhViewer 版本号 / EhViewer version code
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
placeholder: 1.6.9.13
validations:
required: true
- type: input
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 位于设置 - 关于中的版本号后 / Next to the version code in Settings - About
placeholder: 26f05a0
validations:
required: true
- type: checkboxes
id: check
attributes:
label: 自查步骤 / Verify steps
description: |
请确认您已经遵守所有必选项
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: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
required: true
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
required: true
- label: 您确保这个 Issue 只提及一个功能。如果您有多个功能请求,烦请发起多个 Issue / Ensure there is only one feature request in this issue. Please make mutiply issue for mutiply feature request
required: true
- label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
required: true
+177 -28
View File
@@ -4,8 +4,9 @@ on:
push:
branches:
- '*'
tags-ignore:
- "*"
- 'renovate/**'
pull_request:
workflow_dispatch:
jobs:
linux:
@@ -13,56 +14,204 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
submodules: 'recursive'
- name: Setup Java 17
- name: Setup Java 21
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
java-version: 21
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.2.9519653" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- 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
- name: Ktlint Check
run: ./gradlew ktlintCheck
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
- name: Gradle Build
- name: Rustfmt Check
working-directory: ./app/src/main/rust
run: cargo fmt --all -- --check
- name: Clippy Check
working-directory: ./app/src/main/rust
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
gradle-home-cache-cleanup: true
arguments: assembleRelease
- name: Update ARM64
- name: Spotless Check
run: ./gradlew spotlessCheck
- name: Lint Check
run: ./gradlew lintMarshmallowGmsRelease
- name: CMake Cache
uses: actions/cache@v3
with:
path: |
app/.cxx
app/src/main/cpp/nettle/nettle
key: ${{ runner.os }}-cmake-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cmake-
- name: Build OSS
run: ./gradlew assembleDefaultOssRelease
- name: Upload OSS ARM64
uses: actions/upload-artifact@v3
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
name: default-oss-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-arm64-v8a-release.apk
- name: Update x86_64
- name: Upload OSS x86_64
uses: actions/upload-artifact@v3
with:
name: x86_64-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86_64-release.apk
name: default-oss-x86_64-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-x86_64-release.apk
- name: Update ARM32
- name: Upload OSS ARM32
uses: actions/upload-artifact@v3
with:
name: armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
name: default-oss-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-armeabi-v7a-release.apk
- name: Update x86
- name: Upload OSS x86
uses: actions/upload-artifact@v3
with:
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
name: default-oss-x86-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-x86-release.apk
- name: Update mapping
- name: Upload OSS mapping
uses: actions/upload-artifact@v3
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/release/mapping.txt
retention-days: 5
name: default-oss-mapping-${{ github.sha }}
path: app/build/outputs/mapping/defaultOssRelease/mapping.txt
- name: Upload OSS native debug symbols
uses: actions/upload-artifact@v3
with:
name: default-oss-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/defaultOssRelease/native-debug-symbols.zip
- name: Build GMS
run: ./gradlew assembleDefaultGmsRelease
- name: Upload GMS ARM64
uses: actions/upload-artifact@v3
with:
name: default-gms-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-arm64-v8a-release.apk
- name: Upload GMS x86_64
uses: actions/upload-artifact@v3
with:
name: default-gms-x86_64-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-x86_64-release.apk
- name: Upload GMS ARM32
uses: actions/upload-artifact@v3
with:
name: default-gms-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-armeabi-v7a-release.apk
- name: Upload GMS x86
uses: actions/upload-artifact@v3
with:
name: default-gms-x86-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-x86-release.apk
- name: Upload GMS mapping
uses: actions/upload-artifact@v3
with:
name: default-gms-mapping-${{ github.sha }}
path: app/build/outputs/mapping/defaultGmsRelease/mapping.txt
- name: Upload GMS native debug symbols
uses: actions/upload-artifact@v3
with:
name: default-gms-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/defaultGmsRelease/native-debug-symbols.zip
- name: Build Marshmallow OSS
run: ./gradlew assembleMarshmallowOssRelease
- name: Upload Marshmallow OSS ARM64
uses: actions/upload-artifact@v3
with:
name: marshmallow-oss-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-arm64-v8a-release.apk
- name: Upload Marshmallow OSS x86_64
uses: actions/upload-artifact@v3
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
-55
View File
@@ -1,55 +0,0 @@
name: Pull Request Check
on: [pull_request]
jobs:
linux:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 17
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.2.9519653" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Ktlint Check
run: ./gradlew ktlintCheck
- name: Gradle Build
uses: gradle/gradle-build-action@v2
with:
arguments: assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Update x86_64
uses: actions/upload-artifact@v3
with:
name: x86_64-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86_64-release.apk
- name: Update ARM32
uses: actions/upload-artifact@v3
with:
name: armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
- name: Update x86
uses: actions/upload-artifact@v3
with:
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
+79 -37
View File
@@ -11,19 +11,32 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
submodules: 'recursive'
- name: Setup Java 17
- name: Setup Java 21
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
java-version: 21
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.2.9519653" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Install Rust Toolchain
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
- name: CMake Cache
uses: actions/cache@v3
with:
path: |
app/.cxx
app/src/main/cpp/nettle/nettle
key: ${{ runner.os }}-cmake-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cmake-
- name: Gradle Build
uses: gradle/gradle-build-action@v2
@@ -32,42 +45,71 @@ jobs:
- name: Rename Apks
run: |
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal.apk
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64.apk
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86.apk
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
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/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
- name: Releases
uses: softprops/action-gh-release@v1
with:
body: Bump Version
discussion_category_name: Announcements
files: |
app/build/outputs/apk/release/EhViewer-universal.apk
app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
app/build/outputs/apk/release/EhViewer-x86_64.apk
app/build/outputs/apk/release/EhViewer-x86.apk
app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
app/build/outputs/mapping/release/mapping.txt
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
- name: Collect Apks
run: |
mkdir app/build/outputs/apk/release/fdroid
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME.apk
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME-armv7.apk
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
- name: Push Apks
uses: Ehviewer-Overhauled/github-action-push-to-another-repository@main
env:
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
with:
source-directory: app/build/outputs/apk/release/fdroid
destination-github-username: Ehviewer-Overhauled
destination-repository-name: fdroid-repo
user-name: 'github-actions[bot]'
user-email: 'github-actions[bot]@users.noreply.github.com'
target-branch: deploy
create-target-branch: true
commit-message: Add apks from $GITHUB_REF
target-directory: fdroid/tmp-apks
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
-1
View File
@@ -6,5 +6,4 @@
*.iml
.idea
/captures
release
google-services.json
-3
View File
@@ -1,3 +0,0 @@
[submodule "app/src/main/cpp/nettle/nettle"]
path = app/src/main/cpp/nettle/nettle
url = https://github.com/Ehviewer-Overhauled/nettle
+39 -21
View File
@@ -1,60 +1,64 @@
# Maintenance Mode
The project is in maintenance mode, will only receive bugfixes.
<p align="right">
<strong>English</strong>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-cn.md">
<a href="https://github.com/FooIbar/EhViewer/blob/1.8.x.x/docs/README/zh-cn.md">
简体中文
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-tw.md">
<a href="https://github.com/FooIbar/EhViewer/blob/1.8.x.x/docs/README/zh-tw.md">
正體中文
</a>
</p>
<h1 align="center">
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200">
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200" alt="EhViewer">
<br>EhViewer<br>
</h1>
<p align="center">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
<a href="https://github.com/FooIbar/EhViewer/actions/workflows/ci.yml">
<img src="https://github.com/FooIbar/EhViewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/LICENSE">
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
<a href="https://github.com/FooIbar/EhViewer/blob/1.8.x.x/LICENSE">
<img src="https://img.shields.io/github/license/FooIbar/EhViewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
<a href="https://www.codefactor.io/repository/github/FooIbar/EhViewer">
<img src="https://www.codefactor.io/repository/github/FooIbar/EhViewer/badge" alt="CodeFactor">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
<img src="https://img.shields.io/github/downloads/Ehviewer-Overhauled/EhViewer/total.svg" alt="Downloads">
<a href="https://github.com/FooIbar/EhViewer/releases">
<img src="https://img.shields.io/github/downloads/FooIbar/EhViewer/total.svg" alt="Downloads">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
<a href="https://github.com/FooIbar/EhViewer/releases">
<img src="https://img.shields.io/github/v/release/FooIbar/EhViewer" alt="Release">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
<a href="https://github.com/FooIbar/EhViewer/issues">
<img src="https://img.shields.io/github/issues/FooIbar/EhViewer" alt="Issues">
</a>
</p>
<div align="center">
<h3>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#description">
<a href="https://github.com/FooIbar/EhViewer#description">
Description
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#download">
<a href="https://github.com/FooIbar/EhViewer#download">
Download
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#screenshot">
<a href="https://github.com/FooIbar/EhViewer#screenshot">
Screenshot
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#thanks">
<a href="https://github.com/FooIbar/EhViewer#thanks">
Thanks
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#license">
<a href="https://github.com/FooIbar/EhViewer#license">
License
</a>
</h3>
@@ -69,7 +73,20 @@ and [Dynamic Color](https://m3.material.io/styles/color/dynamic-color/overview)
# Download
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Get it on F-Droid" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-fdroid.svg" width="200px"/></a>
| Flavor | Feature |
|-|-|
| Default | Android 9+, full support |
| Marshmallow | Android 6.0+, limited support* |
| OSS | FOSS dependencies** |
| GMS | Proprietary GMS component |
\* Archives and GIFs are not supported
** Fall back to OkHttp on devices that don't support [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine)
<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"/>
</a>
# Screenshot
@@ -99,6 +116,7 @@ Specially thanks [Tachiyomi](https://tachiyomi.org/) for its reader implementati
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
Copyright 2022-2023 Tarsin Norbin
Copyright 2023 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.
+171 -86
View File
@@ -1,31 +1,38 @@
import java.io.ByteArrayOutputStream
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
val isRelease: Boolean
get() = gradle.startParameter.taskNames.any { it.contains("Release") }
plugins {
id("com.android.application")
kotlin("android")
id("kotlin-parcelize")
kotlin("plugin.serialization")
id("com.google.devtools.ksp")
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.parcelize)
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)
}
android {
compileSdk = 33
buildToolsVersion = "33.0.2"
ndkVersion = "25.2.9519653"
androidResources.generateLocaleConfig = true
compileSdk = 34
buildToolsVersion = "34.0.0"
ndkVersion = "26.1.10909125"
splits {
abi {
isEnable = true
reset()
if (gradle.startParameter.taskNames.any { it.contains("Release") }) {
if (isRelease) {
include("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
isUniversalApk = true
} else {
include("arm64-v8a")
include("arm64-v8a", "x86_64")
}
}
}
@@ -39,26 +46,26 @@ android {
enableV4Signing = true
}
val commitSha by lazy {
val stdout = ByteArrayOutputStream()
exec {
commandLine = "git rev-parse --short=7 HEAD".split(' ')
standardOutput = stdout
}
stdout.toString().trim()
}
val commitSha = providers.exec {
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 repoName = providers.exec {
commandLine = "git remote get-url origin".split(' ')
}.standardOutput.asText.get().trim().removePrefix("https://github.com/").removePrefix("git@github.com:")
.removeSuffix(".git")
defaultConfig {
applicationId = "moe.tarsin.ehviewer"
minSdk = 28
targetSdk = 33
versionCode = 180039
versionName = "1.8.8.0"
targetSdk = 34
versionCode = 180044
versionName = "1.8.10.1"
resourceConfigurations.addAll(
listOf(
"zh",
@@ -76,7 +83,43 @@ android {
),
)
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
buildConfigField("String", "BUILD_TIME", "\"$buildTime\"")
buildConfigField("String", "REPO_NAME", "\"$repoName\"")
ndk {
debugSymbolLevel = "FULL"
}
}
flavorDimensions += listOf("api", "oss")
productFlavors {
create("default") {
dimension = "api"
}
create("marshmallow") {
dimension = "api"
minSdk = 23
applicationIdSuffix = ".m"
versionNameSuffix = "-M"
externalNativeBuild {
cmake {
targets += "noop"
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
lint {
checkOnly += setOf("InlinedApi", "NewApi", "UnusedAttribute")
error += setOf("InlinedApi", "UnusedAttribute")
}
}
create("oss") {
dimension = "oss"
}
create("gms") {
dimension = "oss"
versionNameSuffix = "-gms"
}
}
externalNativeBuild {
@@ -85,13 +128,7 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
// https://kotlinlang.org/docs/compiler-reference.html#progressive
"-progressive",
@@ -109,8 +146,9 @@ android {
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-opt-in=splitties.experimental.ExperimentalSplittiesApi",
"-opt-in=splitties.preferences.DataStorePreferencesPreview",
)
}
@@ -137,9 +175,11 @@ android {
isShrinkResources = true
proguardFiles("proguard-rules.pro")
signingConfig = signConfig
buildConfigField("String", "BUILD_TIME", "\"$buildTime\"")
}
debug {
applicationIdSuffix = ".debug"
buildConfigField("String", "BUILD_TIME", "\"\"")
}
}
@@ -150,7 +190,7 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.7"
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
namespace = "com.hippo.ehviewer"
@@ -158,86 +198,131 @@ android {
dependencies {
// https://developer.android.com/jetpack/androidx/releases/activity
implementation("androidx.activity:activity-compose:1.7.2")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha05")
implementation("androidx.browser:browser:1.5.0")
implementation("androidx.collection:collection-ktx:1.3.0-alpha04")
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.biometric)
implementation(libs.androidx.browser)
implementation(libs.androidx.collection)
// https://developer.android.com/jetpack/androidx/releases/compose-material3
// api(platform("androidx.compose:compose-bom:2023.05.00"))
api(platform("dev.chrisbanes.compose:compose-bom:2023.04.00-beta01"))
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material3:material3-window-size-class")
api(platform(libs.compose.bom))
implementation(libs.bundles.compose)
implementation("androidx.core:core-ktx:1.11.0-beta01")
implementation(libs.androidx.core)
implementation(libs.androidx.core.splashscreen)
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha10")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
implementation("androidx.drawerlayout:drawerlayout:1.2.0")
implementation(libs.androidx.constraintlayout.compose)
implementation(libs.androidx.coordinatorlayout)
implementation(libs.androidx.fragment)
// https://developer.android.com/jetpack/androidx/releases/lifecycle
implementation("androidx.lifecycle:lifecycle-process:2.6.1")
implementation(libs.androidx.lifecycle.process)
// https://developer.android.com/jetpack/androidx/releases/navigation
val nav_version = "2.5.3"
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
implementation("androidx.navigation:navigation-compose:$nav_version")
implementation(libs.bundles.androidx.navigation)
// https://developer.android.com/jetpack/androidx/releases/paging
implementation("androidx.paging:paging-common-ktx:3.2.0-alpha06")
implementation("androidx.paging:paging-compose:1.0.0-alpha20")
implementation("androidx.preference:preference-ktx:1.2.0")
implementation("androidx.recyclerview:recyclerview:1.3.0")
implementation(libs.bundles.androidx.paging)
implementation(libs.bundles.androidx.recyclerview)
// https://developer.android.com/jetpack/androidx/releases/room
val room_version = "2.6.0-alpha01"
ksp("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-paging:$room_version")
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.paging)
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation("com.drakeet.drawer:drawer:1.0.3")
implementation("com.github.chrisbanes:PhotoView:2.3.0") // Dead Dependency
implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0") // Dead Dependency
implementation("com.github.nanihadesuka:LazyColumnScrollbar:1.6.3")
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.androidx.work.runtime)
implementation(libs.drawer)
implementation(libs.photoview) // Dead Dependency
implementation(libs.directionalviewpager) // Dead Dependency
// https://github.com/google/accompanist/releases
implementation("com.google.accompanist:accompanist-themeadapter-material3:0.30.1")
implementation("com.google.android.material:material:1.9.0")
implementation(libs.bundles.accompanist)
implementation(libs.material)
implementation(libs.bundles.splitties)
// https://square.github.io/okhttp/changelogs/changelog/
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11"))
implementation("com.squareup.okhttp3:okhttp-coroutines")
implementation(platform(libs.okhttp.bom))
implementation(libs.bundles.okhttp)
implementation("com.squareup.okio:okio-jvm:3.3.0")
implementation(libs.okio.jvm)
implementation("dev.chrisbanes.insetter:insetter:0.6.1") // Dead Dependency
implementation("dev.rikka.rikkax.core:core-ktx:1.4.1")
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
implementation("dev.rikka.rikkax.preference:simplemenu-preference:1.0.3")
implementation("dev.rikka.rikkax.material:material-preference:2.0.0")
implementation(libs.aboutlibraries.core)
implementation(platform("io.arrow-kt:arrow-stack:1.2.0-RC"))
implementation("io.arrow-kt:arrow-fx-coroutines")
implementation(libs.insetter) // Dead Dependency
implementation(libs.bundles.rikkax)
implementation(platform(libs.arrow.stack))
implementation(libs.arrow.fx.coroutines)
// https://coil-kt.github.io/coil/changelog/
implementation(platform("io.coil-kt:coil-bom:2.4.0"))
implementation("io.coil-kt:coil-compose")
implementation("io.coil-kt:coil-gif")
implementation(platform(libs.coil.bom))
implementation(libs.bundles.coil)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.5.1")
implementation("org.jsoup:jsoup:1.16.1")
implementation(libs.bundles.ktor)
implementation(libs.bundles.kotlinx.serialization)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.jsoup)
debugImplementation(libs.chucker)
releaseImplementation(libs.chucker.nop)
debugImplementation(libs.leakcanary.android)
coreLibraryDesugaring(libs.desugar)
"gmsImplementation"(libs.bundles.cronet)
}
configurations.all {
exclude("dev.rikka.rikkax.appcompat", "appcompat")
exclude("dev.rikka.rikkax.material", "material")
kotlin {
jvmToolchain(21)
}
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.generateKotlin", "true")
}
aboutLibraries {
duplicationMode = MERGE
duplicationRule = GROUP
exclusionPatterns = listOf(
"androidx\\..*".toPattern(),
".*annotations".toPattern(),
"com\\.google\\.code\\..*".toPattern(),
"com\\.google\\.guava:listenablefuture".toPattern(),
"com\\.google\\.protobuf:protobuf-javalite".toPattern(),
"org\\.checkerframework:checker-qual".toPattern(),
"org\\.chromium\\.net:cronet-embedded".toPattern(),
"org\\.slf4j:slf4j-api".toPattern(),
)
}
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)
}
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"))
}
}
+9
View File
@@ -13,6 +13,15 @@
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
-keep class com.hippo.ehviewer.client.parser.Torrent { *; }
-keep class com.hippo.ehviewer.client.parser.GalleryListResult { *; }
-keep class com.hippo.ehviewer.client.parser.Limits { *; }
-keep class com.hippo.ehviewer.client.data.BaseGalleryInfo { *; }
-keep class com.hippo.ehviewer.ui.scene.SearchBarScene { getBinding(); }
# Ktor logger
-dontwarn org.slf4j.impl.StaticLoggerBinder
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
@@ -0,0 +1,448 @@
{
"formatVersion": 1,
"database": {
"version": 10,
"identityHash": "613604f93bf815adeb87f03e2166e983",
"entities": [
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `TIME` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `FAVORITE_SLOT` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlot",
"columnName": "FAVORITE_SLOT",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '613604f93bf815adeb87f03e2166e983')"
]
}
}
@@ -0,0 +1,469 @@
{
"formatVersion": 1,
"database": {
"version": 11,
"identityHash": "92915e937cb7ccc536d352fda5cf287f",
"entities": [
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL DEFAULT 0, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `TIME` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL DEFAULT 0, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `FAVORITE_SLOT` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlot",
"columnName": "FAVORITE_SLOT",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '92915e937cb7ccc536d352fda5cf287f')"
]
}
}
@@ -0,0 +1,454 @@
{
"formatVersion": 1,
"database": {
"version": 12,
"identityHash": "d40b38b0ba3b1c53b773728cdee328c8",
"entities": [
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `FAVORITE_SLOT` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlot",
"columnName": "FAVORITE_SLOT",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd40b38b0ba3b1c53b773728cdee328c8')"
]
}
}
@@ -0,0 +1,534 @@
{
"formatVersion": 1,
"database": {
"version": 13,
"identityHash": "df15d8f70f45047a738c8911212ea73c",
"entities": [
{
"tableName": "GALLERIES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "favoriteSlot",
"columnName": "FAVORITE_SLOT",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `FAVORITE_SLOT` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlot",
"columnName": "FAVORITE_SLOT",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'df15d8f70f45047a738c8911212ea73c')"
]
}
}
@@ -0,0 +1,402 @@
{
"formatVersion": 1,
"database": {
"version": 14,
"identityHash": "54e4f2704cc47c5a6a2f4b96de978cce",
"entities": [
{
"tableName": "GALLERIES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "favoriteSlot",
"columnName": "FAVORITE_SLOT",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
}
]
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
}
]
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
}
]
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '54e4f2704cc47c5a6a2f4b96de978cce')"
]
}
}
@@ -0,0 +1,412 @@
{
"formatVersion": 1,
"database": {
"version": 15,
"identityHash": "991a3a3ddc4fe8d4dbcb39b975ab73b8",
"entities": [
{
"tableName": "GALLERIES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "favoriteSlot",
"columnName": "FAVORITE_SLOT",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
}
]
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [
{
"name": "index_DOWNLOAD_LABELS_LABEL",
"unique": true,
"columnNames": [
"LABEL"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
}
],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
}
]
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
}
]
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '991a3a3ddc4fe8d4dbcb39b975ab73b8')"
]
}
}
@@ -0,0 +1,433 @@
{
"formatVersion": 1,
"database": {
"version": 16,
"identityHash": "06e5ff8fd6797d5801e86753fa5831b2",
"entities": [
{
"tableName": "GALLERIES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "favoriteSlot",
"columnName": "FAVORITE_SLOT",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [
{
"name": "index_DOWNLOAD_LABELS_LABEL",
"unique": true,
"columnNames": [
"LABEL"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
}
],
"foreignKeys": []
},
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [
{
"name": "index_DOWNLOADS_LABEL",
"unique": false,
"columnNames": [
"LABEL"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
}
],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
},
{
"table": "DOWNLOAD_LABELS",
"onDelete": "SET NULL",
"onUpdate": "CASCADE",
"columns": [
"LABEL"
],
"referencedColumns": [
"LABEL"
]
}
]
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
}
]
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": [
{
"table": "GALLERIES",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"GID"
],
"referencedColumns": [
"GID"
]
}
]
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "POSITION",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '06e5ff8fd6797d5801e86753fa5831b2')"
]
}
}
@@ -0,0 +1,534 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "f5752deda842dccabded1cdbf1c78384",
"entities": [
{
"tableName": "BOOKMARKS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`PAGE` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "page",
"columnName": "PAGE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumb",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumb",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT, `ENABLE` INTEGER, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlotBackingField",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumb",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumb",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f5752deda842dccabded1cdbf1c78384')"
]
}
}
@@ -0,0 +1,534 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "f5752deda842dccabded1cdbf1c78384",
"entities": [
{
"tableName": "BOOKMARKS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`PAGE` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "page",
"columnName": "PAGE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT, `ENABLE` INTEGER, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlotBackingField",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f5752deda842dccabded1cdbf1c78384')"
]
}
}
@@ -0,0 +1,534 @@
{
"formatVersion": 1,
"database": {
"version": 6,
"identityHash": "e45f4380ef769c127604cb2a502a942b",
"entities": [
{
"tableName": "BOOKMARKS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`PAGE` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "page",
"columnName": "PAGE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT, `ENABLE` INTEGER, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlotBackingField",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e45f4380ef769c127604cb2a502a942b')"
]
}
}
@@ -0,0 +1,448 @@
{
"formatVersion": 1,
"database": {
"version": 7,
"identityHash": "9563c2017be4341dc1bada115426df77",
"entities": [
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT, `ENABLE` INTEGER, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlotBackingField",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9563c2017be4341dc1bada115426df77')"
]
}
}
@@ -0,0 +1,448 @@
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "f09bae277bbdab54ecbb0d698c83e734",
"entities": [
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlotBackingField",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f09bae277bbdab54ecbb0d698c83e734')"
]
}
}
@@ -0,0 +1,448 @@
{
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "64f51a531e13d2b0b971f3d8da0876b0",
"entities": [
{
"tableName": "DOWNLOADS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "state",
"columnName": "STATE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "legacy",
"columnName": "LEGACY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_LABELS",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `TIME` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "label",
"columnName": "LABEL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "DOWNLOAD_DIRNAME",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dirname",
"columnName": "DIRNAME",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "FILTER",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "TEXT",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "ENABLE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "HISTORY",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "favoriteSlotBackingField",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LOCAL_FAVORITES",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
"fields": [
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gid",
"columnName": "GID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "TOKEN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "TITLE",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "titleJpn",
"columnName": "TITLE_JPN",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbKey",
"columnName": "THUMB",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "posted",
"columnName": "POSTED",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "UPLOADER",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "rating",
"columnName": "RATING",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "simpleLanguage",
"columnName": "SIMPLE_LANGUAGE",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"GID"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "QUICK_SEARCH",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "NAME",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "MODE",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "CATEGORY",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "keyword",
"columnName": "KEYWORD",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "advanceSearch",
"columnName": "ADVANCE_SEARCH",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minRating",
"columnName": "MIN_RATING",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageFrom",
"columnName": "PAGE_FROM",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pageTo",
"columnName": "PAGE_TO",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "TIME",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '64f51a531e13d2b0b971f3d8da0876b0')"
]
}
}
@@ -0,0 +1,46 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "69230710633494b4b331e10dae436fb7",
"entities": [
{
"tableName": "suggestions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `date` LONG, `query` TEXT, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "date",
"columnName": "date",
"affinity": "LONG",
"notNull": false
},
{
"fieldPath": "query",
"columnName": "query",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '69230710633494b4b331e10dae436fb7')"
]
}
}
@@ -0,0 +1,46 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "bf4a5b5f3cb9bc7383488bae14f5d4bd",
"entities": [
{
"tableName": "suggestions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`date` INTEGER NOT NULL, `query` TEXT NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "date",
"columnName": "date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "query",
"columnName": "query",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bf4a5b5f3cb9bc7383488bae14f5d4bd')"
]
}
}
@@ -0,0 +1,120 @@
package com.hippo.ehviewer.cronet
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.client.CHROME_ACCEPT
import com.hippo.ehviewer.client.CHROME_ACCEPT_LANGUAGE
import com.hippo.ehviewer.client.CHROME_USER_AGENT
import com.hippo.ehviewer.client.EhCookieStore
import eu.kanade.tachiyomi.util.system.logcat
import io.ktor.utils.io.pool.DirectByteBufferPool
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.HttpUrl.Companion.toHttpUrl
import okio.Path.Companion.toOkioPath
import org.chromium.net.CronetEngine
import org.chromium.net.CronetException
import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
import splitties.init.appCtx
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
private const val TAG = "CronetRequest"
val pool = DirectByteBufferPool(32)
val cronetHttpClient: CronetEngine = CronetEngine.Builder(appCtx).apply {
enableBrotli(true)
val cache = (appCtx.cacheDir.toOkioPath() / "http_cache").toFile().apply { mkdirs() }
setStoragePath(cache.absolutePath)
enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 100 * 1024)
setUserAgent(CHROME_USER_AGENT)
}.build()
val cronetHttpClientExecutor = EhApplication.baseOkHttpClient.dispatcher.executorService
// TODO: Rewrite this to use android.net.http.HttpEngine and make it Android 14 only when released
class CronetRequest {
lateinit var consumer: (ByteBuffer) -> Unit
lateinit var onResponse: CronetRequest.(UrlResponseInfo) -> Unit
lateinit var request: UrlRequest
lateinit var onError: (Throwable) -> Unit
lateinit var readerCont: Continuation<Unit>
val callback = object : UrlRequest.Callback() {
override fun onRedirectReceived(req: UrlRequest, info: UrlResponseInfo, url: String) {
logcat(tag = TAG) { "Redirected to $url" }
req.followRedirect()
}
override fun onResponseStarted(req: UrlRequest, info: UrlResponseInfo) {
onResponse(info)
}
override fun onReadCompleted(req: UrlRequest, info: UrlResponseInfo, data: ByteBuffer) {
data.flip()
consumer(data)
}
override fun onSucceeded(req: UrlRequest, info: UrlResponseInfo) {
readerCont.resume(Unit)
}
override fun onFailed(req: UrlRequest, info: UrlResponseInfo?, e: CronetException) {
onError(e)
}
}
}
inline fun cronetRequest(url: String, referer: String? = null, conf: UrlRequest.Builder.() -> Unit = {}) = CronetRequest().apply {
request = cronetHttpClient.newUrlRequestBuilder(url, callback, cronetHttpClientExecutor).apply {
addHeader("Cookie", EhCookieStore.getCookieHeader(url.toHttpUrl()))
addHeader("Accept", CHROME_ACCEPT)
addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE)
referer?.let { addHeader("Referer", it) }
}.apply(conf).build()
}
suspend inline fun CronetRequest.awaitBodyFully(crossinline callback: (ByteBuffer) -> Unit) {
val buffer = pool.borrow()
return try {
suspendCancellableCoroutine { cont ->
consumer = {
callback(it)
buffer.clear()
request.read(buffer)
}
onError = { readerCont.resumeWithException(it) }
readerCont = cont
request.read(buffer)
}
} finally {
pool.recycle(buffer)
}
}
suspend inline fun CronetRequest.copyToChannel(chan: FileChannel, crossinline listener: ((Int) -> Unit) = {}) = awaitBodyFully {
val bytes = chan.write(it)
listener(bytes)
}
suspend inline fun <R> CronetRequest.execute(crossinline callback: suspend CronetRequest.(UrlResponseInfo) -> R): R {
contract {
callsInPlace(callback, InvocationKind.EXACTLY_ONCE)
}
return coroutineScope {
suspendCancellableCoroutine { cont ->
onResponse = { launch { cont.resume(callback(it)) } }
cont.invokeOnCancellation { request.cancel() }
onError = { cont.resumeWithException(it) }
request.start()
}
}
}
fun UrlRequest.Builder.noCache(): UrlRequest.Builder = disableCache()
fun UrlResponseInfo.getHeadersMap(): Map<String, List<String>> = allHeaders
@@ -0,0 +1,12 @@
package com.hippo.ehviewer.util
import com.google.android.gms.net.CronetProviderInstaller
import com.hippo.ehviewer.Settings
import splitties.init.appCtx
val isCronetSupported: Boolean
get() = Settings.enableCronet && CronetProviderInstaller.isInstalled().also {
if (!it) {
CronetProviderInstaller.installProvider(appCtx)
}
}
+9 -13
View File
@@ -12,7 +12,8 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
@@ -22,27 +23,28 @@
android:allowBackup="true"
android:appCategory="image"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_scheme"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locale_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:targetApi="tiramisu">
<activity
android:name=".ui.login.LoginActivity"
android:name=".ui.ConfigureActivity"
android:exported="false"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"/>
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.MainActivity"
android:banner="@mipmap/ic_leanback_banner"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/AppTheme.Splash"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -69,8 +71,6 @@
<data android:scheme="https" />
<data android:host="exhentai.org" />
<data android:host="e-hentai.org" />
<data android:host="g.e-hentai.org" />
<data android:host="lofi.e-hentai.org" />
</intent-filter>
<intent-filter android:label="@string/keyword_search">
<action android:name="android.intent.action.SEND" />
@@ -125,22 +125,18 @@
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
</activity>
<activity
android:name=".ui.SettingsActivity"
android:configChanges="screenSize"
android:label="@string/settings"
android:theme="@style/AppTheme" />
<activity
android:name=".ui.SecurityActivity"
android:configChanges="screenSize"
android:label="@string/settings"
android:theme="@style/AppTheme" />
android:label="@string/settings" />
<activity
android:name=".shortcuts.ShortcutsActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity
android:name=".ui.WebViewActivity" />
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
@@ -1,34 +0,0 @@
From fc9fb0ea245259e346ab46c334f03b557fe63b6b Mon Sep 17 00:00:00 2001
From: tarsin <yuanqingxiang233@163.com>
Date: Tue, 22 Nov 2022 12:37:12 +0800
Subject: [PATCH] Don't build other binarys
---
CMakeLists.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 01c73cd..b461e68 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -592,7 +592,7 @@ check_symbol_exists(getopt_long getopt.h HAVE_GETOPT_LONG)
# xzdec
#############################################################################
-if(HAVE_GETOPT_LONG)
+if(0)
add_executable(xzdec
src/common/sysdefs.h
src/common/tuklib_common.h
@@ -630,7 +630,7 @@ endif()
# xz
#############################################################################
-if(NOT MSVC AND HAVE_GETOPT_LONG)
+if(0)
add_executable(xz
src/common/mythread.h
src/common/sysdefs.h
--
2.39.2
@@ -1,38 +0,0 @@
From 2a67861e6f06794f3671c4988bac12330d8422e3 Mon Sep 17 00:00:00 2001
From: tarsin <yuanqingxiang233@163.com>
Date: Fri, 27 May 2022 23:21:20 +0800
Subject: [PATCH 1/2] Don't build shared lib
---
libarchive/CMakeLists.txt | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt
index ff7ade00..00c2e170 100644
--- a/libarchive/CMakeLists.txt
+++ b/libarchive/CMakeLists.txt
@@ -242,12 +242,6 @@ ELSEIF(ARCHIVE_ACL_SUNOS)
LIST(APPEND libarchive_SOURCES archive_disk_acl_sunos.c)
ENDIF()
-# Libarchive is a shared library
-ADD_LIBRARY(archive SHARED ${libarchive_SOURCES} ${include_HEADERS})
-TARGET_INCLUDE_DIRECTORIES(archive PUBLIC .)
-TARGET_LINK_LIBRARIES(archive ${ADDITIONAL_LIBS})
-SET_TARGET_PROPERTIES(archive PROPERTIES SOVERSION ${SOVERSION})
-
# archive_static is a static library
ADD_LIBRARY(archive_static STATIC ${libarchive_SOURCES} ${include_HEADERS})
TARGET_LINK_LIBRARIES(archive_static ${ADDITIONAL_LIBS})
@@ -260,7 +254,7 @@ ENDIF(NOT WIN32 OR CYGWIN)
IF(ENABLE_INSTALL)
# How to install the libraries
- INSTALL(TARGETS archive archive_static
+ INSTALL(TARGETS archive_static
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
--
2.39.2
@@ -1,25 +0,0 @@
From c9298fd6dc0a8b04dd4b2247118b9cb895c46e58 Mon Sep 17 00:00:00 2001
From: tarsin <yuanqingxiang233@163.com>
Date: Sat, 23 Jul 2022 16:05:36 +0800
Subject: [PATCH 2/2] Insert link libs
---
CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 713e3bc5..7247640d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -415,7 +415,7 @@ IF(DEFINED __GNUWIN32PATH AND EXISTS "${__GNUWIN32PATH}")
# # endif
ENDIF(DEFINED __GNUWIN32PATH AND EXISTS "${__GNUWIN32PATH}")
-SET(ADDITIONAL_LIBS "")
+SET(ADDITIONAL_LIBS ${LIBARCHIVE_CUSTOM_LIBS})
#
# Find ZLIB
#
--
2.39.2
+22 -7
View File
@@ -3,7 +3,7 @@ project(ehviewer C ASM)
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=thin \
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 \
@@ -20,16 +20,24 @@ endif (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
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://git.tukaani.org/xz.git
GIT_TAG v5.4.1
PATCH_COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/0001-Don-t-build-other-binarys.patch || cd .
GIT_REPOSITORY https://github.com/FooIbar/xz.git
GIT_TAG v5.4.4
)
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
)
FetchContent_MakeAvailable(nettle)
add_subdirectory(nettle)
# Configure libnettle support for libarchive
@@ -50,13 +58,18 @@ SET(HAVE_LZMA_STREAM_ENCODER_MT 1)
SET(HAVE_LZMADEC_H 1)
SET(HAVE_LIBLZMADEC 1)
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/libarchive/libarchive
GIT_TAG v3.6.2
PATCH_COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/0001-Don-t-build-shared-lib.patch ${CMAKE_CURRENT_LIST_DIR}/0002-Insert-link-libs.patch || cd .
GIT_REPOSITORY https://github.com/FooIbar/libarchive.git
GIT_TAG v3.7.2
)
FetchContent_MakeAvailable(libarchive)
@@ -65,3 +78,5 @@ include_directories(${libarchive_SOURCE_DIR}/libarchive)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED archive.c gifutils.c natsort/strnatcmp.c)
target_link_libraries(${PROJECT_NAME} archive_static log)
add_custom_target("noop")
+8 -8
View File
@@ -279,7 +279,7 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
}
JNIEXPORT jint JNICALL
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_openArchive(JNIEnv *env, jclass thiz, jint fd,
Java_com_hippo_ehviewer_jni_ArchiveKt_openArchive(JNIEnv *env, jclass thiz, jint fd,
jlong size) {
EH_UNUSED(env);
EH_UNUSED(thiz);
@@ -359,7 +359,7 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_openArchive(JNIEnv *env, jcl
#pragma ide diagnostic ignored "UnreachableCode"
JNIEXPORT jobject JNICALL
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToByteBuffer(JNIEnv *env, jclass thiz,
Java_com_hippo_ehviewer_jni_ArchiveKt_extractToByteBuffer(JNIEnv *env, jclass thiz,
jint index) {
EH_UNUSED(env);
EH_UNUSED(thiz);
@@ -389,7 +389,7 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToByteBuffer(JNIEnv *
#pragma clang diagnostic pop
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_closeArchive(JNIEnv *env, jclass thiz) {
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++)
@@ -408,14 +408,14 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_closeArchive(JNIEnv *env, jc
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_needPassword(JNIEnv *env, jclass thiz) {
Java_com_hippo_ehviewer_jni_ArchiveKt_needPassword(JNIEnv *env, jclass thiz) {
EH_UNUSED(env);
EH_UNUSED(thiz);
return need_encrypt;
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_providePassword(JNIEnv *env, jclass thiz,
Java_com_hippo_ehviewer_jni_ArchiveKt_providePassword(JNIEnv *env, jclass thiz,
jstring str) {
EH_UNUSED(thiz);
struct archive_entry *entry;
@@ -446,7 +446,7 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_providePassword(JNIEnv *env,
}
JNIEXPORT jstring JNICALL
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_getFilename(JNIEnv *env, jclass thiz,
Java_com_hippo_ehviewer_jni_ArchiveKt_getFilename(JNIEnv *env, jclass thiz,
jint index) {
EH_UNUSED(env);
EH_UNUSED(thiz);
@@ -462,7 +462,7 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_getFilename(JNIEnv *env, jcl
}
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToFd(JNIEnv *env, jclass thiz,
Java_com_hippo_ehviewer_jni_ArchiveKt_extractToFd(JNIEnv *env, jclass thiz,
jint index, jint fd) {
EH_UNUSED(env);
EH_UNUSED(thiz);
@@ -477,7 +477,7 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToFd(JNIEnv *env, jcl
}
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_releaseByteBuffer(JNIEnv *env, jclass thiz,
Java_com_hippo_ehviewer_jni_ArchiveKt_releaseByteBuffer(JNIEnv *env, jclass thiz,
jobject buffer) {
EH_UNUSED(thiz);
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
+23 -7
View File
@@ -1,6 +1,7 @@
#include <jni.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <android/log.h>
#include <sys/stat.h>
#include <sys/mman.h>
@@ -11,6 +12,7 @@
#define GIF_HEADER_87A "GIF87a"
#define GIF_HEADER_89A "GIF89a"
#define GIF_HEADER_LENGTH 6
static int FRAME_DELAY_START_MARKER = 0x0021F904;
@@ -21,7 +23,8 @@ typedef signed char byte;
#define DEFAULT_FRAME_DELAY 10
static inline bool isGif(void *addr) {
return !memcmp(addr, GIF_HEADER_87A, 6) || !memcmp(addr, GIF_HEADER_89A, 6);
return !memcmp(addr, GIF_HEADER_87A, GIF_HEADER_LENGTH) ||
!memcmp(addr, GIF_HEADER_89A, GIF_HEADER_LENGTH);
}
static void doRewrite(byte *addr, size_t size) {
@@ -42,20 +45,33 @@ static void doRewrite(byte *addr, size_t size) {
}
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_ehviewer_jni_GifUtilsKt_isGif(JNIEnv *env, jclass clazz, jint fd) {
byte buffer[GIF_HEADER_LENGTH];
return read(fd, buffer, GIF_HEADER_LENGTH) == GIF_HEADER_LENGTH &&
isGif(buffer);
}
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_image_ImageKt_rewriteGifSource(JNIEnv *env, jclass clazz, jobject buffer) {
Java_com_hippo_ehviewer_jni_GifUtilsKt_rewriteGifSource(JNIEnv *env, jclass clazz, jobject buffer) {
byte *addr = (*env)->GetDirectBufferAddress(env, buffer);
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
doRewrite(addr, size);
}
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_image_ImageKt_rewriteGifSource2(JNIEnv *env, jclass clazz, jint fd) {
JNIEXPORT jobject JNICALL
Java_com_hippo_ehviewer_jni_GifUtilsKt_mmap(JNIEnv *env, jclass clazz, jint fd) {
struct stat64 st;
fstat64(fd, &st);
size_t size = st.st_size;
byte *addr = mmap64(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) return;
doRewrite(addr, size);
byte *addr = mmap64(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED) return NULL;
return (*env)->NewDirectByteBuffer(env, addr, size);
}
JNIEXPORT void JNICALL
Java_com_hippo_ehviewer_jni_GifUtilsKt_munmap(JNIEnv *env, jclass clazz, jobject buffer) {
byte *addr = (*env)->GetDirectBufferAddress(env, buffer);
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
munmap(addr, size);
}
+1
View File
@@ -0,0 +1 @@
/nettle
+1 -2
View File
@@ -32,7 +32,6 @@ set(LIBNETTLE_SOURCES
nettle/aes256-meta.c
nettle/nist-keywrap.c
nettle/arcfour.c
nettle/arcfour-crypt.c
nettle/arctwo.c
nettle/arctwo-meta.c
nettle/blowfish.c
@@ -177,7 +176,7 @@ set(LIBNETTLE_SOURCES
nettle/sha1-compress.c
nettle/sha1-meta.c
nettle/sha256.c
nettle/sha256-compress.c
nettle/sha256-compress-n.c
nettle/sha224-meta.c
nettle/sha256-meta.c
nettle/sha512.c
+25 -20
View File
@@ -4,13 +4,19 @@
/* Define if building universal (internal helper macro) */
/* #undef AC_APPLE_UNIVERSAL_BUILD */
/* Define to 1 if using 'alloca.c'. */
/* 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 to 1 if you have `alloca', as a function or macro. */
#define HAVE_ALLOCA 1
/* Define to 1 if <alloca.h> works. */
/* 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 */
@@ -46,6 +52,9 @@
/* 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 */
@@ -71,7 +80,7 @@
/* #undef HAVE_NATIVE_cbc_aes256_encrypt */
/* #undef HAVE_NATIVE_chacha_core */
/* #undef HAVE_NATIVE_chacha_2core */
/* #undef HAVE_NATIVE_chacha_3core */
#define HAVE_NATIVE_chacha_3core 1
/* #undef HAVE_NATIVE_chacha_4core */
/* #undef HAVE_NATIVE_fat_chacha_2core */
/* #undef HAVE_NATIVE_fat_chacha_3core */
@@ -88,13 +97,18 @@
/* #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 */
/* #undef HAVE_NATIVE_salsa20_2core */
#define HAVE_NATIVE_salsa20_2core 1
/* #undef HAVE_NATIVE_fat_salsa20_2core */
/* #undef HAVE_NATIVE_sha1_compress */
/* #undef HAVE_NATIVE_sha256_compress */
/* #undef HAVE_NATIVE_sha256_compress_n */
/* #undef HAVE_NATIVE_sha512_compress */
/* #undef HAVE_NATIVE_sha3_permute */
/* #undef HAVE_NATIVE_umac_nh */
@@ -107,14 +121,11 @@
/* #undef HAVE_OPENSSL_EVP_H */
/* Define to 1 if you have the `secure_getenv' function. */
/* #undef HAVE_SECURE_GETENV */
#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 <stdio.h> header file. */
#define HAVE_STDIO_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
@@ -130,9 +141,6 @@
/* 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/time.h> header file. */
#define HAVE_SYS_TIME_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
@@ -149,7 +157,7 @@
#define PACKAGE_NAME "nettle"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "nettle 3.8.1"
#define PACKAGE_STRING "nettle 3.9.1"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "nettle"
@@ -158,7 +166,7 @@
#define PACKAGE_URL ""
/* Define to the version of this package. */
#define PACKAGE_VERSION "3.8.1"
#define PACKAGE_VERSION "3.9.1"
/* The size of `long', as computed by sizeof. */
#define SIZEOF_LONG __SIZEOF_LONG__
@@ -174,13 +182,10 @@
STACK_DIRECTION = 0 => direction of growth unknown */
/* #undef STACK_DIRECTION */
/* Define to 1 if all of the C90 standard headers exist (not just the ones
required in a freestanding environment). This macro is provided for
backward compatibility; new code need not use it. */
/* 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>. This
macro is obsolete. */
/* 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 */
+1 -1
View File
@@ -41,7 +41,7 @@ extern "C" {
/* Individual version numbers in decimal */
#define NETTLE_VERSION_MAJOR 3
#define NETTLE_VERSION_MINOR 8
#define NETTLE_VERSION_MINOR 9
#define NETTLE_USE_MINI_GMP 0
@@ -1,177 +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;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Environment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.ehviewer.util.ReadableTime;
import com.hippo.ehviewer.yorozuya.FileUtils;
import com.hippo.ehviewer.yorozuya.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class AppConfig {
public static final String APP_DIRNAME = "EhViewer";
private static final String DOWNLOAD = "download";
private static final String TEMP = "temp";
private static final String PARSE_ERROR = "parse_error";
private static final String CRASH = "crash";
@SuppressLint("StaticFieldLeak")
private static Context sContext;
public static void initialize(Context context) {
sContext = context.getApplicationContext();
}
@Nullable
public static File getExternalAppDir() {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File dir = sContext.getExternalFilesDir(null);
return FileUtils.ensureDirectory(dir) ? dir : null;
}
return null;
}
/**
* mkdirs and get
*/
@Nullable
public static File getDirInExternalAppDir(String filename) {
File appFolder = getExternalAppDir();
if (appFolder != null) {
File dir = new File(appFolder, filename);
return FileUtils.ensureDirectory(dir) ? dir : null;
}
return null;
}
@Nullable
public static File getFileInExternalAppDir(String filename) {
File appFolder = getExternalAppDir();
if (appFolder != null) {
File file = new File(appFolder, filename);
return FileUtils.ensureFile(file) ? file : null;
}
return null;
}
@Nullable
public static File getDefaultDownloadDir() {
return getDirInExternalAppDir(DOWNLOAD);
}
@Nullable
public static File getExternalTempDir() {
File dir = sContext.getExternalCacheDir();
File file;
if (null != dir && FileUtils.ensureDirectory(file = new File(dir, TEMP))) {
return file;
} else {
return null;
}
}
@Nullable
public static File getExternalCopyTempDir() {
File dir = sContext.getExternalCacheDir();
File file;
if (null != dir && FileUtils.ensureDirectory(file = new File(dir, "copy"))) {
return file;
} else {
return null;
}
}
@Nullable
public static File getExternalParseErrorDir() {
return getDirInExternalAppDir(PARSE_ERROR);
}
@Nullable
public static File getExternalCrashDir() {
return getDirInExternalAppDir(CRASH);
}
@Nullable
public static File getTempDir() {
File dir = sContext.getCacheDir();
File file;
if (null != dir && FileUtils.ensureDirectory(file = new File(dir, TEMP))) {
return file;
} else {
return null;
}
}
@Nullable
public static File createTempFile() {
return FileUtils.createTempFile(getTempDir(), null);
}
public static void saveParseErrorBody(@NonNull ParseException e) {
File dir = getExternalParseErrorDir();
if (null == dir) {
return;
}
File file = new File(dir, ReadableTime.getFilenamableTime(System.currentTimeMillis()) + ".txt");
OutputStream os = null;
try {
os = new FileOutputStream(file);
String message = e.getMessage();
String body = e.getBody();
if (null != message) {
os.write(message.getBytes(StandardCharsets.UTF_8));
os.write('\n');
}
os.write(body.getBytes(StandardCharsets.UTF_8));
os.flush();
} catch (IOException e1) {
// Ignore
} finally {
IOUtils.closeQuietly(os);
}
}
@Nullable
public static File getFilesDir(String name) {
File dir = sContext.getFilesDir();
if (dir == null) {
return null;
}
dir = new File(dir, name);
if (dir.isDirectory() || dir.mkdirs()) {
return dir;
} else {
return null;
}
}
}
@@ -1,465 +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
import android.content.Context
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
import androidx.paging.PagingSource
import androidx.room.Room.databaseBuilder
import com.hippo.ehviewer.EhApplication.Companion.ehDatabase
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.dao.BasicDao
import com.hippo.ehviewer.dao.DownloadDirname
import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.dao.DownloadLabel
import com.hippo.ehviewer.dao.EHDB_MIGRATION_4_5
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 com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.util.sendTo
object EhDB {
private const val CUR_DB_VER = 4
private val db = ehDatabase
// Fix state
@get:Synchronized
val allDownloadInfo: List<DownloadInfo>
get() = db.downloadsDao().list().onEach {
if (it.state == DownloadInfo.STATE_WAIT || it.state == DownloadInfo.STATE_DOWNLOAD) {
it.state = DownloadInfo.STATE_NONE
}
}
@Synchronized
fun updateDownloadInfo(downloadInfos: List<DownloadInfo>) {
val dao = db.downloadsDao()
dao.update(downloadInfos)
}
@Synchronized
fun putDownloadInfo(downloadInfo: DownloadInfo) {
db.downloadsDao().run {
if (load(downloadInfo.gid) != null) {
update(downloadInfo)
} else {
insert(downloadInfo)
}
}
}
@Synchronized
fun removeDownloadInfo(downloadInfo: DownloadInfo) {
db.downloadsDao().delete(downloadInfo)
}
@Synchronized
fun getDownloadDirname(gid: Long): String? {
val dao = db.downloadDirnameDao()
val raw = dao.load(gid)
return raw?.dirname
}
@Synchronized
fun putDownloadDirname(gid: Long, dirname: String?) {
val dao = db.downloadDirnameDao()
var raw = dao.load(gid)
if (raw != null) {
raw.dirname = dirname
dao.update(raw)
} else {
raw = DownloadDirname(gid, dirname)
dao.insert(raw)
}
}
@Synchronized
fun removeDownloadDirname(gid: Long) {
val dao = db.downloadDirnameDao()
dao.deleteByKey(gid)
}
@Synchronized
fun clearDownloadDirname() {
val dao = db.downloadDirnameDao()
dao.deleteAll()
}
@get:Synchronized
val allDownloadLabelList: List<DownloadLabel>
get() = db.downloadLabelDao().list()
@Synchronized
fun addDownloadLabel(label: String): DownloadLabel {
val dao = db.downloadLabelDao()
val raw = DownloadLabel()
raw.label = label
raw.time = System.currentTimeMillis()
raw.id = dao.insert(raw)
return raw
}
@Synchronized
fun addDownloadLabel(raw: DownloadLabel): DownloadLabel {
// Reset id
raw.id = null
val dao = db.downloadLabelDao()
raw.id = dao.insert(raw)
return raw
}
@Synchronized
fun updateDownloadLabel(raw: DownloadLabel?) {
val dao = db.downloadLabelDao()
dao.update(raw!!)
}
@Synchronized
fun moveDownloadLabel(fromPosition: Int, toPosition: Int) {
if (fromPosition == toPosition) {
return
}
val reverse = fromPosition > toPosition
val offset = if (reverse) toPosition else fromPosition
val limit = if (reverse) fromPosition - toPosition + 1 else toPosition - fromPosition + 1
val dao = db.downloadLabelDao()
val list = dao.list(offset, limit)
val step = if (reverse) 1 else -1
val start = if (reverse) limit - 1 else 0
val end = if (reverse) 0 else limit - 1
val toTime = list[end].time
var i = end
while (if (reverse) i < start else i > start) {
list[i].time = list[i + step].time
i += step
}
list[start].time = toTime
dao.update(list)
}
@Synchronized
fun removeDownloadLabel(raw: DownloadLabel?) {
val dao = db.downloadLabelDao()
dao.delete(raw!!)
}
@get:Synchronized
val allLocalFavorites: List<GalleryInfo>
get() {
val dao = db.localFavoritesDao()
val list = dao.list()
return ArrayList<GalleryInfo>(list)
}
@Synchronized
fun searchLocalFavorites(query: String): List<GalleryInfo> {
val dao = db.localFavoritesDao()
val list = dao.list("%$query%")
return ArrayList<GalleryInfo>(list)
}
@Synchronized
fun removeLocalFavorites(gid: Long) {
db.localFavoritesDao().deleteByKey(gid)
}
@Synchronized
fun removeLocalFavorites(gidArray: LongArray) {
val dao = db.localFavoritesDao()
for (gid in gidArray) {
dao.deleteByKey(gid)
}
}
@JvmStatic
@Synchronized
fun containLocalFavorites(gid: Long): Boolean {
val dao = db.localFavoritesDao()
return dao.contains(gid)
}
@Synchronized
fun putLocalFavorites(galleryInfo: GalleryInfo) {
val dao = db.localFavoritesDao()
if (null == dao.load(galleryInfo.gid)) {
val info: LocalFavoriteInfo
if (galleryInfo is LocalFavoriteInfo) {
info = galleryInfo
} else {
info = LocalFavoriteInfo(galleryInfo)
info.time = System.currentTimeMillis()
}
dao.insert(info)
}
}
@Synchronized
fun putLocalFavorites(galleryInfoList: List<GalleryInfo>) {
for (gi in galleryInfoList) {
putLocalFavorites(gi)
}
}
@get:Synchronized
val allQuickSearch: List<QuickSearch>
get() {
val dao = db.quickSearchDao()
return dao.list()
}
@Synchronized
fun insertQuickSearch(quickSearch: QuickSearch) {
val dao = db.quickSearchDao()
quickSearch.id = null
quickSearch.time = System.currentTimeMillis()
quickSearch.id = dao.insert(quickSearch)
}
@Synchronized
fun importQuickSearch(quickSearchList: List<QuickSearch?>) {
val dao = db.quickSearchDao()
for (quickSearch in quickSearchList) {
dao.insert(quickSearch!!)
}
}
@Synchronized
fun deleteQuickSearch(quickSearch: QuickSearch?) {
val dao = db.quickSearchDao()
if (quickSearch != null) {
dao.delete(quickSearch)
}
}
@Synchronized
fun moveQuickSearch(fromPosition: Int, toPosition: Int) {
if (fromPosition == toPosition) {
return
}
val reverse = fromPosition > toPosition
val offset = if (reverse) toPosition else fromPosition
val limit = if (reverse) fromPosition - toPosition + 1 else toPosition - fromPosition + 1
val dao = db.quickSearchDao()
val list = dao.list(offset, limit)
val step = if (reverse) 1 else -1
val start = if (reverse) limit - 1 else 0
val end = if (reverse) 0 else limit - 1
val toTime = list[end].time
var i = end
while (if (reverse) i < start else i > start) {
list[i].time = list[i + step].time
i += step
}
list[start].time = toTime
dao.update(list)
}
@get:Synchronized
val historyLazyList: PagingSource<Int, HistoryInfo>
get() = db.historyDao().listLazy()
@Synchronized
fun putHistoryInfo(galleryInfo: GalleryInfo?) {
val dao = db.historyDao()
val info: HistoryInfo = if (galleryInfo is HistoryInfo) {
galleryInfo
} else {
HistoryInfo(galleryInfo!!)
}
info.time = System.currentTimeMillis()
if (null != dao.load(info.gid)) {
dao.update(info)
} else {
dao.insert(info)
}
}
@Synchronized
fun putHistoryInfoNonRefresh(info: GalleryInfo) {
val dao = db.historyDao()
val i = dao.load(info.gid)
if (null != i) {
val historyInfo: HistoryInfo
if (info is HistoryInfo) {
historyInfo = info
} else {
historyInfo = HistoryInfo(info)
historyInfo.time = i.time
}
dao.update(historyInfo)
}
}
@Synchronized
fun putHistoryInfo(historyInfoList: List<HistoryInfo>) {
val dao = db.historyDao()
for (info in historyInfoList) {
if (null == dao.load(info.gid)) {
dao.insert(info)
}
}
}
@Synchronized
fun deleteHistoryInfo(info: HistoryInfo?) {
val dao = db.historyDao()
dao.delete(info!!)
}
@Synchronized
fun clearHistoryInfo() {
val dao = db.historyDao()
dao.deleteAll()
}
@get:Synchronized
val allFilter: List<Filter>
get() = db.filterDao().list()
@Synchronized
fun addFilter(filter: Filter): Boolean {
val existFilter: Filter? = try {
db.filterDao().load(filter.text!!, filter.mode)
} catch (e: Exception) {
null
}
return if (existFilter == null) {
filter.id = null
filter.id = db.filterDao().insert(filter)
true
} else {
false
}
}
@Synchronized
fun deleteFilter(filter: Filter) {
db.filterDao().delete(filter)
}
@Synchronized
fun triggerFilter(filter: Filter) {
filter.enable = filter.enable?.not() ?: false
db.filterDao().update(filter)
}
private fun <T> copyDao(from: BasicDao<T>, to: BasicDao<T>) {
val list = from.list()
for (item in list) to.insert(item)
}
@Synchronized
fun exportDB(context: Context, uri: Uri): Boolean {
val ehExportName = "eh.export.db"
runCatching {
context.deleteDatabase(ehExportName)
val newDb = databaseBuilder(context, EhDatabase::class.java, ehExportName).build()
copyDao(db.downloadsDao(), newDb.downloadsDao())
copyDao(db.downloadLabelDao(), newDb.downloadLabelDao())
copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao())
copyDao(db.historyDao(), newDb.historyDao())
copyDao(db.quickSearchDao(), newDb.quickSearchDao())
copyDao(db.localFavoritesDao(), newDb.localFavoritesDao())
copyDao(db.filterDao(), newDb.filterDao())
newDb.close()
val dbFile = context.getDatabasePath(ehExportName)
context.contentResolver.openFileDescriptor(uri, "rw")!!.use { toFd ->
ParcelFileDescriptor.open(dbFile, MODE_READ_ONLY).use { fromFd ->
fromFd sendTo toFd
}
}
return true
}.onFailure {
it.printStackTrace()
}
return false
}
/**
* @return error string, null for no error
*/
@Synchronized
fun importDB(context: Context, uri: Uri): String? {
runCatching {
val oldDB = databaseBuilder(context, EhDatabase::class.java, "tmp.db")
.createFromInputStream { context.contentResolver.openInputStream(uri) }
.addMigrations(EHDB_MIGRATION_4_5)
.build()
// Download label
val manager = DownloadManager
runCatching {
val downloadLabelList = oldDB.downloadLabelDao().list()
manager.addDownloadLabel(downloadLabelList)
}
// Downloads
runCatching {
val downloadInfoList = oldDB.downloadsDao().list()
manager.addDownload(downloadInfoList, false)
}
// Download dirname
runCatching {
oldDB.downloadDirnameDao().list().forEach {
putDownloadDirname(it.gid, it.dirname)
}
}
// History
runCatching {
val historyInfoList = oldDB.historyDao().list()
putHistoryInfo(historyInfoList)
}
// QuickSearch
runCatching {
val quickSearchList = oldDB.quickSearchDao().list()
val currentQuickSearchList = db.quickSearchDao().list()
val importList = quickSearchList.mapNotNull { newQS ->
newQS.takeIf { currentQuickSearchList.find { it.name == newQS.name } == null }
}
importQuickSearch(importList)
}
// LocalFavorites
runCatching {
oldDB.localFavoritesDao().list().forEach {
putLocalFavorites(it)
}
}
// Filter
runCatching {
val filterList = oldDB.filterDao().list()
val currentFilterList = db.filterDao().list()
filterList.forEach {
if (it !in currentFilterList) addFilter(it)
}
}
oldDB.close()
}.onFailure {
it.printStackTrace()
return context.getString(R.string.cant_read_the_file)
}
return null
}
}
@@ -1,92 +0,0 @@
/*
* Copyright 2019 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
import android.text.TextUtils
import com.hippo.ehviewer.Settings.proxyIp
import com.hippo.ehviewer.Settings.proxyPort
import com.hippo.ehviewer.Settings.proxyType
import com.hippo.ehviewer.network.InetValidator.isValidInetPort
import com.hippo.ehviewer.util.ExceptionUtils
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.ProxySelector
import java.net.SocketAddress
import java.net.URI
class EhProxySelector internal constructor() : ProxySelector() {
private var delegation: ProxySelector? = null
private var alternative: ProxySelector?
init {
alternative = getDefault() ?: NullProxySelector()
updateProxy()
}
fun updateProxy() {
delegation = when (proxyType) {
TYPE_DIRECT -> NullProxySelector()
TYPE_SYSTEM -> alternative
TYPE_HTTP, TYPE_SOCKS -> null
else -> alternative
}
}
override fun select(uri: URI): List<Proxy> {
val type = proxyType
if (type == TYPE_HTTP || type == TYPE_SOCKS) {
runCatching {
val ip = proxyIp
val port = proxyPort
if (!TextUtils.isEmpty(ip) && isValidInetPort(port)) {
val inetAddress = InetAddress.getByName(ip)
val socketAddress = InetSocketAddress(inetAddress, port)
return listOf(
Proxy(
if (type == TYPE_HTTP) Proxy.Type.HTTP else Proxy.Type.SOCKS,
socketAddress,
),
)
}
}.onFailure {
ExceptionUtils.throwIfFatal(it)
it.printStackTrace()
}
}
return delegation?.select(uri) ?: alternative!!.select(uri)
}
override fun connectFailed(uri: URI, sa: SocketAddress, ioe: IOException) {
delegation?.select(uri)
}
private class NullProxySelector : ProxySelector() {
override fun select(uri: URI): List<Proxy> {
return listOf(Proxy.NO_PROXY)
}
override fun connectFailed(uri: URI, sa: SocketAddress, ioe: IOException) {}
}
companion object {
const val TYPE_DIRECT = 0
const val TYPE_SYSTEM = 1
const val TYPE_HTTP = 2
const val TYPE_SOCKS = 3
}
}
@@ -1,74 +0,0 @@
/*
* Copyright 2019 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;
import android.annotation.SuppressLint;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.yorozuya.IntIdGenerator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FavouriteStatusRouter {
private static final String KEY_DATA_MAP_NEXT_ID = "data_map_next_id";
private final IntIdGenerator idGenerator = new IntIdGenerator(Settings.getInt(KEY_DATA_MAP_NEXT_ID, 0));
@SuppressLint("UseSparseArrays")
private final HashMap<Integer, Map<Long, GalleryInfo>> maps = new HashMap<>();
private final List<Listener> listeners = new ArrayList<>();
public int saveDataMap(Map<Long, GalleryInfo> map) {
int id = idGenerator.nextId();
maps.put(id, map);
Settings.putInt(KEY_DATA_MAP_NEXT_ID, idGenerator.nextId());
return id;
}
public Map<Long, GalleryInfo> restoreDataMap(int id) {
return maps.remove(id);
}
public void modifyFavourites(long gid, int slot) {
for (Map<Long, GalleryInfo> map : maps.values()) {
GalleryInfo info = map.get(gid);
if (info != null) {
info.setFavoriteSlot(slot);
}
}
for (Listener listener : listeners) {
listener.onModifyFavourites(gid, slot);
}
}
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
public interface Listener {
void onModifyFavourites(long gid, int slot);
}
}
@@ -1,343 +0,0 @@
/*
* Copyright 2018 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;
/*
* Created by Hippo on 2018/3/21.
*/
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.database.MSQLiteBuilder;
import com.hippo.ehviewer.util.SqlUtils;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
public class Hosts {
private static final int VERSION_1 = 1;
private static final String TABLE_HOSTS = "HOSTS";
private static final String COLUMN_HOST = "HOST";
private static final String COLUMN_IP = "IP";
private static final int DB_VERSION = VERSION_1;
private final SQLiteDatabase db;
public Hosts(Context context, String name) {
SQLiteOpenHelper helper = new MSQLiteBuilder()
.version(VERSION_1)
.createTable(TABLE_HOSTS)
.insertColumn(TABLE_HOSTS, COLUMN_HOST, String.class)
.insertColumn(TABLE_HOSTS, COLUMN_IP, String.class)
.build(context, name, DB_VERSION);
db = helper.getWritableDatabase();
}
@Nullable
public static InetAddress toInetAddress(String host, String ip) {
if (!isValidHost(host)) {
return null;
}
if (ip == null) {
return null;
}
byte[] bytes = parseV4(ip);
if (bytes == null) {
bytes = parseV6(ip);
}
if (bytes == null) {
return null;
}
try {
return InetAddress.getByAddress(host, bytes);
} catch (UnknownHostException e) {
return null;
}
}
/**
* Returns true if the host is valid.
*/
public static boolean isValidHost(String host) {
if (host == null) {
return false;
}
if (host.length() > 253) {
return false;
}
int labelLength = 0;
for (int i = 0, n = host.length(); i < n; i++) {
char ch = host.charAt(i);
if (ch == '.') {
if (labelLength < 1 || labelLength > 63) {
return false;
}
labelLength = 0;
} else {
labelLength++;
}
if ((ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '-' && ch != '.') {
return false;
}
}
return labelLength >= 1 && labelLength <= 63;
}
/**
* Returns true if ips are valid.
*/
public static boolean isValidIp(String ips) {
if (ips == null) {
return false;
}
String[] list = ips.split("\\+");
for (String ip : list) {
if (parseV4(ip) == null && parseV6(ip) == null) {
return false;
}
}
return true;
}
// org.xbill.DNS.Address.parseV4
@Nullable
private static byte[] parseV4(String s) {
int numDigits;
int currentOctet;
byte[] values = new byte[4];
int currentValue;
int length = s.length();
currentOctet = 0;
currentValue = 0;
numDigits = 0;
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
/* Can't have more than 3 digits per octet. */
if (numDigits == 3)
return null;
/* Octets shouldn't start with 0, unless they are 0. */
if (numDigits > 0 && currentValue == 0)
return null;
numDigits++;
currentValue *= 10;
currentValue += (c - '0');
/* 255 is the maximum value for an octet. */
if (currentValue > 255)
return null;
} else if (c == '.') {
/* Can't have more than 3 dots. */
if (currentOctet == 3)
return null;
/* Two consecutive dots are bad. */
if (numDigits == 0)
return null;
values[currentOctet++] = (byte) currentValue;
currentValue = 0;
numDigits = 0;
} else
return null;
}
/* Must have 4 octets. */
if (currentOctet != 3)
return null;
/* The fourth octet can't be empty. */
if (numDigits == 0)
return null;
values[currentOctet] = (byte) currentValue;
return values;
}
// org.xbill.DNS.Address.parseV6
@Nullable
private static byte[] parseV6(String s) {
int range = -1;
byte[] data = new byte[16];
String[] tokens = s.split(":", -1);
int first = 0;
int last = tokens.length - 1;
if (tokens[0].length() == 0) {
// If the first two tokens are empty, it means the string
// started with ::, which is fine. If only the first is
// empty, the string started with :, which is bad.
if (last - first > 0 && tokens[1].length() == 0)
first++;
else
return null;
}
if (tokens[last].length() == 0) {
// If the last two tokens are empty, it means the string
// ended with ::, which is fine. If only the last is
// empty, the string ended with :, which is bad.
if (last - first > 0 && tokens[last - 1].length() == 0)
last--;
else
return null;
}
if (last - first + 1 > 8)
return null;
int i, j;
for (i = first, j = 0; i <= last; i++) {
if (tokens[i].length() == 0) {
if (range >= 0)
return null;
range = j;
continue;
}
if (tokens[i].indexOf('.') >= 0) {
// An IPv4 address must be the last component
if (i < last)
return null;
// There can't have been more than 6 components.
if (i > 6)
return null;
byte[] v4addr = parseV4(tokens[i]);
if (v4addr == null)
return null;
for (int k = 0; k < 4; k++)
data[j++] = v4addr[k];
break;
}
try {
for (int k = 0; k < tokens[i].length(); k++) {
char c = tokens[i].charAt(k);
if (Character.digit(c, 16) < 0)
return null;
}
int x = Integer.parseInt(tokens[i], 16);
if (x > 0xFFFF || x < 0)
return null;
data[j++] = (byte) (x >>> 8);
data[j++] = (byte) (x & 0xFF);
} catch (NumberFormatException e) {
return null;
}
}
if (j < 16 && range < 0)
return null;
if (range >= 0) {
int empty = 16 - j;
System.arraycopy(data, range, data, range + empty, j - range);
for (i = range; i < range + empty; i++)
data[i] = 0;
}
return data;
}
/**
* Gets a InetAddress with the host.
*/
@Nullable
public List<InetAddress> get(String host) {
if (!isValidHost(host)) {
return null;
}
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[]{host})) {
if (cursor.moveToNext()) {
String[] ips = SqlUtils.getString(cursor, COLUMN_IP, null).split("\\+");
List<InetAddress> addresses = new ArrayList<>();
for (String ip : ips) {
addresses.add(toInetAddress(host, ip));
}
return addresses;
} else {
return null;
}
}
}
public boolean contains(String host) {
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[]{host})) {
return cursor.moveToNext();
}
}
/**
* Puts the host-ip pair into this hosts.
*/
public boolean put(String host, String ip) {
if (!isValidHost(host) || !isValidIp(ip)) {
return false;
}
ContentValues values = new ContentValues();
values.put(COLUMN_HOST, host);
values.put(COLUMN_IP, ip);
if (contains(host)) {
db.update(TABLE_HOSTS, values, COLUMN_HOST + " = ?", new String[]{host});
} else {
db.insert(TABLE_HOSTS, null, values);
}
return true;
}
/**
* Puts delete the entry with the host.
*/
public void delete(String host) {
db.delete(TABLE_HOSTS, COLUMN_HOST + " = ?", new String[]{host});
}
/**
* Get all data from this host.
*/
public List<Pair<String, String>> getAll() {
List<Pair<String, String>> result = new ArrayList<>();
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + ";", null)) {
while (cursor.moveToNext()) {
String host = SqlUtils.getString(cursor, COLUMN_HOST, null);
String ip = SqlUtils.getString(cursor, COLUMN_IP, null);
result.add(new Pair<>(host, ip));
}
}
return result;
}
}
@@ -1,641 +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
import android.content.SharedPreferences
import android.net.Uri
import android.util.Log
import androidx.annotation.DimenRes
import androidx.preference.PreferenceManager
import com.hippo.ehviewer.EhApplication.Companion.application
import com.hippo.ehviewer.client.data.FavListUrlBuilder
import com.hippo.ehviewer.ui.scene.GalleryListScene
import com.hippo.ehviewer.yorozuya.LayoutUtils.dp2pix
import com.hippo.ehviewer.yorozuya.NumberUtils
import com.hippo.unifile.UniFile
import java.util.Locale
object Settings {
/********************
* Eh
*/
const val KEY_THEME = "theme"
const val KEY_ACCOUNT = "account"
const val KEY_IMAGE_LIMITS = "image_limits"
const val KEY_U_CONFIG = "uconfig"
const val KEY_MY_TAGS = "mytags"
const val KEY_BLACK_DARK_THEME = "black_dark_theme"
const val DEFAULT_BLACK_DARK_THEME = false
const val THEME_SYSTEM = -1
const val KEY_GALLERY_SITE = "gallery_site"
const val KEY_LIST_MODE = "list_mode"
const val KEY_DETAIL_SIZE = "detail_size"
const val KEY_THUMB_SIZE = "thumb_size_"
const val KEY_THUMB_RESOLUTION = "thumb_resolution"
const val KEY_SHOW_JPN_TITLE = "show_jpn_title"
const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
const val KEY_TAG_TRANSLATIONS_SOURCE = "tag_translations_source"
const val KEY_REQUEST_NEWS = "request_news"
const val KEY_REQUEST_NEWS_TIMER = "request_news_timer"
const val KEY_REQUEST_NEWS_TIMER_HOUR = "request_news_timer_hour"
const val KEY_REQUEST_NEWS_TIMER_MINUTE = "request_news_timer_minute"
const val KEY_HIDE_HV_EVENTS = "hide_hv_events"
val SIGN_IN_REQUIRED = arrayOf(
KEY_IMAGE_LIMITS,
KEY_GALLERY_SITE,
KEY_U_CONFIG,
KEY_MY_TAGS,
KEY_SHOW_JPN_TITLE,
KEY_REQUEST_NEWS,
KEY_REQUEST_NEWS_TIMER,
KEY_HIDE_HV_EVENTS,
)
/********************
* Privacy and Security
*/
const val KEY_SEC_SECURITY = "enable_secure"
const val VALUE_SEC_SECURITY = false
/********************
* Download
*/
const val KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme"
const val KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority"
const val KEY_DOWNLOAD_SAVE_PATH = "image_path"
const val KEY_NOTIFICATION_REQUIRED = "notification_required"
const val KEY_DOWNLOAD_SAVE_QUERY = "image_query"
const val KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment"
const val KEY_MEDIA_SCAN = "media_scan"
const val INVALID_DEFAULT_FAV_SLOT = -2
/********************
* Advanced
*/
const val KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body"
const val KEY_SECURITY = "require_unlock"
const val KEY_SECURITY_DELAY = "require_unlock_delay"
const val KEY_READ_CACHE_SIZE = "read_cache_size"
const val DEFAULT_READ_CACHE_SIZE = 640
const val KEY_BUILT_IN_HOSTS = "built_in_hosts_2"
const val KEY_DOMAIN_FRONTING = "domain_fronting"
const val KEY_BYPASS_VPN = "bypass_vpn"
const val KEY_LIST_THUMB_SIZE = "list_tile_size"
private const val KEY_LAST_DAWN_DAY = "last_dawn_day"
private const val DEFAULT_HIDE_HV_EVENTS = false
private const val KEY_SHOW_COMMENTS = "show_gallery_comments"
private const val DEFAULT_SHOW_COMMENTS = true
private val TAG = Settings::class.java.simpleName
private const val KEY_DISPLAY_NAME = "display_name"
private val DEFAULT_DISPLAY_NAME: String? = null
private const val DEFAULT_LIST_THUMB_SIZE = 40
private const val KEY_AVATAR = "avatar"
private const val KEY_REMOVE_IMAGE_FILES = "include_pic"
private const val DEFAULT_REMOVE_IMAGE_FILES = true
private const val KEY_NEED_SIGN_IN = "need_sign_in"
private const val DEFAULT_NEED_SIGN_IN = true
private const val KEY_SELECT_SITE = "select_site"
private const val DEFAULT_SELECT_SITE = true
private const val DEFAULT_THEME = THEME_SYSTEM
private const val DEFAULT_GALLERY_SITE = 0
private const val KEY_LAUNCH_PAGE = "launch_page"
private const val DEFAULT_LAUNCH_PAGE = 0
private const val DEFAULT_LIST_MODE = 0
private const val DEFAULT_DETAIL_SIZE = 0
private const val DEFAULT_THUMB_SIZE = 120
private const val DEFAULT_THUMB_RESOLUTION = 0
private const val DEFAULT_SHOW_JPN_TITLE = false
private const val KEY_SHOW_GALLERY_PAGES = "show_gallery_pages"
private const val DEFAULT_SHOW_GALLERY_PAGES = false
private const val DEFAULT_SHOW_TAG_TRANSLATIONS = false
private const val KEY_METERED_NETWORK_WARNING = "cellular_network_warning"
private const val DEFAULT_METERED_NETWORK_WARNING = false
private const val KEY_APP_LINK_VERIFY_TIP = "app_link_verify_tip"
private const val DEFAULT_APP_LINK_VERIFY_TIP = false
private const val DEFAULT_MEDIA_SCAN = false
private const val KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label"
private val DEFAULT_RECENT_DOWNLOAD_LABEL: String? = null
private const val KEY_HAS_DEFAULT_DOWNLOAD_LABEL = "has_default_download_label"
private const val DEFAULT_HAS_DOWNLOAD_LABEL = false
private const val KEY_DEFAULT_DOWNLOAD_LABEL = "default_download_label"
private val DEFAULT_DOWNLOAD_LABEL: String? = null
private const val KEY_MULTI_THREAD_DOWNLOAD = "download_thread"
private const val DEFAULT_MULTI_THREAD_DOWNLOAD = 3
private const val KEY_PRELOAD_IMAGE = "preload_image"
private const val DEFAULT_PRELOAD_IMAGE = 5
private const val KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image"
private const val DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false
/********************
* Favorites
*/
private const val KEY_FAV_CAT_0 = "fav_cat_0"
private const val KEY_FAV_CAT_1 = "fav_cat_1"
private const val KEY_FAV_CAT_2 = "fav_cat_2"
private const val KEY_FAV_CAT_3 = "fav_cat_3"
private const val KEY_FAV_CAT_4 = "fav_cat_4"
private const val KEY_FAV_CAT_5 = "fav_cat_5"
private const val KEY_FAV_CAT_6 = "fav_cat_6"
private const val KEY_FAV_CAT_7 = "fav_cat_7"
private const val KEY_FAV_CAT_8 = "fav_cat_8"
private const val KEY_FAV_CAT_9 = "fav_cat_9"
private const val DEFAULT_FAV_CAT_0 = "Favorites 0"
private const val DEFAULT_FAV_CAT_1 = "Favorites 1"
private const val DEFAULT_FAV_CAT_2 = "Favorites 2"
private const val DEFAULT_FAV_CAT_3 = "Favorites 3"
private const val DEFAULT_FAV_CAT_4 = "Favorites 4"
private const val DEFAULT_FAV_CAT_5 = "Favorites 5"
private const val DEFAULT_FAV_CAT_6 = "Favorites 6"
private const val DEFAULT_FAV_CAT_7 = "Favorites 7"
private const val DEFAULT_FAV_CAT_8 = "Favorites 8"
private const val DEFAULT_FAV_CAT_9 = "Favorites 9"
private const val KEY_FAV_COUNT_0 = "fav_count_0"
private const val KEY_FAV_COUNT_1 = "fav_count_1"
private const val KEY_FAV_COUNT_2 = "fav_count_2"
private const val KEY_FAV_COUNT_3 = "fav_count_3"
private const val KEY_FAV_COUNT_4 = "fav_count_4"
private const val KEY_FAV_COUNT_5 = "fav_count_5"
private const val KEY_FAV_COUNT_6 = "fav_count_6"
private const val KEY_FAV_COUNT_7 = "fav_count_7"
private const val KEY_FAV_COUNT_8 = "fav_count_8"
private const val KEY_FAV_COUNT_9 = "fav_count_9"
private const val KEY_FAV_LOCAL = "fav_local"
private const val KEY_FAV_CLOUD = "fav_cloud"
private const val DEFAULT_FAV_COUNT = 0
private const val KEY_RECENT_FAV_CAT = "recent_fav_cat"
private const val DEFAULT_RECENT_FAV_CAT = FavListUrlBuilder.FAV_CAT_ALL
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
private const val KEY_DEFAULT_FAV_SLOT = "default_favorite_2"
private const val DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT
private const val DEFAULT_SAVE_PARSE_ERROR_BODY = true
private const val KEY_SAVE_CRASH_LOG = "save_crash_log"
private const val DEFAULT_SAVE_CRASH_LOG = true
private const val DEFAULT_BUILT_IN_HOSTS = false
private const val DEFAULT_FRONTING = false
private const val DEFAULT_BYPASS_VPN = true
private const val KEY_PROXY_TYPE = "proxy_type"
private const val DEFAULT_PROXY_TYPE = EhProxySelector.TYPE_SYSTEM
private const val KEY_PROXY_IP = "proxy_ip"
private val DEFAULT_PROXY_IP: String? = null
private const val KEY_PROXY_PORT = "proxy_port"
private const val DEFAULT_PROXY_PORT = -1
private const val KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code"
private const val DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0
private const val KEY_DOWNLOAD_DELAY = "download_delay"
private const val DEFAULT_DOWNLOAD_DELAY = 0
private const val DEFAULT_REQUEST_NEWS = false
private const val KEY_ARCHIVE_PASSWDS = "archive_passwds"
private const val KEY_QS_SAVE_PROGRESS = "qs_save_progress"
private const val DEFAULT_QS_SAVE_PROGRESS = true
private const val KEY_PRELOAD_THUMB_AGGRESIVELY = "preload_thumb_aggressively"
private const val DEFAULT_PRELOAD_THUMB_AGGRESIVELY = false
var LIST_THUMB_SIZE_INITED = false
private lateinit var sSettingsPre: SharedPreferences
private var LIST_THUMB_SIZE = 40
fun initialize() {
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(application)
fixDefaultValue()
}
private fun fixDefaultValue() {
if ("CN" == Locale.getDefault().country) {
// Enable domain fronting if the country is CN
if (!sSettingsPre.contains(KEY_BUILT_IN_HOSTS)) {
putBuiltInHosts(true)
}
if (!sSettingsPre.contains(KEY_DOMAIN_FRONTING)) {
putDF(true)
}
// Enable show tag translations if the country is CN
if (!sSettingsPre.contains(KEY_SHOW_TAG_TRANSLATIONS)) {
putShowTagTranslations(true)
}
}
}
private fun getBoolean(key: String, defValue: Boolean): Boolean {
return try {
sSettingsPre.getBoolean(key, defValue)
} catch (e: ClassCastException) {
Log.d(TAG, "Get ClassCastException when get $key value", e)
defValue
}
}
private fun putBoolean(key: String, value: Boolean) {
sSettingsPre.edit().putBoolean(key, value).apply()
}
@JvmStatic
fun getInt(key: String, defValue: Int): Int {
return try {
sSettingsPre.getInt(key, defValue)
} catch (e: ClassCastException) {
Log.d(TAG, "Get ClassCastException when get $key value", e)
defValue
}
}
@JvmStatic
fun putInt(key: String, value: Int) {
sSettingsPre.edit().putInt(key, value).apply()
}
private fun getString(key: String, defValue: String?): String? {
return try {
sSettingsPre.getString(key, defValue)
} catch (e: ClassCastException) {
Log.d(TAG, "Get ClassCastException when get $key value", e)
defValue
}
}
private fun putString(key: String, value: String?) {
sSettingsPre.edit().putString(key, value).apply()
}
private fun getStringSet(key: String): MutableSet<String>? {
return sSettingsPre.getStringSet(key, null)
}
private fun putStringToStringSet(key: String, value: String) {
var set = getStringSet(key)
if (set == null) {
set =
mutableSetOf(value)
} else if (set.contains(value)) return else set.add(value)
sSettingsPre.edit().putStringSet(key, set).apply()
}
private fun getIntFromStr(key: String, defValue: Int): Int {
return try {
NumberUtils.parseIntSafely(
sSettingsPre.getString(key, defValue.toString()),
defValue,
)
} catch (e: ClassCastException) {
Log.d(TAG, "Get ClassCastException when get $key value", e)
defValue
}
}
private fun putIntToStr(key: String, value: Int) {
sSettingsPre.edit().putString(key, value.toString()).apply()
}
var lastDawnDay: Long
get() = sSettingsPre.getLong(KEY_LAST_DAWN_DAY, 0)
set(value) = sSettingsPre.edit().putLong(KEY_LAST_DAWN_DAY, value).apply()
val displayName: String?
get() = getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME)
fun putDisplayName(value: String?) {
putString(KEY_DISPLAY_NAME, value)
}
fun putAvatar(value: String?) {
putString(KEY_AVATAR, value)
}
val removeImageFiles: Boolean
get() = getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES)
fun putRemoveImageFiles(value: Boolean) {
putBoolean(KEY_REMOVE_IMAGE_FILES, value)
}
val needSignIn: Boolean
get() = getBoolean(KEY_NEED_SIGN_IN, DEFAULT_NEED_SIGN_IN)
fun putNeedSignIn(value: Boolean) {
putBoolean(KEY_NEED_SIGN_IN, value)
}
val selectSite: Boolean
get() = getBoolean(KEY_SELECT_SITE, DEFAULT_SELECT_SITE)
fun putSelectSite(value: Boolean) {
putBoolean(KEY_SELECT_SITE, value)
}
val theme: Int
get() = getIntFromStr(KEY_THEME, DEFAULT_THEME)
val blackDarkTheme
get() = getBoolean(KEY_BLACK_DARK_THEME, DEFAULT_BLACK_DARK_THEME)
val gallerySite: Int
get() = getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE)
fun putGallerySite(value: Int) {
putIntToStr(KEY_GALLERY_SITE, value)
}
val launchPageGalleryListSceneAction: String
get() {
return when (val value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE)) {
0 -> GalleryListScene.ACTION_HOMEPAGE
1 -> GalleryListScene.ACTION_SUBSCRIPTION
2 -> GalleryListScene.ACTION_WHATS_HOT
3 -> GalleryListScene.ACTION_TOP_LIST
else -> throw IllegalStateException("Unexpected value: $value")
}
}
val listMode: Int
get() = getIntFromStr(KEY_LIST_MODE, DEFAULT_LIST_MODE)
private val detailSize: Int
get() = getIntFromStr(KEY_DETAIL_SIZE, DEFAULT_DETAIL_SIZE)
@get:DimenRes
val detailSizeResId: Int
get() = when (detailSize) {
0 -> R.dimen.gallery_list_column_width_long
1 -> R.dimen.gallery_list_column_width_short
else -> throw IllegalStateException("Unexpected value: $detailSize")
}
val thumbSize: Int
get() = dp2pix(application, thumbSizeDp.toFloat())
val thumbSizeDp: Int
get() = getInt(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE)
val thumbResolution: Int
get() = getIntFromStr(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION)
val showComments: Boolean
get() = getBoolean(KEY_SHOW_COMMENTS, DEFAULT_SHOW_COMMENTS)
val requestNews: Boolean
get() = getBoolean(KEY_REQUEST_NEWS, DEFAULT_REQUEST_NEWS)
val hideHvEvents: Boolean
get() = getBoolean(KEY_HIDE_HV_EVENTS, DEFAULT_HIDE_HV_EVENTS)
val showJpnTitle: Boolean
get() = getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE)
val showGalleryPages: Boolean
get() = getBoolean(KEY_SHOW_GALLERY_PAGES, DEFAULT_SHOW_GALLERY_PAGES)
val showTagTranslations: Boolean
get() = getBoolean(KEY_SHOW_TAG_TRANSLATIONS, DEFAULT_SHOW_TAG_TRANSLATIONS)
private fun putShowTagTranslations(value: Boolean) {
putBoolean(KEY_SHOW_TAG_TRANSLATIONS, value)
}
val meteredNetworkWarning: Boolean
get() = getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING)
val appLinkVerifyTip: Boolean
get() = getBoolean(KEY_APP_LINK_VERIFY_TIP, DEFAULT_APP_LINK_VERIFY_TIP)
fun putAppLinkVerifyTip(value: Boolean) {
putBoolean(KEY_APP_LINK_VERIFY_TIP, value)
}
val enabledSecurity: Boolean
get() = getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY)
val downloadLocation: UniFile?
get() {
val dir: UniFile?
val builder = Uri.Builder()
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null))
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null))
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null))
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null))
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null))
dir = UniFile.fromUri(application, builder.build())
return dir ?: UniFile.fromFile(AppConfig.getDefaultDownloadDir())
}
fun putDownloadLocation(location: UniFile) {
val uri = location.uri
putString(KEY_DOWNLOAD_SAVE_SCHEME, uri.scheme)
putString(KEY_DOWNLOAD_SAVE_AUTHORITY, uri.encodedAuthority)
putString(KEY_DOWNLOAD_SAVE_PATH, uri.encodedPath)
putString(KEY_DOWNLOAD_SAVE_QUERY, uri.encodedQuery)
putString(KEY_DOWNLOAD_SAVE_FRAGMENT, uri.encodedFragment)
}
val mediaScan: Boolean
get() = getBoolean(KEY_MEDIA_SCAN, DEFAULT_MEDIA_SCAN)
val recentDownloadLabel: String?
get() = getString(KEY_RECENT_DOWNLOAD_LABEL, DEFAULT_RECENT_DOWNLOAD_LABEL)
fun putRecentDownloadLabel(value: String?) {
putString(KEY_RECENT_DOWNLOAD_LABEL, value)
}
val hasDefaultDownloadLabel: Boolean
get() = getBoolean(KEY_HAS_DEFAULT_DOWNLOAD_LABEL, DEFAULT_HAS_DOWNLOAD_LABEL)
fun putHasDefaultDownloadLabel(hasDefaultDownloadLabel: Boolean) {
putBoolean(KEY_HAS_DEFAULT_DOWNLOAD_LABEL, hasDefaultDownloadLabel)
}
val defaultDownloadLabel: String?
get() = getString(KEY_DEFAULT_DOWNLOAD_LABEL, DEFAULT_DOWNLOAD_LABEL)
fun putDefaultDownloadLabel(value: String?) {
putString(KEY_DEFAULT_DOWNLOAD_LABEL, value)
}
val multiThreadDownload: Int
get() = getIntFromStr(KEY_MULTI_THREAD_DOWNLOAD, DEFAULT_MULTI_THREAD_DOWNLOAD)
val downloadDelay: Int
get() = getIntFromStr(KEY_DOWNLOAD_DELAY, DEFAULT_DOWNLOAD_DELAY)
val preloadImage: Int
get() = getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE)
val downloadOriginImage: Boolean
get() = getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE)
val favCat: Array<String>
get() = arrayOf(
sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0)!!,
sSettingsPre.getString(KEY_FAV_CAT_1, DEFAULT_FAV_CAT_1)!!,
sSettingsPre.getString(KEY_FAV_CAT_2, DEFAULT_FAV_CAT_2)!!,
sSettingsPre.getString(KEY_FAV_CAT_3, DEFAULT_FAV_CAT_3)!!,
sSettingsPre.getString(KEY_FAV_CAT_4, DEFAULT_FAV_CAT_4)!!,
sSettingsPre.getString(KEY_FAV_CAT_5, DEFAULT_FAV_CAT_5)!!,
sSettingsPre.getString(KEY_FAV_CAT_6, DEFAULT_FAV_CAT_6)!!,
sSettingsPre.getString(KEY_FAV_CAT_7, DEFAULT_FAV_CAT_7)!!,
sSettingsPre.getString(KEY_FAV_CAT_8, DEFAULT_FAV_CAT_8)!!,
sSettingsPre.getString(KEY_FAV_CAT_9, DEFAULT_FAV_CAT_9)!!,
)
fun putFavCat(value: Array<String?>) {
check(value.size == 10)
sSettingsPre.edit()
.putString(KEY_FAV_CAT_0, value[0])
.putString(KEY_FAV_CAT_1, value[1])
.putString(KEY_FAV_CAT_2, value[2])
.putString(KEY_FAV_CAT_3, value[3])
.putString(KEY_FAV_CAT_4, value[4])
.putString(KEY_FAV_CAT_5, value[5])
.putString(KEY_FAV_CAT_6, value[6])
.putString(KEY_FAV_CAT_7, value[7])
.putString(KEY_FAV_CAT_8, value[8])
.putString(KEY_FAV_CAT_9, value[9])
.apply()
}
val favCount: IntArray
get() = intArrayOf(
sSettingsPre.getInt(KEY_FAV_COUNT_0, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_1, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_2, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_3, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_4, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_5, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_6, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_7, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_8, DEFAULT_FAV_COUNT),
sSettingsPre.getInt(KEY_FAV_COUNT_9, DEFAULT_FAV_COUNT),
)
fun putFavCount(count: IntArray) {
check(count.size == 10)
sSettingsPre.edit()
.putInt(KEY_FAV_COUNT_0, count[0])
.putInt(KEY_FAV_COUNT_1, count[1])
.putInt(KEY_FAV_COUNT_2, count[2])
.putInt(KEY_FAV_COUNT_3, count[3])
.putInt(KEY_FAV_COUNT_4, count[4])
.putInt(KEY_FAV_COUNT_5, count[5])
.putInt(KEY_FAV_COUNT_6, count[6])
.putInt(KEY_FAV_COUNT_7, count[7])
.putInt(KEY_FAV_COUNT_8, count[8])
.putInt(KEY_FAV_COUNT_9, count[9])
.apply()
}
val favLocalCount: Int
get() = sSettingsPre.getInt(KEY_FAV_LOCAL, DEFAULT_FAV_COUNT)
fun putFavLocalCount(count: Int) {
sSettingsPre.edit().putInt(KEY_FAV_LOCAL, count).apply()
}
val favCloudCount: Int
get() = sSettingsPre.getInt(KEY_FAV_CLOUD, DEFAULT_FAV_COUNT)
fun putFavCloudCount(count: Int) {
sSettingsPre.edit().putInt(KEY_FAV_CLOUD, count).apply()
}
val recentFavCat: Int
get() = getInt(KEY_RECENT_FAV_CAT, DEFAULT_RECENT_FAV_CAT)
fun putRecentFavCat(value: Int) {
putInt(KEY_RECENT_FAV_CAT, value)
}
val defaultFavSlot: Int
get() = getInt(KEY_DEFAULT_FAV_SLOT, DEFAULT_DEFAULT_FAV_SLOT)
fun putDefaultFavSlot(value: Int) {
putInt(KEY_DEFAULT_FAV_SLOT, value)
}
val qSSaveProgress: Boolean
get() = getBoolean(KEY_QS_SAVE_PROGRESS, DEFAULT_QS_SAVE_PROGRESS)
fun putQSSaveProgress(value: Boolean) {
putBoolean(KEY_QS_SAVE_PROGRESS, value)
}
val saveParseErrorBody: Boolean
get() = getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY)
val saveCrashLog: Boolean
get() = getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG)
val security: Boolean
get() = getBoolean(KEY_SECURITY, false)
fun putSecurity(value: Boolean) {
putBoolean(KEY_SECURITY, value)
}
val securityDelay: Int
get() = getInt(KEY_SECURITY_DELAY, 0)
val readCacheSize: Int
get() = getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE)
val builtInHosts: Boolean
get() = getBoolean(KEY_BUILT_IN_HOSTS, DEFAULT_BUILT_IN_HOSTS)
private fun putBuiltInHosts(value: Boolean) {
putBoolean(KEY_BUILT_IN_HOSTS, value)
}
val dF: Boolean
get() = getBoolean(KEY_DOMAIN_FRONTING, DEFAULT_FRONTING)
private fun putDF(value: Boolean) {
putBoolean(KEY_DOMAIN_FRONTING, value)
}
val bypassVpn: Boolean
get() = getBoolean(KEY_BYPASS_VPN, DEFAULT_BYPASS_VPN)
@JvmStatic
val proxyType: Int
get() = getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE)
fun putProxyType(value: Int) {
putInt(KEY_PROXY_TYPE, value)
}
@JvmStatic
val proxyIp: String?
get() = getString(KEY_PROXY_IP, DEFAULT_PROXY_IP)
fun putProxyIp(value: String?) {
putString(KEY_PROXY_IP, value)
}
@JvmStatic
val proxyPort: Int
get() = getInt(KEY_PROXY_PORT, DEFAULT_PROXY_PORT)
fun putProxyPort(value: Int) {
putInt(KEY_PROXY_PORT, value)
}
val clipboardTextHashCode: Int
get() = getInt(KEY_CLIPBOARD_TEXT_HASH_CODE, DEFAULT_CLIPBOARD_TEXT_HASH_CODE)
fun putClipboardTextHashCode(value: Int) {
putInt(KEY_CLIPBOARD_TEXT_HASH_CODE, value)
}
val archivePasswds: Set<String>?
get() = getStringSet(KEY_ARCHIVE_PASSWDS)
fun putPasswdToArchivePasswds(value: String) {
putStringToStringSet(KEY_ARCHIVE_PASSWDS, value)
}
val listThumbSize: Int
get() {
if (LIST_THUMB_SIZE_INITED) {
return LIST_THUMB_SIZE
}
val size = 3 * getInt(KEY_LIST_THUMB_SIZE, DEFAULT_LIST_THUMB_SIZE)
LIST_THUMB_SIZE = size
LIST_THUMB_SIZE_INITED = true
return size
}
val notificationRequired: Boolean
get() = getBoolean(KEY_NOTIFICATION_REQUIRED, false)
fun putNotificationRequired() {
putBoolean(KEY_NOTIFICATION_REQUIRED, true)
}
val preloadThumbAggressively: Boolean
get() = getBoolean(KEY_PRELOAD_THUMB_AGGRESIVELY, DEFAULT_PRELOAD_THUMB_AGGRESIVELY)
}
@@ -1,269 +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
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.network.CookieDatabase
import com.hippo.ehviewer.network.CookieSet
import eu.kanade.tachiyomi.util.lang.launchIO
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.Collections
import java.util.regex.Pattern
object EhCookieStore : CookieJar {
private val db: CookieDatabase = CookieDatabase(EhApplication.application)
private val map: MutableMap<String, CookieSet> = db.allCookies
fun signOut() {
clear()
}
fun hasSignedIn(): Boolean {
val url = EhUrl.HOST_E.toHttpUrl()
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"
const val KEY_SETTINGS_PROFILE = "sp"
const val KEY_STAR = "star"
private const val KEY_CONTENT_WARNING = "nw"
private const val CONTENT_WARNING_NOT_SHOW = "1"
private val sTipsCookie: Cookie = Cookie.Builder()
.name(KEY_CONTENT_WARNING)
.value(CONTENT_WARNING_NOT_SHOW)
.domain(EhUrl.DOMAIN_E)
.path("/")
.expiresAt(Long.MAX_VALUE)
.build()
fun newCookie(
cookie: Cookie,
newDomain: String,
forcePersistent: Boolean = false,
forceLongLive: Boolean = false,
forceNotHostOnly: Boolean = false,
): Cookie {
val builder = Cookie.Builder()
builder.name(cookie.name)
builder.value(cookie.value)
if (forceLongLive) {
builder.expiresAt(Long.MAX_VALUE)
} else if (cookie.persistent) {
builder.expiresAt(cookie.expiresAt)
} else if (forcePersistent) {
builder.expiresAt(Long.MAX_VALUE)
}
if (cookie.hostOnly && !forceNotHostOnly) {
builder.hostOnlyDomain(newDomain)
} else {
builder.domain(newDomain)
}
builder.path(cookie.path)
if (cookie.secure) {
builder.secure()
}
if (cookie.httpOnly) {
builder.httpOnly()
}
return builder.build()
}
fun copyCookie(domain: String, newDomain: String, name: String, path: String = "/") {
val cookie = map[domain]?.get(name, domain, path)
cookie?.let { addCookie(newCookie(it, newDomain)) }
}
fun deleteCookie(url: HttpUrl, name: String) {
val deletedCookie = Cookie.Builder()
.name(name)
.value("deleted")
.domain(url.host)
.expiresAt(0)
.build()
addCookie(deletedCookie)
}
@Synchronized
fun addCookie(cookie: Cookie) {
// For cookie database
var toAdd: Cookie? = null
var toUpdate: Cookie? = null
var toRemove: Cookie? = null
var set = map[cookie.domain]
if (set == null) {
set = CookieSet()
map[cookie.domain] = set
}
if (cookie.expiresAt <= System.currentTimeMillis()) {
toRemove = set.remove(cookie)
// If the cookie is not persistent, it's not in database
if (toRemove != null && !toRemove.persistent) {
toRemove = null
}
} else {
toAdd = cookie
toUpdate = set.add(cookie)
// If the cookie is not persistent, it's not in database
if (!toAdd.persistent) toAdd = null
if (toUpdate != null && !toUpdate.persistent) toUpdate = null
// Remove the cookie if it updates to null
if (toAdd == null && toUpdate != null) {
toRemove = toUpdate
toUpdate = null
}
}
if (toRemove != null) {
db.remove(toRemove)
}
if (toAdd != null) {
if (toUpdate != null) {
db.update(toUpdate, toAdd)
} else {
db.add(toAdd)
}
}
}
fun getCookieHeader(url: HttpUrl): String {
val cookies = getCookies(url)
val cookieHeader = StringBuilder()
var i = 0
val size = cookies.size
while (i < size) {
if (i > 0) {
cookieHeader.append("; ")
}
val cookie = cookies[i]
cookieHeader.append(cookie.name).append('=').append(cookie.value)
i++
}
return cookieHeader.toString()
}
@Synchronized
fun getCookies(url: HttpUrl): List<Cookie> {
val accepted: MutableList<Cookie> = ArrayList()
val expired: MutableList<Cookie> = ArrayList()
for ((domain, cookieSet) in map) {
if (domainMatch(url, domain)) {
cookieSet[url, accepted, expired]
}
}
for (cookie in expired) {
if (cookie.persistent) {
db.remove(cookie)
}
}
// RFC 6265 Section-5.4 step 2, sort the cookie-list
// Cookies with longer paths are listed before cookies with shorter paths.
// Ignore creation-time, we don't store them.
accepted.sortWith { o1: Cookie, o2: Cookie -> o2.path.length - o1.path.length }
return accepted
}
fun contains(url: HttpUrl, name: String?): Boolean {
for (cookie in getCookies(url)) {
if (cookie.name == name) {
return true
}
}
return false
}
/**
* Remove all cookies in this `CookieRepository`.
*/
@Synchronized
fun clear() {
map.clear()
launchIO {
db.clear()
}
}
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
for (cookie in cookies) {
// See https://github.com/Ehviewer-Overhauled/Ehviewer/issues/873
if (cookie.name != "__utmp") {
addCookie(cookie)
}
}
}
/**
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
* of Android's private InetAddress#isNumeric API.
*
*
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
* including dots after the first colon. It matches IPv4 addresses as strings containing only
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
* verification).
*/
private val VERIFY_AS_IP_ADDRESS =
Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)")
/**
* Returns true if `host` is not a host name and might be an IP address.
*/
private fun verifyAsIpAddress(host: String): Boolean {
return VERIFY_AS_IP_ADDRESS.matcher(host).matches()
}
// okhttp3.Cookie.domainMatch(HttpUrl, String)
@JvmStatic
fun domainMatch(url: HttpUrl, domain: String?): Boolean {
val urlHost = url.host
return if (urlHost == domain) {
true // As in 'example.com' matching 'example.com'.
} else {
urlHost.endsWith(domain!!) &&
urlHost[urlHost.length - domain.length - 1] == '.' && !verifyAsIpAddress(
urlHost,
)
}
// As in 'example.com' matching 'www.example.com'.
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
val cookies = getCookies(url)
val checkTips = domainMatch(url, EhUrl.DOMAIN_E)
return if (checkTips) {
val result: MutableList<Cookie> = ArrayList(cookies.size + 1)
// Add all but skip some
for (cookie in cookies) {
val name = cookie.name
if (KEY_CONTENT_WARNING == name) {
continue
}
result.add(cookie)
}
// Add some
result.add(sTipsCookie)
Collections.unmodifiableList(result)
} else {
cookies
}
}
}
@@ -1,137 +0,0 @@
/*
* Copyright 2018 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
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.Hosts
import com.hippo.ehviewer.Settings
import okhttp3.Dns
import java.net.InetAddress
import java.net.UnknownHostException
object EhDns : Dns {
private val hosts = EhApplication.hosts
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
init {
// Pair(ip: String!, blockedByCCP: Boolean!)
val ehgtHosts = arrayOf(
Pair("37.48.89.44", false),
Pair("81.171.10.48", false),
Pair("178.162.139.24", false),
Pair("178.162.140.212", false),
Pair("2001:1af8:4700:a062:8::47de", false),
Pair("2001:1af8:4700:a062:9::47de", true),
Pair("2001:1af8:4700:a0c9:4::47de", false),
Pair("2001:1af8:4700:a0c9:3::47de", true),
)
put(
builtInHosts,
"e-hentai.org",
Pair("104.20.134.21", false),
Pair("104.20.135.21", false),
Pair("172.67.0.127", false),
)
put(
builtInHosts,
"exhentai.org",
Pair("178.175.128.252", false),
Pair("178.175.129.252", false),
Pair("178.175.129.254", false),
Pair("178.175.128.254", false),
Pair("178.175.132.20", false),
Pair("178.175.132.22", false),
)
put(
builtInHosts,
"s.exhentai.org",
Pair("178.175.129.254", false),
Pair("178.175.128.254", false),
Pair("178.175.132.22", false),
)
put(
builtInHosts,
"repo.e-hentai.org",
Pair("94.100.28.57", true),
Pair("94.100.29.73", true),
)
put(
builtInHosts,
"forums.e-hentai.org",
Pair("94.100.18.243", false),
)
put(
builtInHosts,
"ehgt.org",
*ehgtHosts,
)
put(
builtInHosts,
"gt0.ehgt.org",
*ehgtHosts,
)
put(
builtInHosts,
"gt1.ehgt.org",
*ehgtHosts,
)
put(
builtInHosts,
"gt2.ehgt.org",
*ehgtHosts,
)
put(
builtInHosts,
"gt3.ehgt.org",
*ehgtHosts,
)
put(
builtInHosts,
"ul.ehgt.org",
Pair("94.100.24.82", true),
Pair("94.100.24.72", true),
)
put(
builtInHosts,
"raw.githubusercontent.com",
Pair("151.101.0.133", false),
Pair("151.101.64.133", false),
Pair("151.101.128.133", false),
Pair("151.101.192.133", false),
)
}
private fun put(
map: MutableMap<String, List<InetAddress>>,
host: String,
vararg ips: Pair<String, Boolean>,
) {
map[host] = ips.mapNotNull { pair ->
Hosts.toInetAddress(host, pair.first).takeUnless { Settings.dF && pair.second }
}
}
@Throws(UnknownHostException::class)
override fun lookup(hostname: String): List<InetAddress> {
return hosts[hostname] ?: builtInHosts[hostname].takeIf { Settings.builtInHosts }
?: Dns.SYSTEM.lookup(hostname)
}
fun isInHosts(hostname: String): Boolean {
return hosts.contains(hostname) || (builtInHosts.contains(hostname) && Settings.builtInHosts)
}
}
@@ -1,153 +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
import android.util.Log
import arrow.core.memoize
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.dao.Filter
private val regex = { p: Filter -> Regex(p.text!!) }.memoize()
object EhFilter {
val titleFilterList = mutableListOf<Filter>()
val uploaderFilterList = mutableListOf<Filter>()
val tagFilterList = mutableListOf<Filter>()
val tagNamespaceFilterList = mutableListOf<Filter>()
val commenterFilterList = mutableListOf<Filter>()
val commentFilterList = mutableListOf<Filter>()
const val MODE_TITLE = 0
const val MODE_UPLOADER = 1
const val MODE_TAG = 2
const val MODE_TAG_NAMESPACE = 3
const val MODE_COMMENTER = 4
const val MODE_COMMENT = 5
private const val TAG = "EhFilter"
init { EhDB.allFilter.forEach(::memorizeFilter) }
private fun memorizeFilter(filter: Filter) {
when (filter.mode) {
MODE_TITLE -> {
filter.text = filter.text!!.lowercase()
titleFilterList.add(filter)
}
MODE_TAG -> {
filter.text = filter.text!!.lowercase()
tagFilterList.add(filter)
}
MODE_TAG_NAMESPACE -> {
filter.text = filter.text!!.lowercase()
tagNamespaceFilterList.add(filter)
}
MODE_UPLOADER -> uploaderFilterList.add(filter)
MODE_COMMENTER -> commenterFilterList.add(filter)
MODE_COMMENT -> commentFilterList.add(filter)
else -> Log.d(TAG, "Unknown mode: " + filter.mode)
}
}
@Synchronized
fun addFilter(filter: Filter): Boolean {
filter.enable = true
if (!EhDB.addFilter(filter)) return false
memorizeFilter(filter)
return true
}
@Synchronized
fun triggerFilter(filter: Filter) {
EhDB.triggerFilter(filter)
}
@Synchronized
fun deleteFilter(filter: Filter) {
EhDB.deleteFilter(filter)
when (filter.mode) {
MODE_TITLE -> titleFilterList.remove(filter)
MODE_TAG -> tagFilterList.remove(filter)
MODE_TAG_NAMESPACE -> tagNamespaceFilterList.remove(filter)
MODE_UPLOADER -> uploaderFilterList.remove(filter)
MODE_COMMENTER -> commenterFilterList.remove(filter)
MODE_COMMENT -> commentFilterList.remove(filter)
else -> Log.d(TAG, "Unknown mode: " + filter.mode)
}
}
@Synchronized
fun needTags(): Boolean {
return 0 != tagFilterList.size || 0 != tagNamespaceFilterList.size
}
@Synchronized
fun filterTitle(info: GalleryInfo): Boolean {
val title = info.title ?: return false
return titleFilterList.any { it.enable!! && it.text!! in title.lowercase() }
}
@Synchronized
fun filterUploader(info: GalleryInfo): Boolean {
val uploader = info.uploader ?: return false
return uploaderFilterList.any { it.enable!! && it.text == uploader }
}
private fun spiltTag(tag: String) = tag.run {
val index = indexOf(':')
if (index < 0) null to this else substring(0, index) to substring(index + 1)
}
private fun matchTag(tag: String, filter: String): Boolean {
val (tagNamespace, tagName) = spiltTag(tag)
val (filterNamespace, filterName) = spiltTag(filter)
return if (null != tagNamespace && null != filterNamespace && tagNamespace != filterNamespace) {
false
} else {
tagName == filterName
}
}
@Synchronized
fun filterTag(info: GalleryInfo): Boolean {
val tags = info.simpleTags ?: return false
return tags.any { tag -> tagFilterList.any { it.enable!! && matchTag(tag, it.text!!) } }
}
private fun matchTagNamespace(tag: String, filter: String): Boolean {
val (nameSpace, _) = spiltTag(tag)
return nameSpace == filter
}
@Synchronized
fun filterTagNamespace(info: GalleryInfo): Boolean {
val tags = info.simpleTags ?: return false
return tags.any { tag -> tagNamespaceFilterList.any { it.enable!! && matchTagNamespace(tag, it.text!!) } }
}
@Synchronized
fun filterCommenter(commenter: String): Boolean {
return commenterFilterList.any { it.enable!! && it.text == commenter }
}
@Synchronized
fun filterComment(comment: String): Boolean {
return commentFilterList.any { it.enable!! && regex(it).containsMatchIn(comment) }
}
}
@@ -1,69 +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
import com.hippo.ehviewer.EhApplication.Companion.nonCacheOkHttpClient
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
import okhttp3.FormBody
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.executeAsync
import org.json.JSONArray
import org.json.JSONObject
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
inline fun ehRequest(url: String, referer: String? = null, origin: String? = null, builder: Request.Builder.() -> Unit = {}) = Request.Builder().url(url).apply {
addHeader("User-Agent", CHROME_USER_AGENT)
addHeader("Accept", CHROME_ACCEPT)
addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE)
referer?.let { addHeader("Referer", it) }
origin?.let { addHeader("Origin", it) }
}.apply(builder).build()
inline fun Request.Builder.formBody(builder: FormBody.Builder.() -> Unit) = post(FormBody.Builder().apply(builder).build())
inline fun Request.Builder.multipartBody(builder: MultipartBody.Builder.() -> Unit) = post(MultipartBody.Builder().apply(builder).build())
val MEDIA_TYPE_JSON: MediaType = "application/json; charset=utf-8".toMediaType()
inline fun JSONObject.array(name: String, builder: JSONArray.() -> Unit): JSONObject = put(name, JSONArray().apply(builder))
fun jsonArrayOf(vararg element: Any?) = JSONArray().apply { element.forEach { put(it) } } // Should ensure it is inlined
inline fun Request.Builder.jsonBody(builder: JSONObject.() -> Unit) = post(JSONObject().apply(builder).toString().toRequestBody(MEDIA_TYPE_JSON))
suspend inline fun <R> Request.execute(block: Response.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return okHttpClient.newCall(this).executeAsync().use(block)
}
suspend inline fun <R> Request.executeNonCache(block: Response.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return nonCacheOkHttpClient.newCall(this).executeAsync().use(block)
}
const val CHROME_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 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"
@@ -1,74 +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
import android.util.Log
import java.net.InetAddress
import java.net.Socket
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
private const val EXCEPTIONAL_DOMAIN = "hath.network"
object EhSSLSocketFactory : SSLSocketFactory() {
private val sslSocketFactory = getDefault() as SSLSocketFactory
override fun getDefaultCipherSuites(): Array<String> {
return sslSocketFactory.defaultCipherSuites
}
override fun getSupportedCipherSuites(): Array<String> {
return sslSocketFactory.supportedCipherSuites
}
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
val address = s.inetAddress.hostAddress.takeIf { EhDns.isInHosts(host) || EXCEPTIONAL_DOMAIN in host }
val socket = sslSocketFactory.createSocket(s, address ?: host, port, autoClose) as SSLSocket
val sslSession = socket.session
Log.d(
"EhSSLSocketFactory",
"Host: " + host + " Address: " + address + " Protocol:" + sslSession.protocol + " CipherSuite:" + sslSession.cipherSuite,
)
return socket
}
override fun createSocket(host: String, port: Int): Socket {
return sslSocketFactory.createSocket(host, port)
}
override fun createSocket(
host: String,
port: Int,
localHost: InetAddress,
localPort: Int,
): Socket {
return sslSocketFactory.createSocket(host, port, localHost, localPort)
}
override fun createSocket(host: InetAddress, port: Int): Socket {
return sslSocketFactory.createSocket(host, port)
}
override fun createSocket(
address: InetAddress,
port: Int,
localAddress: InetAddress,
localPort: Int,
): Socket {
return sslSocketFactory.createSocket(address, port, localAddress, localPort)
}
}
@@ -1,25 +0,0 @@
package com.hippo.ehviewer.client.data
interface AbstractGalleryInfo {
var gid: Long
var token: String?
var title: String?
var titleJpn: String?
var thumbKey: String?
var category: Int
var posted: String?
var uploader: String?
var disowned: Boolean
var rating: Float
var rated: Boolean
var simpleTags: Array<String>?
var pages: Int
var thumbWidth: Int
var thumbHeight: Int
var spanSize: Int
var spanIndex: Int
var spanGroupIndex: Int
var simpleLanguage: String?
var favoriteSlot: Int
var favoriteName: String?
}
@@ -1,62 +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.GetText.getString
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.util.ExceptionUtils
import com.hippo.ehviewer.util.JsoupUtils
import org.jsoup.Jsoup
object FavoritesParser {
fun parse(body: String): Result {
if (body.contains("This page requires you to log on.</p>")) {
throw EhException(getString(R.string.need_sign_in))
}
val catArray = arrayOfNulls<String>(10)
val countArray = IntArray(10)
val d = Jsoup.parse(body)
runCatching {
val ido = JsoupUtils.getElementByClass(d, "ido")
val fps = ido!!.getElementsByClass("fp")
// Last one is "fp fps"
check(fps.size == 11)
for (i in 0..9) {
val fp = fps[i]
countArray[i] = ParserUtils.parseInt(fp.child(0).text(), 0)
catArray[i] = ParserUtils.trim(fp.child(2).text())
}
}.onFailure {
ExceptionUtils.throwIfFatal(it)
it.printStackTrace()
throw ParseException("Parse favorites error", body)
}
val result = GalleryListParser.parse(d, body)
return Result(catArray, countArray, result)
}
class Result(
val catArray: Array<String?>,
val countArray: IntArray,
galleryListResult: GalleryListParser.Result,
) {
val prev = galleryListResult.prev
val next = galleryListResult.next
val galleryInfoList = galleryListResult.galleryInfoList
}
}
@@ -1,48 +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.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.yorozuya.NumberUtils
import org.json.JSONObject
object GalleryApiParser {
fun parse(body: String, galleryInfoList: List<GalleryInfo>) {
val jo = JSONObject(body)
val ja = jo.getJSONArray("gmetadata")
for (i in 0 until ja.length()) {
val g = ja.getJSONObject(i)
val gid = g.getLong("gid")
val gi = galleryInfoList.find { it.gid == gid } ?: continue
gi.title = ParserUtils.trim(g.getString("title"))
gi.titleJpn = ParserUtils.trim(g.getString("title_jpn"))
gi.category = getCategory(g.getString("category"))
gi.thumbKey = getThumbKey(handleThumbUrlResolution(g.getString("thumb"))!!)
gi.uploader = g.getString("uploader")
gi.posted =
ParserUtils.formatDate(ParserUtils.parseLong(g.getString("posted"), 0) * 1000)
gi.rating = NumberUtils.parseFloatSafely(g.getString("rating"), 0.0f)
// tags
val tagJa = g.getJSONArray("tags")
gi.simpleTags = (0 until tagJa.length()).map { tagJa.getString(it) }.toTypedArray()
gi.pages = NumberUtils.parseIntSafely(g.getString("filecount"), 0)
gi.generateSLang()
}
}
}
@@ -1,343 +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 android.util.Log
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.client.getThumbKey
import com.hippo.ehviewer.util.ExceptionUtils
import com.hippo.ehviewer.util.JsoupUtils
import com.hippo.ehviewer.yorozuya.NumberUtils
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.regex.Pattern
object GalleryListParser {
private val TAG = GalleryListParser::class.java.simpleName
private const val NO_UNFILTERED_TEXT =
"No unfiltered results in this page range. You either requested an invalid page or used too aggressive filters."
private val PATTERN_RATING = Pattern.compile("\\d+px")
private val PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px")
private val PATTERN_FAVORITE_SLOT =
Pattern.compile("background-color:rgba\\((\\d+),(\\d+),(\\d+),")
private val PATTERN_PAGES = Pattern.compile("(\\d+) page")
private val PATTERN_NEXT_PAGE = Pattern.compile("p=(\\d+)")
private val PATTERN_PREV = Pattern.compile("prev=(\\d+(-\\d+)?)")
private val PATTERN_NEXT = Pattern.compile("next=(\\d+(-\\d+)?)")
private val FAVORITE_SLOT_RGB = arrayOf(
Triple("0", "0", "0"),
Triple("240", "0", "0"),
Triple("240", "160", "0"),
Triple("208", "208", "0"),
Triple("0", "128", "0"),
Triple("144", "240", "64"),
Triple("64", "176", "240"),
Triple("0", "0", "240"),
Triple("80", "0", "128"),
Triple("224", "128", "224"),
)
private fun parseRating(ratingStyle: String): String? {
val m = PATTERN_RATING.matcher(ratingStyle)
var num1 = Int.MIN_VALUE
var num2 = Int.MIN_VALUE
var rate = 5
if (m.find()) {
num1 = ParserUtils.parseInt(m.group().replace("px", ""), Int.MIN_VALUE)
}
if (m.find()) {
num2 = ParserUtils.parseInt(m.group().replace("px", ""), Int.MIN_VALUE)
}
if (num1 == Int.MIN_VALUE || num2 == Int.MIN_VALUE) {
return null
}
rate -= num1 / 16
return if (num2 == 21) {
"${rate - 1}.5"
} else {
rate.toString()
}
}
private fun parseFavoriteSlot(style: String): Int {
val m = PATTERN_FAVORITE_SLOT.matcher(style)
if (m.find()) {
val r = m.group(1)!!
val g = m.group(2)!!
val b = m.group(3)!!
return FAVORITE_SLOT_RGB.indexOf(Triple(r, g, b))
}
return -2
}
private fun parseGalleryInfo(e: Element): GalleryInfo? {
val gi: GalleryInfo = BaseGalleryInfo()
// Title, gid, token (required), tags
val glname = JsoupUtils.getElementByClass(e, "glname")
if (glname != null) {
var a = JsoupUtils.getElementByTag(glname, "a")
if (a == null) {
val parent = glname.parent()
if (parent != null && "a" == parent.tagName()) {
a = parent
}
}
if (a != null) {
val result = GalleryDetailUrlParser.parse(a.attr("href"))
if (result != null) {
gi.gid = result.gid
gi.token = result.token
}
}
var child: Element = glname
var children = glname.children()
while (children.size != 0) {
child = children[0]
children = child.children()
}
gi.title = child.text().trim { it <= ' ' }
}
if (gi.title == null) {
return null
}
// Tags
val gts = e.select(".gt, .gtl")
if (gts.size != 0) {
val tags = ArrayList<String>()
for (gt in gts) {
tags.add(gt.attr("title"))
}
gi.simpleTags = tags.toTypedArray()
}
// Category
gi.category = EhUtils.UNKNOWN
var ce = JsoupUtils.getElementByClass(e, "cn")
if (ce == null) {
ce = JsoupUtils.getElementByClass(e, "cs")
}
if (ce != null) {
gi.category = EhUtils.getCategory(ce.text())
}
// Thumb
var thumbUrl: String? = null
val glthumb = JsoupUtils.getElementByClass(e, "glthumb")
if (glthumb != null) {
val img = glthumb.select("div:nth-child(1)>img").first()
if (img != null) {
// Thumb size
val m = PATTERN_THUMB_SIZE.matcher(img.attr("style"))
if (m.find()) {
gi.thumbWidth = NumberUtils.parseIntSafely(m.group(2), 0)
gi.thumbHeight = NumberUtils.parseIntSafely(m.group(1), 0)
} else {
Log.w(TAG, "Can't parse gallery info thumb size")
gi.thumbWidth = 0
gi.thumbHeight = 0
}
// Thumb url
var url = img.attr("data-src")
if (url.isEmpty()) {
url = img.attr("src")
}
if (url.isNotEmpty()) {
thumbUrl = EhUtils.handleThumbUrlResolution(url)!!
}
}
// Pages
val div = glthumb.select("div:nth-child(2)>div:nth-child(2)>div:nth-child(2)").first()
if (div != null) {
val matcher = PATTERN_PAGES.matcher(div.text())
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0)
}
}
}
// Try extended and thumbnail version
if (thumbUrl == null) {
var gl = JsoupUtils.getElementByClass(e, "gl1e")
if (gl == null) {
gl = JsoupUtils.getElementByClass(e, "gl3t")
}
if (gl != null) {
val img = JsoupUtils.getElementByTag(gl, "img")
if (img != null) {
// Thumb size
val m = PATTERN_THUMB_SIZE.matcher(img.attr("style"))
if (m.find()) {
gi.thumbWidth = NumberUtils.parseIntSafely(m.group(2), 0)
gi.thumbHeight = NumberUtils.parseIntSafely(m.group(1), 0)
} else {
Log.w(TAG, "Can't parse gallery info thumb size")
gi.thumbWidth = 0
gi.thumbHeight = 0
}
thumbUrl = EhUtils.handleThumbUrlResolution(img.attr("src"))
}
}
}
gi.thumbKey = getThumbKey(thumbUrl!!)
// Posted
val posted = e.getElementById("posted_" + gi.gid)
if (posted != null) {
gi.posted = posted.text().trim { it <= ' ' }
gi.favoriteSlot = parseFavoriteSlot(posted.attr("style"))
}
if (gi.favoriteSlot < 0) {
gi.favoriteSlot = if (EhDB.containLocalFavorites(gi.gid)) -1 else -2
}
// Rating
val ir = JsoupUtils.getElementByClass(e, "ir")
if (ir != null) {
gi.rating = NumberUtils.parseFloatSafely(parseRating(ir.attr("style")), -1.0f)
// TODO The gallery may be rated even if it doesn't has one of these classes
gi.rated = ir.hasClass("irr") || ir.hasClass("irg") || ir.hasClass("irb")
}
// Uploader and pages
var gl = JsoupUtils.getElementByClass(e, "glhide")
var uploaderIndex = 0
var pagesIndex = 1
if (gl == null) {
// For extended
gl = JsoupUtils.getElementByClass(e, "gl3e")
uploaderIndex = 3
pagesIndex = 4
}
if (gl != null) {
val children = gl.children()
if (children.size > uploaderIndex) {
val div = children[uploaderIndex]
if (div != null) {
gi.disowned = div.attr("style").contains("opacity:0.5")
val a = div.children().first()
gi.uploader = a?.text()?.trim { it <= ' ' } ?: div.text().trim { it <= ' ' }
}
}
if (children.size > pagesIndex) {
val matcher = PATTERN_PAGES.matcher(children[pagesIndex].text())
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0)
}
}
}
// For thumbnail
val gl5t = JsoupUtils.getElementByClass(e, "gl5t")
if (gl5t != null) {
val div = gl5t.select("div:nth-child(2)>div:nth-child(2)").first()
if (div != null) {
val matcher = PATTERN_PAGES.matcher(div.text())
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0)
}
}
}
gi.generateSLang()
return gi
}
fun parse(body: String): Result {
val d = Jsoup.parse(body)
return parse(d, body)
}
fun parse(d: Document, body: String): Result {
val result = Result()
try {
val prev = d.getElementById("uprev")
val next = d.getElementById("unext")
assert(prev != null)
assert(next != null)
val matcherPrev = PATTERN_PREV.matcher(prev!!.attr("href"))
val matcherNext = PATTERN_NEXT.matcher(next!!.attr("href"))
if (matcherPrev.find()) result.prev = matcherPrev.group(1)
if (matcherNext.find()) result.next = matcherNext.group(1)
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
result.noWatchedTags = body.contains("<p>You do not have any watched tags")
if (body.contains("No hits found</p>")) {
return result
}
}
try { // For toplists
val ptt = d.getElementsByClass("ptt").first()
if (ptt != null) {
val es = ptt.child(0).child(0).children()
result.pages = es[es.size - 2].text().trim { it <= ' ' }.toInt()
var e = es[es.size - 1]
if (e != null) {
e = e.children().first()
if (e != null) {
val href = e.attr("href")
val matcher = PATTERN_NEXT_PAGE.matcher(href)
if (matcher.find()) {
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0)
}
}
}
}
} catch (e: Throwable) {
e.printStackTrace()
}
try {
val itg = d.getElementsByClass("itg").first()
val es = if ("table".equals(itg!!.tagName(), ignoreCase = true)) {
itg.child(0).children()
} else {
itg.children()
}
val list = result.galleryInfoList
// First one is table header, skip it
for (i in es.indices) {
val gi = parseGalleryInfo(es[i])
if (null != gi) {
list.add(gi)
}
}
if (list.isEmpty()) {
if (es.size < 2 || NO_UNFILTERED_TEXT != es[1].text()) {
Log.d(TAG, "No gallery found")
}
}
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
e.printStackTrace()
throw ParseException("Can't parse gallery list", body)
}
return result
}
class Result {
var pages = 0
var nextPage = 0
var prev: String? = null
var next: String? = null
var noWatchedTags = false
val galleryInfoList = mutableListOf<GalleryInfo>()
}
}
@@ -1,71 +0,0 @@
/*
* Copyright 2019 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.exception.ParseException
import com.hippo.ehviewer.yorozuya.StringUtils
import org.json.JSONException
import org.json.JSONObject
import java.util.regex.Matcher
import java.util.regex.Pattern
object GalleryPageApiParser {
private val PATTERN_IMAGE_URL = Pattern.compile("<img[^>]*src=\"([^\"]+)\" style")
private val PATTERN_SKIP_HATH_KEY = Pattern.compile("onclick=\"return nl\\('([^)]+)'\\)")
private val PATTERN_ORIGIN_IMAGE_URL =
Pattern.compile("<a href=\"([^\"]+)fullimg.php([^\"]+)\">")
fun parse(body: String): Result {
return try {
var m: Matcher
val jo = JSONObject(body)
if (jo.has("error")) {
throw ParseException(jo.getString("error"), body)
}
val i3 = jo.getString("i3")
m = PATTERN_IMAGE_URL.matcher(i3)
val imageUrl = if (m.find()) {
StringUtils.unescapeXml(StringUtils.trim(m.group(1)))
} else {
null
}
val i6 = jo.getString("i6")
m = PATTERN_SKIP_HATH_KEY.matcher(i6)
val skipHathKey = if (m.find()) {
StringUtils.unescapeXml(StringUtils.trim(m.group(1)))
} else {
null
}
val i7 = jo.getString("i7")
m = PATTERN_ORIGIN_IMAGE_URL.matcher(i7)
val originImageUrl = if (m.find()) {
StringUtils.unescapeXml(m.group(1)) + "fullimg.php" +
StringUtils.unescapeXml(m.group(2))
} else {
null
}
if (!imageUrl.isNullOrEmpty()) {
Result(imageUrl, skipHathKey, originImageUrl)
} else {
throw ParseException("Parse image url and skip hath key error", body)
}
} catch (e: JSONException) {
throw ParseException("Can't parse json", body, e)
}
}
class Result(val imageUrl: String, val skipHathKey: String?, val originImageUrl: String?)
}
@@ -1,44 +0,0 @@
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.ParseException
import org.jsoup.Jsoup
object HomeParser {
private val PATTERN_FUNDS =
Regex("Available: ([\\d,]+) Credits.*Available: ([\\d,]+) kGP", RegexOption.DOT_MATCHES_ALL)
private const val RESET_SUCCEED = "Image limit was successfully reset."
fun parse(body: String): Limits {
Jsoup.parse(body).selectFirst("div.homebox")?.let {
val es = it.select("p > strong")
if (es.size == 3) {
val current = ParserUtils.parseInt(es[0].text(), 0)
val maximum = ParserUtils.parseInt(es[1].text(), 0)
val resetCost = ParserUtils.parseInt(es[2].text(), 0)
return Limits(current, maximum, resetCost)
}
}
throw ParseException("Parse image limits error", body)
}
fun parseResetLimits(body: String): Limits? {
return if (body.contains(RESET_SUCCEED)) {
null
} else {
parse(body)
}
}
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
return Funds(fundsGP, fundsC)
}
throw ParseException("Parse funds error", body)
}
data class Limits(val current: Int = 0, val maximum: Int, val resetCost: Int = 0)
data class Funds(val fundsGP: Int, val fundsC: Int)
class Result(val limits: Limits, val funds: Funds)
}
@@ -1,35 +0,0 @@
/*
* Copyright 2015 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.exception.ParseException
import org.json.JSONException
import org.json.JSONObject
object RateGalleryParser {
fun parse(body: String): Result {
return try {
val jsonObject = JSONObject(body)
val rating = jsonObject.getDouble("rating_avg").toFloat()
val ratingCount = jsonObject.getInt("rating_cnt")
Result(rating, ratingCount)
} catch (e: JSONException) {
throw ParseException("Can't parse rate gallery", body, e)
}
}
class Result(val rating: Float, val ratingCount: Int)
}
@@ -1,56 +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 org.jsoup.Jsoup
import java.util.regex.Pattern
object TorrentParser {
private val PATTERN_TORRENT =
Pattern.compile("</span> ([0-9-]+) [0-9:]+</td>[\\s\\S]+</span> ([0-9.]+ [KMGT]B)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span>([^<]+)</td>[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)</a>")
fun parse(body: String): List<Result> {
val torrentList = ArrayList<Result>()
val d = Jsoup.parse(body)
val es = d.select("form>div>table")
for (e in es) {
val m = PATTERN_TORRENT.matcher(e.html())
if (m.find()) {
val posted = m.group(1)!!
val size = m.group(2)!!
val seeds = m.group(3)!!.toInt()
val peers = m.group(4)!!.toInt()
val downloads = m.group(5)!!.toInt()
val url = ParserUtils.trim(m.group(7))
val name = ParserUtils.trim(m.group(8))
torrentList.add(Result(posted, size, seeds, peers, downloads, url, name))
}
}
return torrentList
}
class Result(
private val posted: String,
private val size: String,
private val seeds: Int,
private val peers: Int,
private val downloads: Int,
val url: String,
val name: String,
) {
fun format() = "[$posted] $name [$size] [↑$seeds ↓$peers ✓$downloads]"
}
}
@@ -1,31 +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 org.json.JSONObject
object VoteCommentParser {
// {"comment_id":1253922,"comment_score":-19,"comment_vote":0}
fun parse(body: String, expectVote: Int): Result {
val jo = JSONObject(body)
val id = jo.getLong("comment_id")
val score = jo.getInt("comment_score")
val vote = jo.getInt("comment_vote")
return Result(id, score, vote, expectVote)
}
class Result(val id: Long, val score: Int, val vote: Int, val expectVote: Int)
}
@@ -1,18 +0,0 @@
package com.hippo.ehviewer.coil
import coil.disk.DiskCache
suspend inline fun DiskCache.edit(key: String, crossinline block: suspend DiskCache.Editor.() -> Unit): Boolean {
val editor = openEditor(key) ?: return false
editor.runCatching {
block(this)
}.onFailure {
editor.abort()
throw it
}.onSuccess {
editor.commit()
}
return true
}
inline fun <T> DiskCache.read(key: String, block: DiskCache.Snapshot.() -> T): T? = openSnapshot(key)?.use(block)
@@ -1,80 +0,0 @@
package com.hippo.ehviewer.coil
import coil.intercept.Interceptor
import coil.request.ImageRequest
import coil.request.ImageResult
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
object MergeInterceptor : Interceptor {
private val pendingContinuationMap: HashMap<String, MutableList<Continuation<Unit>>> = hashMapOf()
private val pendingContinuationMapLock = Mutex()
private val notifyScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val EMPTY_LIST = mutableListOf<Continuation<Unit>>()
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val originRequest = chain.request
val originListener = originRequest.listener
val key = originRequest.data as String
val newRequest = ImageRequest.Builder(originRequest).listener(
{ originListener?.onStart(it) },
{ originListener?.onCancel(it) },
{ request, result ->
originListener?.onError(request, result)
// Wake all pending continuations since this request is to be failed
notifyScope.launch {
pendingContinuationMapLock.withLock {
pendingContinuationMap.remove(key)?.forEach { it.resume(Unit) }
}
}
},
{ request, result ->
originListener?.onSuccess(request, result)
// Wake all pending continuations shared with the same memory key since we have written it to memory cache
notifyScope.launch {
pendingContinuationMapLock.withLock {
pendingContinuationMap.remove(key)?.forEach { it.resume(Unit) }
}
}
},
).build()
pendingContinuationMapLock.lock()
val existPendingContinuations = pendingContinuationMap[key]
if (existPendingContinuations == null) {
pendingContinuationMap[key] = EMPTY_LIST
pendingContinuationMapLock.unlock()
} else {
if (existPendingContinuations === EMPTY_LIST) pendingContinuationMap[key] = mutableListOf()
pendingContinuationMap[key]!!.apply {
suspendCancellableCoroutine { continuation ->
add(continuation)
pendingContinuationMapLock.unlock()
continuation.invokeOnCancellation { remove(continuation) }
}
}
}
try {
return withContext(originRequest.interceptorDispatcher) { chain.proceed(newRequest) }
} catch (e: CancellationException) {
notifyScope.launch {
pendingContinuationMapLock.withLock {
// Wake up a pending continuation to continue executing task
val successor = pendingContinuationMap[key]?.removeFirstOrNull()?.apply { resume(Unit) }
// If no successor, delete this entry from hashmap
successor ?: pendingContinuationMap.remove(key)
}
}
throw e
}
}
}
@@ -1,6 +0,0 @@
package com.hippo.ehviewer.dao
interface BasicDao<T> {
fun list(): List<T>
fun insert(t: T): Long
}
@@ -1,16 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.hippo.ehviewer.client.data.BaseGalleryInfo
@Entity(tableName = "BOOKMARKS")
class BookmarkInfo : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "PAGE")
var page = 0
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
}
@@ -1,18 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
@Dao
interface BookmarksDao : BasicDao<BookmarkInfo> {
@Insert
override fun insert(t: BookmarkInfo): Long
@Delete
fun delete(bookmark: BookmarkInfo)
@Query("SELECT * FROM BOOKMARKS ORDER BY TIME DESC")
override fun list(): List<BookmarkInfo>
}
@@ -1,22 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface CookiesDao {
@Query("SELECT * FROM OK_HTTP_3_COOKIE")
fun list(): List<Cookie>
@Delete
fun delete(cookie: Cookie)
@Insert
fun insert(cookie: Cookie): Long
@Update
fun update(cookie: Cookie)
}
@@ -1,27 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface DownloadDirnameDao : BasicDao<DownloadDirname> {
@Query("SELECT * FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
fun load(gid: Long): DownloadDirname?
@Update
fun update(downloadDirname: DownloadDirname)
@Insert
override fun insert(t: DownloadDirname): Long
@Query("DELETE FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
fun deleteByKey(gid: Long)
@Query("DELETE FROM DOWNLOAD_DIRNAME")
fun deleteAll()
@Query("SELECT * FROM DOWNLOAD_DIRNAME")
override fun list(): List<DownloadDirname>
}
@@ -1,69 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "DOWNLOADS")
class DownloadInfo() : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "STATE")
var state = 0
@JvmField
@ColumnInfo(name = "LEGACY")
var legacy = 0
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
@JvmField
@ColumnInfo(name = "LABEL")
var label: String? = null
@JvmField
@Ignore
var speed: Long = 0
@JvmField
@Ignore
var remaining: Long = 0
@JvmField
@Ignore
var finished = 0
@JvmField
@Ignore
var downloaded = 0
@JvmField
@Ignore
var total = 0
constructor(galleryInfo: GalleryInfo) : this() {
gid = galleryInfo.gid
token = galleryInfo.token
title = galleryInfo.title
titleJpn = galleryInfo.titleJpn
thumbKey = galleryInfo.thumbKey
this.category = galleryInfo.category
posted = galleryInfo.posted
uploader = galleryInfo.uploader
rating = galleryInfo.rating
simpleTags = galleryInfo.simpleTags
simpleLanguage = galleryInfo.simpleLanguage
}
companion object {
const val STATE_INVALID = -1
const val STATE_NONE = 0
const val STATE_WAIT = 1
const val STATE_DOWNLOAD = 2
const val STATE_FINISH = 3
const val STATE_FAILED = 4
}
}
@@ -1,28 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface DownloadLabelDao : BasicDao<DownloadLabel> {
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC")
override fun list(): List<DownloadLabel>
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
fun list(offset: Int, limit: Int): List<DownloadLabel>
@Update
fun update(downloadLabels: List<DownloadLabel>)
@Update
fun update(downloadLabel: DownloadLabel)
@Insert
override fun insert(t: DownloadLabel): Long
@Delete
fun delete(downloadLabel: DownloadLabel)
}
@@ -1,31 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface DownloadsDao : BasicDao<DownloadInfo> {
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC")
override fun list(): List<DownloadInfo>
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC LIMIT :limit OFFSET :offset")
fun list(offset: Int, limit: Int): List<DownloadInfo>
@Query("SELECT * FROM DOWNLOADS WHERE GID = :gid")
fun load(gid: Long): DownloadInfo?
@Update
fun update(downloadInfos: List<DownloadInfo>)
@Update
fun update(downloadInfo: DownloadInfo)
@Insert
override fun insert(t: DownloadInfo): Long
@Delete
fun delete(downloadInfo: DownloadInfo)
}
@@ -1,71 +0,0 @@
package com.hippo.ehviewer.dao
import android.content.Context
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val EHDB_MIGRATION_4_5 = object : Migration(4, 5) {
override fun migrate(db: SupportSQLiteDatabase) {
val needMigrationTables = arrayOf(
"DOWNLOADS",
"HISTORY",
"LOCAL_FAVORITES",
)
val prefixToRemove = arrayOf(
"https://ehgt.org/",
"https://s.exhentai.org/t/",
"https://exhentai.org/t/",
)
needMigrationTables.forEach { table ->
prefixToRemove.forEach { prefix ->
db.execSQL("UPDATE $table SET thumb = SUBSTR(thumb ,LENGTH('$prefix') + 1) WHERE thumb LIKE '$prefix%'")
}
}
}
}
@Database(
entities = [BookmarkInfo::class, DownloadInfo::class, DownloadLabel::class, DownloadDirname::class, Filter::class, HistoryInfo::class, LocalFavoriteInfo::class, QuickSearch::class],
version = 5,
exportSchema = false,
)
abstract class EhDatabase : RoomDatabase() {
abstract fun bookmarksBao(): BookmarksDao
abstract fun downloadDirnameDao(): DownloadDirnameDao
abstract fun downloadLabelDao(): DownloadLabelDao
abstract fun downloadsDao(): DownloadsDao
abstract fun filterDao(): FilterDao
abstract fun historyDao(): HistoryDao
abstract fun localFavoritesDao(): LocalFavoritesDao
abstract fun quickSearchDao(): QuickSearchDao
}
// 1 -> 2 some nullability changes
@Database(
entities = [Cookie::class],
version = 2,
autoMigrations = [
AutoMigration(
from = 1,
to = 2,
),
],
)
abstract class CookiesDatabase : RoomDatabase() {
abstract fun cookiesDao(): CookiesDao
}
fun buildMainDB(context: Context): EhDatabase {
// TODO: Remove allowMainThreadQueries
return Room.databaseBuilder(context, EhDatabase::class.java, "eh.db").allowMainThreadQueries()
.addMigrations(EHDB_MIGRATION_4_5)
.build()
}
fun buildCookiesDB(context: Context): CookiesDatabase {
return Room.databaseBuilder(context, CookiesDatabase::class.java, "okhttp3-cookie.db").build()
}
@@ -1,24 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "FILTER")
data class Filter(
@JvmField
@ColumnInfo(name = "MODE")
var mode: Int = 0,
@JvmField
@ColumnInfo(name = "TEXT")
var text: String? = null,
@JvmField
@ColumnInfo(name = "ENABLE")
var enable: Boolean? = null,
@PrimaryKey
@ColumnInfo(name = "_id")
var id: Long? = null,
)
@@ -1,38 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface HistoryDao : BasicDao<HistoryInfo> {
@Query("SELECT * FROM HISTORY WHERE GID = :gid")
fun load(gid: Long): HistoryInfo?
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC")
override fun list(): List<HistoryInfo>
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC LIMIT :limit OFFSET :offset")
fun list(offset: Int, limit: Int): List<HistoryInfo>
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC")
fun listLazy(): PagingSource<Int, HistoryInfo>
@Update
fun update(historyInfo: HistoryInfo)
@Insert
override fun insert(t: HistoryInfo): Long
@Delete
fun delete(historyInfo: HistoryInfo)
@Delete
fun delete(historyInfo: List<HistoryInfo>)
@Query("DELETE FROM HISTORY")
fun deleteAll()
}
@@ -1,39 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "HISTORY")
class HistoryInfo() : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
// Trick: Use MODE for favoriteSlot
@ColumnInfo(name = "MODE")
var favoriteSlotBackingField: Int = 0
override var favoriteSlot: Int
get() = favoriteSlotBackingField - 2
set(value) {
favoriteSlotBackingField = value + 2
}
// Trick end
constructor(galleryInfo: GalleryInfo) : this() {
gid = galleryInfo.gid
token = galleryInfo.token
title = galleryInfo.title
titleJpn = galleryInfo.titleJpn
thumbKey = galleryInfo.thumbKey
this.category = galleryInfo.category
posted = galleryInfo.posted
uploader = galleryInfo.uploader
rating = galleryInfo.rating
simpleTags = galleryInfo.simpleTags
simpleLanguage = galleryInfo.simpleLanguage
favoriteSlot = galleryInfo.favoriteSlot
}
}
@@ -1,31 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "LOCAL_FAVORITES")
class LocalFavoriteInfo() : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
constructor(galleryInfo: GalleryInfo) : this() {
gid = galleryInfo.gid
token = galleryInfo.token
title = galleryInfo.title
titleJpn = galleryInfo.titleJpn
thumbKey = galleryInfo.thumbKey
this.category = galleryInfo.category
posted = galleryInfo.posted
uploader = galleryInfo.uploader
rating = galleryInfo.rating
simpleTags = galleryInfo.simpleTags
simpleLanguage = galleryInfo.simpleLanguage
}
init {
favoriteSlot = -1
}
}
@@ -1,30 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
@Dao
interface LocalFavoritesDao : BasicDao<LocalFavoriteInfo> {
@Query("SELECT * FROM LOCAL_FAVORITES ORDER BY TIME DESC")
override fun list(): List<LocalFavoriteInfo>
@Query("SELECT * FROM LOCAL_FAVORITES WHERE TITLE LIKE :title ORDER BY TIME DESC")
fun list(title: String): List<LocalFavoriteInfo>
@Query("SELECT * FROM LOCAL_FAVORITES WHERE GID = :gid")
fun load(gid: Long): LocalFavoriteInfo?
@Query("SELECT EXISTS(SELECT * FROM LOCAL_FAVORITES WHERE GID = :gid)")
fun contains(gid: Long): Boolean
@Insert
override fun insert(t: LocalFavoriteInfo): Long
@Delete
fun delete(localFavoriteInfo: LocalFavoriteInfo)
@Query("DELETE FROM LOCAL_FAVORITES WHERE GID = :gid")
fun deleteByKey(gid: Long)
}
@@ -1,28 +0,0 @@
package com.hippo.ehviewer.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface QuickSearchDao : BasicDao<QuickSearch> {
@Query("SELECT * FROM QUICK_SEARCH ORDER BY TIME ASC")
override fun list(): List<QuickSearch>
@Query("SELECT * FROM QUICK_SEARCH ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
fun list(offset: Int, limit: Int): List<QuickSearch>
@Update
fun update(downloadLabels: List<QuickSearch>)
@Update
fun update(quickSearch: QuickSearch)
@Insert
override fun insert(t: QuickSearch): Long
@Delete
fun delete(quickSearch: QuickSearch)
}
@@ -1,124 +0,0 @@
/*
* Copyright 2017 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.database
import android.content.Context
import android.database.sqlite.SQLiteOpenHelper
import android.util.SparseArray
class MSQLiteBuilder {
companion object {
const val COLUMN_ID = "_id"
private val JAVA_TYPE_TO_SQLITE_TYPE: MutableMap<Class<*>?, String> = HashMap()
private fun javaTypeToSQLiteType(clazz: Class<*>?): String {
return JAVA_TYPE_TO_SQLITE_TYPE[clazz]
?: throw IllegalStateException("Unknown type: $clazz")
}
init {
JAVA_TYPE_TO_SQLITE_TYPE[Boolean::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Byte::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Short::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Int::class.javaPrimitiveType] = "INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Long::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Float::class.javaPrimitiveType] =
"REAL NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Double::class.javaPrimitiveType] = "REAL NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[String::class.java] = "TEXT"
}
}
private val statementsMap = SparseArray<List<String>?>()
private var version = 0
private var statements: MutableList<String>? = null
/**
* Bump database version.
*/
fun version(version: Int): MSQLiteBuilder {
check(version > this.version) {
(
"New version must be bigger than current version. " +
"current version: " + this.version + ", new version: " + version + "."
)
}
this.version = version
statements = ArrayList()
statementsMap.put(version, statements)
return this
}
/**
* Creates a table.
*/
@JvmOverloads
fun createTable(
table: String,
column: String = COLUMN_ID,
clazz: Class<*>? = Int::class.javaPrimitiveType,
): MSQLiteBuilder {
return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);")
}
/**
* Drops a table.
*/
fun dropTable(table: String): MSQLiteBuilder {
return statement("DROP TABLE $table;")
}
/**
* Inserts a column to the table.
*/
fun insertColumn(table: String, column: String, clazz: Class<*>?): MSQLiteBuilder {
return statement(
"ALTER TABLE $table ADD COLUMN $column " + javaTypeToSQLiteType(
clazz,
) + ";",
)
}
/**
* Add a statement.
*/
fun statement(statement: String): MSQLiteBuilder {
check(!(version == 0 || statements == null)) { "Call version() first!" }
statements!!.add(statement)
return this
}
/**
* Build a SQLiteOpenHelper from it.
*/
fun build(context: Context?, name: String?, version: Int): SQLiteOpenHelper {
return MSQLiteOpenHelper(context, name, version, this)
}
fun getStatements(oldVersion: Int, newVersion: Int): List<String> {
val result: MutableList<String> = ArrayList()
for (i in oldVersion + 1..newVersion) {
val list = statementsMap[i]
if (list != null) {
result.addAll(list)
}
}
return result
}
}
@@ -1,43 +0,0 @@
/*
* Copyright 2017 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.database
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import com.hippo.ehviewer.util.SqlUtils
internal class MSQLiteOpenHelper(
context: Context?,
name: String?,
private val version: Int,
private val builder: MSQLiteBuilder,
) : SQLiteOpenHelper(context, name, null, version) {
override fun onCreate(db: SQLiteDatabase) {
onUpgrade(db, 0, version)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
for (command in builder.getStatements(oldVersion, newVersion)) {
db.execSQL(command)
}
}
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
SqlUtils.dropAllTable(db)
onCreate(db)
}
}
@@ -1,110 +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.image
import android.graphics.ColorSpace
import android.graphics.ImageDecoder
import android.graphics.ImageDecoder.ImageInfo
import android.graphics.ImageDecoder.Source
import android.graphics.drawable.Animatable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import java.nio.ByteBuffer
import kotlin.math.min
class Image private constructor(private val src: CloseableSource) {
var mObtainedDrawable: Drawable? =
ImageDecoder.decodeDrawable(src.source) { decoder: ImageDecoder, info: ImageInfo, _: Source ->
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
// Allocating hardware bitmap may cause a crash on framework versions prior to Android Q
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
}
decoder.setTargetColorSpace(colorSpace)
decoder.setTargetSampleSize(
calculateSampleSize(info, 2 * screenHeight, 2 * screenWidth),
)
}.also {
if (it !is Animatable) src.close()
}
private set
val size: Int
get() = mObtainedDrawable!!.run { intrinsicHeight * intrinsicWidth * 4 * if (this is Animatable) 4 else 1 }
@Synchronized
fun recycle() {
(mObtainedDrawable as? Animatable)?.stop()
(mObtainedDrawable as? BitmapDrawable)?.bitmap?.recycle()
mObtainedDrawable?.callback = null
if (mObtainedDrawable is Animatable) src.close()
mObtainedDrawable = null
}
companion object {
fun calculateSampleSize(info: ImageInfo, targetHeight: Int, targetWeight: Int): Int {
return min(
info.size.width / targetWeight,
info.size.height / targetHeight,
).coerceAtLeast(1)
}
private val imageSearchMaxSize =
EhApplication.application.resources.getDimensionPixelOffset(R.dimen.image_search_max_size)
@JvmStatic
val imageSearchDecoderSampleListener =
ImageDecoder.OnHeaderDecodedListener { decoder, info, _ ->
decoder.setTargetSampleSize(
calculateSampleSize(info, imageSearchMaxSize, imageSearchMaxSize),
)
}
val screenWidth = EhApplication.application.resources.displayMetrics.widthPixels
val screenHeight = EhApplication.application.resources.displayMetrics.heightPixels
val isWideColorGamut =
EhApplication.application.resources.configuration.isScreenWideColorGamut
var colorSpace = ColorSpace.get(
if (isWideColorGamut && EhApplication.readerPreferences.wideColorGamut()
.get()
) {
ColorSpace.Named.DISPLAY_P3
} else {
ColorSpace.Named.SRGB
},
)
@JvmStatic
fun decode(src: CloseableSource): Image? {
return runCatching {
Image(src)
}.onFailure {
src.close()
it.printStackTrace()
}.getOrNull()
}
}
interface CloseableSource : AutoCloseable {
val source: Source
}
}
external fun rewriteGifSource(buffer: ByteBuffer)
external fun rewriteGifSource2(fd: Int)
@@ -1,90 +0,0 @@
/*
* Copyright 2017 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.network
import android.content.Context
import com.hippo.ehviewer.dao.Cookie
import com.hippo.ehviewer.dao.buildCookiesDB
import okhttp3.Cookie as OkHttpCookie
class CookieDatabase(context: Context) {
private val cookiesList by lazy {
val now = System.currentTimeMillis()
db.cookiesDao().list().mapNotNull {
it.takeUnless { !it.persistent || it.expiresAt <= now } ?: run {
db.cookiesDao().delete(it)
null
}
}.toMutableList()
}
private val db = buildCookiesDB(context)
val allCookies by lazy {
hashMapOf<String, CookieSet>().also { map ->
cookiesList.map { it.toOkHttp3Cookie() }.forEach {
val set = map[it.domain] ?: CookieSet().apply { map[it.domain] = this }
set.add(it)
}
}
}
private fun findCookieWithOkHttpCookies(cookie: OkHttpCookie): Cookie? {
return cookiesList.find { it.name == cookie.name && it.domain == cookie.domain && it.value == cookie.value }
}
fun add(cookie: OkHttpCookie) {
val c = cookie.toCookie()
c.id = db.cookiesDao().insert(c)
cookiesList.add(c)
}
fun update(from: OkHttpCookie, to: OkHttpCookie) {
val origin = findCookieWithOkHttpCookies(from) ?: return
val new = to.toCookie(origin.id)
cookiesList.remove(origin)
cookiesList.add(new)
db.cookiesDao().update(new)
}
fun remove(cookie: OkHttpCookie) {
val origin = findCookieWithOkHttpCookies(cookie) ?: return
db.cookiesDao().delete(origin)
cookiesList.remove(origin)
}
fun clear() {
db.clearAllTables()
cookiesList.clear()
}
}
fun Cookie.toOkHttp3Cookie(): OkHttpCookie {
return OkHttpCookie.Builder().apply {
name(name)
value(value)
expiresAt(expiresAt)
if (hostOnly) hostOnlyDomain(domain) else domain(domain)
path(path)
if (secure) secure()
if (httpOnly) httpOnly()
}.build()
}
private fun OkHttpCookie.toCookie(id: Long? = null): Cookie {
return Cookie(
name, value, expiresAt, domain, path, secure, httpOnly, persistent, hostOnly, id,
)
}
@@ -1,68 +0,0 @@
/*
* Copyright 2017 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.network
import okhttp3.Cookie
import okhttp3.HttpUrl
class CookieSet {
private val map: MutableMap<Key, Cookie> = HashMap()
/**
* Adds a cookie to this `CookieSet`.
* Returns a previous cookie with
* the same name, domain and path or `null`.
*/
fun add(cookie: Cookie): Cookie? {
return map.put(Key(cookie), cookie)
}
/**
* Removes a cookie with the same name,
* domain and path as the cookie.
* Returns the removed cookie or `null`.
*/
fun remove(cookie: Cookie): Cookie? {
return map.remove(Key(cookie))
}
/**
* Get cookies for the url. Fill `accepted` and `expired`.
*/
operator fun get(url: HttpUrl, accepted: MutableList<Cookie>, expired: MutableList<Cookie>) {
val now = System.currentTimeMillis()
val iterator = map.entries.iterator()
while (iterator.hasNext()) {
val cookie = iterator.next().value
if (cookie.expiresAt <= now) {
iterator.remove()
expired.add(cookie)
} else if (cookie.matches(url)) {
accepted.add(cookie)
}
}
}
fun get(name: String, domain: String, path: String) = map[Key(name, domain, path)]
internal data class Key(
val name: String,
val domain: String,
val path: String,
) {
constructor(cookie: Cookie) : this(cookie.name, cookie.domain, cookie.path)
}
}
@@ -1,59 +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.network
import java.util.regex.Pattern
object InetValidator {
private const val IPV4_REGEX = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"
private val IPV4_PATTERN = Pattern.compile(IPV4_REGEX)
@JvmStatic
fun isValidInet4Address(inet4Address: String?): Boolean {
if (null == inet4Address) {
return false
}
val matcher = IPV4_PATTERN.matcher(inet4Address)
if (!matcher.find()) {
return false
}
// verify that address subgroups are legal
for (i in 1..4) {
val ipSegment = matcher.group(i)
if (ipSegment == null || ipSegment.isEmpty()) {
return false
}
val iIpSegment: Int = try {
ipSegment.toInt()
} catch (e: NumberFormatException) {
return false
}
if (iIpSegment > 255) {
return false
}
if (ipSegment.length > 1 && ipSegment.startsWith("0")) {
return false
}
}
return true
}
@JvmStatic
fun isValidInetPort(inetPort: Int): Boolean {
return inetPort in 0..65535
}
}
@@ -1,44 +0,0 @@
/*
* Copyright (C) 2014-2015 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.network
class UrlBuilder(private var mRootUrl: String) {
private var mQueryMap: MutableMap<String, Any> = HashMap()
fun addQuery(key: String, value: Any) {
mQueryMap[key] = value
}
fun build(): String {
return if (mQueryMap.isEmpty()) {
mRootUrl
} else {
val sb = StringBuilder(mRootUrl)
sb.append("?")
val iter: Iterator<String> = mQueryMap.keys.iterator()
if (iter.hasNext()) {
val key = iter.next()
val value = mQueryMap[key]
sb.append(key).append("=").append(value)
}
while (iter.hasNext()) {
val key = iter.next()
val value = mQueryMap[key]
sb.append("&").append(key).append("=").append(value)
}
sb.toString()
}
}
}
@@ -1,96 +0,0 @@
/*
* Copyright 2018 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.preference
import android.content.Context
import android.content.DialogInterface
import android.text.Html
import android.util.AttributeSet
import androidx.appcompat.app.AlertDialog
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.ui.SettingsActivity
import com.hippo.ehviewer.ui.legacy.preference.MessagePreference
import com.hippo.ehviewer.ui.scene.BaseScene
import com.hippo.ehviewer.util.addTextToClipboard
import okhttp3.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.LinkedList
class AccountPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : MessagePreference(context, attrs) {
private val mActivity = context as SettingsActivity
private var message: String? = null
init {
val store = EhCookieStore
val eCookies = store.getCookies(EhUrl.HOST_E.toHttpUrl())
val exCookies = store.getCookies(EhUrl.HOST_EX.toHttpUrl())
val cookies: MutableList<Cookie> = LinkedList(eCookies)
cookies.addAll(exCookies)
var ipbMemberId: String? = null
var ipbPassHash: String? = null
var igneous: String? = null
var i = 0
val n = cookies.size
while (i < n) {
val cookie = cookies[i]
when (cookie.name) {
EhCookieStore.KEY_IPB_MEMBER_ID -> ipbMemberId = cookie.value
EhCookieStore.KEY_IPB_PASS_HASH -> ipbPassHash = cookie.value
EhCookieStore.KEY_IGNEOUS -> igneous = cookie.value
}
i++
}
if (ipbMemberId != null || ipbPassHash != null || igneous != null) {
message = (
EhCookieStore.KEY_IPB_MEMBER_ID + ": " + ipbMemberId + "<br>" +
EhCookieStore.KEY_IPB_PASS_HASH + ": " + ipbPassHash + "<br>" +
EhCookieStore.KEY_IGNEOUS + ": " + igneous
)
setDialogMessage(
Html.fromHtml(
context.getString(
R.string.settings_eh_identity_cookies_signed,
message,
),
Html.FROM_HTML_MODE_LEGACY,
),
)
message = message!!.replace("<br>", "\n")
} else {
setDialogMessage(context.getString(R.string.settings_eh_identity_cookies_tourist))
}
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
if (message != null) {
builder.setNeutralButton(R.string.settings_eh_identity_cookies_copy) { dialog: DialogInterface?, which: Int ->
mActivity.addTextToClipboard(message, true)
this@AccountPreference.onClick(dialog, which)
}
}
builder.setPositiveButton(R.string.settings_eh_sign_out) { _: DialogInterface?, _: Int ->
EhUtils.signOut()
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT)
}
}
}
@@ -1,78 +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.preference
import android.content.Context
import android.util.AttributeSet
import com.hippo.ehviewer.GetText
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import com.hippo.ehviewer.download.DownloadManager as downloadManager
private val NO_REDUNDANCY =
GetText.getString(R.string.settings_download_clean_redundancy_no_redundancy)
private val CLEAR_REDUNDANCY_DONE =
{ cnt: Int -> GetText.getString(R.string.settings_download_clean_redundancy_done, cnt) }
private val FINAL_CLEAR_REDUNDANCY_MSG =
{ cnt: Int -> if (cnt == 0) NO_REDUNDANCY else CLEAR_REDUNDANCY_DONE(cnt) }
class CleanRedundancyPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : TaskPreference(context, attrs) {
private fun clearFile(file: UniFile): Boolean {
var name = file.name ?: return false
val index = name.indexOf('-')
if (index >= 0) {
name = name.substring(0, index)
}
val gid = name.toLongOrNull() ?: return false
if (downloadManager.containDownloadInfo(gid)) {
return false
}
file.delete()
return true
}
private fun doRealWork(): Int {
return Settings.downloadLocation?.listFiles()?.sumOf { clearFile(it).compareTo(false) } ?: 0
}
override fun launchJob() {
if (singletonJob?.isActive == true) {
singletonJob?.invokeOnCompletion {
launchUI {
dialog.dismiss()
}
}
} else {
singletonJob = launch {
val cnt = doRealWork()
withUIContext {
showTip(FINAL_CLEAR_REDUNDANCY_MSG(cnt))
dialog.dismiss()
}
}
}
}
}
private var singletonJob: Job? = null
@@ -1,86 +0,0 @@
package com.hippo.ehviewer.preference
import android.content.Context
import android.content.DialogInterface
import android.util.AttributeSet
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.SettingsActivity
import com.hippo.ehviewer.ui.legacy.preference.DialogPreference
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
class ImageLimitsPreference(context: Context, attrs: AttributeSet) :
DialogPreference(context, attrs), View.OnClickListener {
private val mActivity = context as SettingsActivity
private val placeholder = context.getString(R.string.please_wait)
private lateinit var resetButton: Button
private lateinit var mDialog: AlertDialog
init {
updateSummary()
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setMessage(placeholder)
}
override fun onDialogCreated(dialog: AlertDialog) {
super.onDialogCreated(dialog)
mDialog = dialog
resetButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
resetButton.setOnClickListener(this)
resetButton.isEnabled = false
launchIOCatching {
mActivity.getImageLimits()
}
}
override fun onDialogClosed(positiveResult: Boolean) {
super.onDialogClosed(positiveResult)
updateSummary()
}
private fun updateSummary() {
summary = mActivity.mLimits?.run {
mActivity.getString(R.string.image_limits_summary, current, maximum)
} ?: mActivity.getString(R.string.image_limits_summary, 0, 0)
}
private fun launchIOCatching(block: suspend () -> Unit) = mActivity.lifecycleScope.launchIO {
runCatching {
block()
}.onFailure {
it.printStackTrace()
withUIContext {
mDialog.setMessage(it.message)
}
}.onSuccess {
withUIContext {
bind()
}
}
}
private fun bind() {
val (current, maximum, resetCost) = mActivity.mLimits!!
val (fundsGP, fundsC) = mActivity.mFunds!!
val cost = if (fundsGP >= resetCost) "$resetCost GP" else "$resetCost Credits"
val message = mActivity.getString(R.string.current_limits, "$current / $maximum", cost) +
"\n" + mActivity.getString(R.string.current_funds, "$fundsGP+", fundsC)
mDialog.setMessage(message)
resetButton.isEnabled = resetCost in 1..maxOf(fundsGP, fundsC)
}
override fun onClick(v: View) {
resetButton.isEnabled = false
mDialog.setMessage(placeholder)
launchIOCatching {
mActivity.resetImageLimits()
}
}
}
@@ -1,171 +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.preference
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
import android.widget.AutoCompleteTextView
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import com.google.android.material.textfield.TextInputLayout
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhProxySelector
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.network.InetValidator
import com.hippo.ehviewer.ui.legacy.preference.DialogPreference
import com.hippo.ehviewer.yorozuya.MathUtils
import com.hippo.ehviewer.yorozuya.ViewUtils
class ProxyPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : DialogPreference(context, attrs), View.OnClickListener {
private var mType: TextInputLayout? = null
private var mIpInputLayout: TextInputLayout? = null
private var mIp: EditText? = null
private var mPortInputLayout: TextInputLayout? = null
private var mPort: EditText? = null
private val mArray: Array<String>
init {
mArray = context.resources.getStringArray(R.array.proxy_types)
dialogLayoutResource = R.layout.preference_dialog_proxy
updateSummary(Settings.proxyType, Settings.proxyIp, Settings.proxyPort)
}
private fun getProxyTypeText(type: Int): String {
return mArray[MathUtils.clamp(type, 0, mArray.size - 1)]
}
private fun updateSummary(type: Int, ip: String?, port: Int) {
var type1 = type
if ((type1 == EhProxySelector.TYPE_HTTP || type1 == EhProxySelector.TYPE_SOCKS) &&
(TextUtils.isEmpty(ip) || !InetValidator.isValidInetPort(port))
) {
type1 = EhProxySelector.TYPE_SYSTEM
}
summary = if (type1 == EhProxySelector.TYPE_HTTP || type1 == EhProxySelector.TYPE_SOCKS) {
val context = context
context.getString(
R.string.settings_advanced_proxy_summary_1,
getProxyTypeText(type1),
ip,
port,
)
} else {
val context = context
context.getString(
R.string.settings_advanced_proxy_summary_2,
getProxyTypeText(type1),
)
}
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setPositiveButton(android.R.string.ok, null)
}
@SuppressLint("SetTextI18n")
override fun onDialogCreated(dialog: AlertDialog) {
super.onDialogCreated(dialog)
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(this)
mType = ViewUtils.`$$`(dialog, R.id.type) as TextInputLayout
val array = context.resources.getStringArray(R.array.proxy_types)
mIpInputLayout = ViewUtils.`$$`(dialog, R.id.ip_input_layout) as TextInputLayout
mIp = ViewUtils.`$$`(dialog, R.id.ip) as EditText
mPortInputLayout = ViewUtils.`$$`(dialog, R.id.port_input_layout) as TextInputLayout
mPort = ViewUtils.`$$`(dialog, R.id.port) as EditText
val type = Settings.proxyType
(mType!!.editText as AutoCompleteTextView).setText(
array[
MathUtils.clamp(
type,
0,
array.size,
),
],
false,
)
mIp!!.setText(Settings.proxyIp)
val portString: String?
val port = Settings.proxyPort
portString = if (!InetValidator.isValidInetPort(port)) {
null
} else {
port.toString()
}
mPort!!.setText(portString)
}
override fun onDialogClosed(positiveResult: Boolean) {
super.onDialogClosed(positiveResult)
mType = null
mIpInputLayout = null
mIp = null
mPortInputLayout = null
mPort = null
}
override fun onClick(v: View) {
val dialog = dialog
val context: Context = context
if (null == dialog || null == mType || null == mIpInputLayout || null == mIp || null == mPortInputLayout || null == mPort) {
return
}
val type = mArray.indexOf(mType!!.editText!!.text.toString())
val ip = mIp!!.text.toString().trim { it <= ' ' }
if (ip.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mIpInputLayout!!.error = context.getString(R.string.text_is_empty)
return
}
}
mIpInputLayout!!.error = null
val port: Int
val portString = mPort!!.text.toString().trim { it <= ' ' }
if (portString.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mPortInputLayout!!.error = context.getString(R.string.text_is_empty)
return
} else {
port = -1
}
} else {
port = try {
portString.toInt()
} catch (e: NumberFormatException) {
-1
}
if (!InetValidator.isValidInetPort(port)) {
mPortInputLayout!!.error = context.getString(R.string.proxy_invalid_port)
return
}
}
mPortInputLayout!!.error = null
Settings.putProxyType(type)
Settings.putProxyIp(ip)
Settings.putProxyPort(port)
updateSummary(type, ip, port)
EhApplication.ehProxySelector.updateProxy()
dialog.dismiss()
}
}
@@ -1,134 +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.preference
import android.content.Context
import android.util.AttributeSet
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.GetText
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine.fillGalleryListByApi
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.spider.SpiderQueen
import com.hippo.ehviewer.spider.readCompatFromUniFile
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import moe.tarsin.coroutines.runSuspendCatching
import com.hippo.ehviewer.download.DownloadManager as downloadManager
private val RESTORE_NOT_FOUND = GetText.getString(R.string.settings_download_restore_not_found)
private val RESTORE_FAILED = GetText.getString(R.string.settings_download_restore_failed)
private val RESTORE_COUNT_MSG =
{ cnt: Int -> if (cnt == 0) RESTORE_NOT_FOUND else GetText.getString(R.string.settings_download_restore_successfully, cnt) }
class RestoreDownloadPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : TaskPreference(context, attrs) {
private val mManager: DownloadManager = downloadManager
private var restoreDirCount = 0
private fun getRestoreItem(file: UniFile?): RestoreItem? {
if (null == file || !file.isDirectory) return null
val siFile = file.findFile(SpiderQueen.SPIDER_INFO_FILENAME) ?: return null
return runCatching {
val spiderInfo = readCompatFromUniFile(siFile) ?: return null
val gid = spiderInfo.gid
val dirname = file.name
if (mManager.containDownloadInfo(gid)) {
// Restore download dir to avoid redownload
val dbdirname = EhDB.getDownloadDirname(gid)
if (null == dbdirname || dirname != dbdirname) {
EhDB.putDownloadDirname(gid, dirname)
restoreDirCount++
}
return null
}
RestoreItem().also {
it.gid = spiderInfo.gid
it.token = spiderInfo.token
it.dirname = file.name
}
}.onFailure {
it.printStackTrace()
}.getOrNull()
}
private suspend fun doRealWork(): List<GalleryInfo>? {
val dir = Settings.downloadLocation ?: return null
val files = dir.listFiles() ?: return null
return files.mapNotNull { getRestoreItem(it) }.apply {
runSuspendCatching {
fillGalleryListByApi(this, EhUrl.referer)
}.onFailure {
it.printStackTrace()
}
}
}
private class RestoreItem : BaseGalleryInfo() {
var dirname: String? = null
}
override fun launchJob() {
if (singletonJob?.isActive == true) {
singletonJob?.invokeOnCompletion {
launchUI {
dialog.dismiss()
}
}
} else {
singletonJob = launch {
val result = doRealWork()
withUIContext {
if (result == null) {
showTip(RESTORE_FAILED)
} else {
if (result.isEmpty()) {
showTip(RESTORE_COUNT_MSG(restoreDirCount))
} else {
var count = 0
var i = 0
val n = result.size
while (i < n) {
val item = result[i]
// Avoid failed gallery info
if (null != item.title) {
// Put to download
mManager.addDownload(item, null)
// Put download dir to DB
EhDB.putDownloadDirname(item.gid, (item as RestoreItem).dirname)
count++
}
i++
}
showTip(RESTORE_COUNT_MSG(count + restoreDirCount))
}
}
dialog.dismiss()
}
}
}
}
}
private var singletonJob: Job? = null
@@ -1,54 +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.preference
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.SettingsActivity
import com.hippo.ehviewer.ui.legacy.preference.DialogPreference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
abstract class TaskPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : DialogPreference(context, attrs), CoroutineScope {
override val coroutineContext = Dispatchers.IO + Job()
protected lateinit var dialog: AlertDialog
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
builder.setTitle(null)
builder.setView(R.layout.preference_dialog_task)
builder.setCancelable(false)
}
abstract fun launchJob()
override fun onDialogCreated(dialog: AlertDialog) {
this.dialog = dialog
launchJob()
}
protected fun showTip(msg: String) {
(context as SettingsActivity).showTip(
msg,
Snackbar.LENGTH_SHORT,
)
}
}
@@ -1,33 +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.preference
import android.content.Context
import android.util.AttributeSet
import androidx.preference.Preference
import com.hippo.ehviewer.BuildConfig
import com.hippo.ehviewer.R
class VersionPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : Preference(context, attrs) {
init {
setTitle(R.string.settings_about_version)
summary = "${BuildConfig.VERSION_NAME} (${BuildConfig.COMMIT_SHA})\n" +
context.getString(R.string.settings_about_build_time, BuildConfig.BUILD_TIME)
}
}
@@ -1,46 +0,0 @@
/*
* Copyright 2018 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.shortcuts;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.hippo.ehviewer.download.DownloadService;
/**
* Created by onlymash on 3/25/18.
*/
public class ShortcutsActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String action;
Intent intent = getIntent();
if (intent != null) {
action = intent.getAction();
if (action != null && (action.equals(DownloadService.ACTION_START_ALL) ||
action.equals(DownloadService.ACTION_STOP_ALL))) {
ContextCompat.startForegroundService(this, new Intent(this, DownloadService.class).setAction(action));
}
}
finish();
}
}
@@ -1,245 +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.ui
import android.Manifest
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import com.hippo.ehviewer.EhApplication.Companion.favouriteStatusRouter
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.download.DownloadService
import com.hippo.ehviewer.ui.legacy.ListCheckBoxDialogBuilder
import com.hippo.ehviewer.ui.scene.BaseScene
import com.hippo.ehviewer.util.requestPermission
import com.hippo.ehviewer.yorozuya.collect.LongList
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.resume
import com.hippo.ehviewer.download.DownloadManager as downloadManager
object CommonOperations {
fun startDownload(activity: MainActivity?, galleryInfo: GalleryInfo, forceDefault: Boolean) {
startDownload(activity!!, listOf(galleryInfo), forceDefault)
}
fun startDownload(
activity: MainActivity,
galleryInfos: List<GalleryInfo>,
forceDefault: Boolean,
) {
launchNow {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
activity.requestPermission(Manifest.permission.POST_NOTIFICATIONS)
}
doStartDownload(activity, galleryInfos, forceDefault)
}
}
private fun doStartDownload(
activity: MainActivity,
galleryInfos: List<GalleryInfo>,
forceDefault: Boolean,
) {
val dm = downloadManager
val toStart = LongList()
val toAdd: MutableList<GalleryInfo> = ArrayList()
for (gi in galleryInfos) {
if (dm.containDownloadInfo(gi.gid)) {
toStart.add(gi.gid)
} else {
toAdd.add(gi)
}
}
if (!toStart.isEmpty()) {
val intent = Intent(activity, DownloadService::class.java)
intent.action = DownloadService.ACTION_START_RANGE
intent.putExtra(DownloadService.KEY_GID_LIST, toStart)
ContextCompat.startForegroundService(activity, intent)
}
if (toAdd.isEmpty()) {
activity.showTip(R.string.added_to_download_list, BaseScene.LENGTH_SHORT)
return
}
var justStart = forceDefault
var label: String? = null
// Get default download label
if (!justStart && Settings.hasDefaultDownloadLabel) {
label = Settings.defaultDownloadLabel
justStart = label == null || dm.containLabel(label)
}
// If there is no other label, just use null label
if (!justStart && dm.labelList.isEmpty()) {
justStart = true
label = null
}
if (justStart) {
// Got default label
for (gi in toAdd) {
val intent = Intent(activity, DownloadService::class.java)
intent.action = DownloadService.ACTION_START
intent.putExtra(DownloadService.KEY_LABEL, label)
intent.putExtra(DownloadService.KEY_GALLERY_INFO, gi)
ContextCompat.startForegroundService(activity, intent)
}
// Notify
activity.showTip(R.string.added_to_download_list, BaseScene.LENGTH_SHORT)
} else {
// Let use chose label
val list = dm.labelList
val items = mutableListOf<String>()
items.add(activity.getString(R.string.default_download_label_name))
items.addAll(list.mapNotNull { it.label })
ListCheckBoxDialogBuilder(
activity,
items,
{ builder: ListCheckBoxDialogBuilder?, _: AlertDialog?, position: Int ->
var label1: String?
if (position == 0) {
label1 = null
} else {
label1 = items[position]
if (!dm.containLabel(label1)) {
label1 = null
}
}
// Start download
for (gi in toAdd) {
val intent = Intent(activity, DownloadService::class.java)
intent.action = DownloadService.ACTION_START
intent.putExtra(DownloadService.KEY_LABEL, label1)
intent.putExtra(DownloadService.KEY_GALLERY_INFO, gi)
ContextCompat.startForegroundService(activity, intent)
}
// Save settings
if (builder?.isChecked == true) {
Settings.putHasDefaultDownloadLabel(true)
Settings.putDefaultDownloadLabel(label1)
} else {
Settings.putHasDefaultDownloadLabel(false)
}
// Notify
activity.showTip(R.string.added_to_download_list, BaseScene.LENGTH_SHORT)
},
activity.getString(R.string.remember_download_label),
false,
)
.setTitle(R.string.download)
.show()
}
}
}
private fun removeNoMediaFile(downloadDir: UniFile) {
val noMedia = downloadDir.subFile(".nomedia") ?: return
noMedia.delete()
}
private fun ensureNoMediaFile(downloadDir: UniFile) {
downloadDir.createFile(".nomedia") ?: return
}
private val lck = Mutex()
suspend fun keepNoMediaFileStatus() {
lck.withLock {
val downloadLocation = Settings.downloadLocation ?: return
if (Settings.mediaScan) {
removeNoMediaFile(downloadLocation)
} else {
ensureNoMediaFile(downloadLocation)
}
}
}
suspend fun Context.addToFavorites(galleryInfo: GalleryInfo, select: Boolean = false) {
val slot = Settings.defaultFavSlot
val localFav = getString(R.string.local_favorites)
val items = Settings.favCat.toMutableList().apply { add(0, localFav) }
suspend fun doAddToFavorites(slot: Int, newFavoriteName: String?) {
when (slot) {
-1 -> {
EhDB.putLocalFavorites(galleryInfo)
}
in 0..9 -> {
EhEngine.addFavorites(galleryInfo.gid, galleryInfo.token, slot, "")
}
else -> {
throw EhException("Invalid favslot!")
}
}
galleryInfo.favoriteName = newFavoriteName
galleryInfo.favoriteSlot = slot
withUIContext {
favouriteStatusRouter.modifyFavourites(galleryInfo.gid, slot)
}
}
if (!select && slot >= -1 && slot <= 9) {
val newFavoriteName = if (slot >= 0) items[slot + 1] else null
doAddToFavorites(slot, newFavoriteName)
} else {
val (slot1, checked) = withUIContext {
suspendCancellableCoroutine { cont ->
ListCheckBoxDialogBuilder(
this@addToFavorites,
items,
{ b, _, position -> cont.resume(position - 1 to b.isChecked) },
getString(R.string.remember_favorite_collection),
false,
).setTitle(R.string.add_favorites_dialog_title).setOnCancelListener { cont.cancel() }.show()
}
}
val newFavoriteName = if (slot1 in 0..9) items[slot1 + 1] else null
doAddToFavorites(slot1, newFavoriteName)
if (checked) {
Settings.putDefaultFavSlot(slot1)
} else {
Settings.putDefaultFavSlot(Settings.INVALID_DEFAULT_FAV_SLOT)
}
}
}
suspend fun removeFromFavorites(galleryInfo: GalleryInfo) {
EhDB.removeLocalFavorites(galleryInfo.gid)
EhEngine.addFavorites(galleryInfo.gid, galleryInfo.token, -1, "")
galleryInfo.favoriteName = null
galleryInfo.favoriteSlot = -2
withUIContext {
favouriteStatusRouter.modifyFavourites(galleryInfo.gid, -2)
}
}
fun Context.navToReader(info: GalleryInfo, page: Int) {
val intent = Intent(this, ReaderActivity::class.java)
intent.action = ReaderActivity.ACTION_EH
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, info)
intent.putExtra(ReaderActivity.KEY_PAGE, page)
startActivity(intent)
}
@@ -1,146 +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.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.thumbUrl
import com.hippo.ehviewer.ui.login.LocalNavController
import com.hippo.ehviewer.util.tellClipboardWithToast
private const val INDEX_URL = 2
private const val INDEX_PARENT = 9
private val keys = arrayOf(
R.string.key_gid,
R.string.key_token,
R.string.key_url,
R.string.key_title,
R.string.key_title_jpn,
R.string.key_thumb,
R.string.key_category,
R.string.key_uploader,
R.string.key_posted,
R.string.key_parent,
R.string.key_visible,
R.string.key_language,
R.string.key_pages,
R.string.key_size,
R.string.key_favorite_count,
R.string.key_favorited,
R.string.key_rating_count,
R.string.key_rating,
R.string.key_torrents,
R.string.key_torrent_url,
R.string.favorite_name,
)
@Composable
fun GalleryInfoBottomSheet(
galleryDetail: GalleryDetail,
onDismissRequest: () -> Unit,
) {
val data = remember(galleryDetail) {
galleryDetail.run {
arrayOf(
gid.toString(),
token,
EhUrl.getGalleryDetailUrl(gid, token),
title,
titleJpn,
thumbUrl,
EhUtils.getCategory(category),
uploader,
posted,
parent,
visible,
language,
pages.toString(),
size,
favoriteCount.toString(),
isFavorited.toString(),
ratingCount.toString(),
rating.toString(),
torrentCount.toString(),
torrentUrl,
favoriteName,
)
}
}
ModalBottomSheet(
onDismissRequest = onDismissRequest,
windowInsets = WindowInsets(0),
) {
check(data.size == keys.size)
val context = LocalContext.current
val navController = LocalNavController.current
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
Text(text = stringResource(id = R.string.gallery_info), style = MaterialTheme.typography.titleLarge)
}
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.labelLarge) {
LazyColumn {
itemsIndexed(keys) { index, i ->
val key = stringResource(i)
Row(
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.keyline_margin)).clickable {
if (index == INDEX_PARENT) {
data[index]?.let { navController.navWithUrl(it) }
} else {
context tellClipboardWithToast data[index]
if (index == INDEX_URL) {
// Save it to avoid detect the gallery
Settings.putClipboardTextHashCode(data[index].hashCode())
}
}
}.fillMaxWidth(),
) {
Text(key, modifier = Modifier.width(90.dp).padding(8.dp))
Text(data[index].orEmpty(), modifier = Modifier.padding(8.dp))
}
}
}
}
}
}
@@ -1,120 +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.ui
import android.os.Bundle
import android.view.MenuItem
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.lifecycleScope
import androidx.navigation.ActivityNavigator
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.parser.HomeParser
import com.hippo.ehviewer.ui.fragment.SettingsFragment
import com.hippo.ehviewer.ui.scene.BaseScene
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
class SettingsActivity : EhActivity() {
private var mFab: FloatingActionButton? = null
private var mContentText: TextView? = null
private var mAppbarLayout: AppBarLayout? = null
var mLimits: HomeParser.Limits? = null
private set
var mFunds: HomeParser.Funds? = null
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (EhCookieStore.hasSignedIn()) {
lifecycleScope.launchIO {
runCatching {
getImageLimits()
}.exceptionOrNull()?.printStackTrace()
}
}
setContentView(R.layout.activity_preference)
setSupportActionBar(findViewById(R.id.toolbar))
mContentText = findViewById(R.id.tip)
mFab = findViewById(R.id.fab)
mAppbarLayout = findViewById(R.id.appbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
.replace(R.id.fragment, SettingsFragment())
.commitAllowingStateLoss()
}
}
suspend fun getImageLimits() = EhEngine.getImageLimits().run {
mLimits = limits
mFunds = funds
}
suspend fun resetImageLimits() {
mLimits =
EhEngine.resetImageLimits() ?: HomeParser.Limits(maximum = mLimits!!.maximum).also {
withUIContext {
showTip(R.string.reset_limits_succeed, BaseScene.LENGTH_SHORT)
}
}
}
fun showTip(@StringRes id: Int, length: Int) {
showTip(getString(id), length)
}
fun showTip(message: CharSequence?, length: Int) {
Snackbar.make(
findViewById(R.id.snackbar),
message!!,
if (length == BaseScene.LENGTH_LONG) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT,
).show()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressedDispatcher.onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
fun getMFab(): FloatingActionButton {
return mFab!!
}
fun getMContentText(): TextView {
return mContentText!!
}
fun resetAppbarLiftStatus() {
mAppbarLayout?.setExpanded(true)
}
override fun finish() {
super.finish()
ActivityNavigator.applyPopAnimationsToPendingTransition(this)
}
}
@@ -1,106 +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.ui
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.annotation.MainThread
import androidx.browser.customtabs.CustomTabsIntent
import androidx.navigation.NavController
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
import com.hippo.ehviewer.client.parser.GalleryListUrlParser
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
import com.hippo.ehviewer.ui.scene.GalleryDetailScene
import com.hippo.ehviewer.ui.scene.GalleryListScene
import com.hippo.ehviewer.ui.scene.ProgressScene
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
private val intent = CustomTabsIntent.Builder().apply { setShowTitle(true) }.build()
fun Context.openBrowser(url: String) {
if (url.isEmpty()) return
try {
intent.launchUrl(this, Uri.parse(url))
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, R.string.no_browser_installed, Toast.LENGTH_LONG).show()
}
}
@MainThread
fun Context.jumpToReaderByPage(url: String, detail: GalleryDetail): Boolean {
fun jump(page: Int) {
Intent(this, ReaderActivity::class.java).apply {
action = ReaderActivity.ACTION_EH
putExtra(ReaderActivity.KEY_GALLERY_INFO, detail)
putExtra(ReaderActivity.KEY_PAGE, page)
startActivity(this)
}
}
GalleryPageUrlParser.parse(url)?.let {
if (it.gid == detail.gid) {
jump(it.page)
return true
}
}
if (url.startsWith("#c")) {
runCatching {
jump(url.replace("#c", "").toInt() - 1)
return true
}
}
return false
}
@MainThread
fun NavController.navWithUrl(url: String): Boolean {
if (url.isEmpty()) return false
GalleryListUrlParser.parse(url)?.let { lub ->
Bundle().apply {
putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_LIST_URL_BUILDER)
putParcelable(GalleryListScene.KEY_LIST_URL_BUILDER, lub)
navigate(R.id.galleryListScene, this)
}
return true
}
GalleryDetailUrlParser.parse(url)?.apply {
Bundle().apply {
putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN)
putLong(GalleryDetailScene.KEY_GID, gid)
putString(GalleryDetailScene.KEY_TOKEN, token)
navigate(R.id.galleryDetailScene, this)
}
return true
}
GalleryPageUrlParser.parse(url)?.apply {
Bundle().apply {
putString(ProgressScene.KEY_ACTION, ProgressScene.ACTION_GALLERY_TOKEN)
putLong(ProgressScene.KEY_GID, gid)
putString(ProgressScene.KEY_PTOKEN, pToken)
putInt(ProgressScene.KEY_PAGE, page)
navigate(R.id.progressScene, this)
}
return true
}
return false
}
@@ -1,18 +0,0 @@
package com.hippo.ehviewer.ui.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
@Composable
fun Deferred(block: suspend () -> Unit, content: @Composable () -> Unit) {
var completed by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(key1 = Unit) {
block()
completed = true
}
if (completed) content()
}
@@ -1,104 +0,0 @@
package com.hippo.ehviewer.ui.compose
import androidx.annotation.StringRes
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
interface DialogScope<R> {
infix fun shouldReturn(value: R)
}
class DialogState {
private var content: (@Composable () -> Unit)? by mutableStateOf(null)
@Composable
fun Handler() = content?.invoke()
fun dismiss() {
content = null
}
suspend fun <R> show(initial: R, @StringRes title: Int? = null, block: @Composable DialogScope<R>.() -> Unit): R {
return suspendCancellableCoroutine { cont ->
cont.invokeOnCancellation { dismiss() }
content = {
var result by remember { mutableStateOf(initial) }
AlertDialog(
onDismissRequest = {
cont.cancel()
dismiss()
},
confirmButton = {
TextButton(onClick = {
dismiss()
cont.resume(result)
}) {
Text(text = stringResource(id = android.R.string.ok))
}
},
title = title?.let { { Text(text = stringResource(id = title)) } },
text = {
block(object : DialogScope<R> {
override infix fun shouldReturn(value: R) {
result = value
}
})
},
)
}
}
}
suspend fun show(
@StringRes confirmText: Int? = null,
@StringRes dismissText: Int? = null,
@StringRes title: Int? = null,
text: (@Composable () -> Unit)? = null,
): Boolean {
return suspendCancellableCoroutine { cont ->
cont.invokeOnCancellation { dismiss() }
content = {
AlertDialog(
onDismissRequest = {
dismiss()
cont.resume(false)
},
confirmButton = {
TextButton(onClick = {
dismiss()
cont.resume(true)
}) {
Text(text = stringResource(id = confirmText ?: android.R.string.ok))
}
},
dismissButton = dismissText?.let {
{
TextButton(onClick = {
dismiss()
cont.resume(false)
}) {
Text(text = stringResource(id = dismissText))
}
}
},
title = title?.let { { Text(text = stringResource(id = title)) } },
text = text,
)
}
}
}
}
@Composable
fun rememberDialogState(): DialogState {
return remember { DialogState() }
}
@@ -1,56 +0,0 @@
package com.hippo.ehviewer.ui.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
private val remembered = hashMapOf<String, Any>()
// This works mostly like [rememberSaveable], but saves data in global hashmap, to avoid [TransactionTooLargeException]
@Composable
fun <T : Any> rememberMemorized(
vararg inputs: Any?,
key: String? = null,
init: () -> T,
): T {
// key is the one provided by the user or the one generated by the compose runtime
val finalKey = if (!key.isNullOrEmpty()) {
key
} else {
// currentCompositeKeyHash is unreliable here
// Is this Composable inlined or its groups elided?
// Or a compose compiler bug?
init.javaClass.toString()
}
val registry = LocalSaveableStateRegistry.current
// value is restored using the registry or created via [init] lambda
val value = remember(*inputs) {
@Suppress("UNCHECKED_CAST")
val restored = registry?.consumeRestored(finalKey)?.let {
remembered.remove(it as String)
} as T?
restored ?: init()
}
// re-register if the registry or key has been changed
if (registry != null) {
// we want to use the latest instances of saver and value in the valueProvider lambda
// without restarting DisposableEffect as it would cause re-registering the provider in
// the different order. so we use rememberUpdatedState.
val valueState = rememberUpdatedState(value)
DisposableEffect(registry, finalKey) {
val valueProvider = {
finalKey.also { remembered[it] = valueState.value }
}
val entry = registry.registerProvider(finalKey, valueProvider)
onDispose {
entry.unregister()
}
}
}
return value
}
Loaded 100 of 651 files, more files were not shown because too many files have changed in this diff. Show more