Compare commits

..
Author SHA1 Message Date
tarsin ce008f5576 Bump Version
Releases / Build (push) Has been cancelled
2023-01-06 08:49:47 +08:00
tarsin 25f7e8b35c Always show toast in BottomSheet mode, Resolve #633 2023-01-06 08:47:37 +08:00
tarsin 109398d955 Fix enter Reader from previews 2023-01-06 08:38:57 +08:00
tarsin 4a8551087b Fix crash when open history 2023-01-06 08:15:42 +08:00
tarsin 34febe3545 Fix crash when open download 2023-01-06 08:09:11 +08:00
tarsin 53a0307a8e Bump Versions
Releases / Build (push) Has been cancelled
2023-01-05 21:42:34 +08:00
tarsin 1c6f624fd1 Kotlinize EhExceptions 2023-01-05 21:21:47 +08:00
tarsin 780e5f08f9 Rename .java to .kt 2023-01-05 21:21:47 +08:00
tarsin 1bb73b1147 Kotlinize 2023-01-05 21:15:57 +08:00
tarsin 09b0d4d6c4 Rename .java to .kt 2023-01-05 21:15:57 +08:00
tarsin 7e3856fa05 Refine dao nullability 2023-01-05 21:10:14 +08:00
tarsin fbcb3f5436 Get download manager earlier, #629 2023-01-05 19:44:56 +08:00
tarsin 324b4ced06 Convert DAO to kotlin, step 3, and use ksp 2023-01-05 19:15:19 +08:00
tarsin 79c2abe790 Rename .java to .kt 2023-01-05 19:15:19 +08:00
tarsin cdab9168fc Convert DAO to kotlin, step 2 2023-01-05 19:01:35 +08:00
tarsin ce272724de Rename .java to .kt 2023-01-05 19:01:35 +08:00
tarsin 2fa0a19d3b Convert DAO to kotlin, step 1 2023-01-05 18:45:45 +08:00
tarsin 38b9a6e04a Rename .java to .kt 2023-01-05 18:45:45 +08:00
tarsin 22d3c2b6bf Migrate to Parcelize step 2 2023-01-05 17:42:07 +08:00
tarsin 218a8397c4 Rename .java to .kt 2023-01-05 17:42:07 +08:00
tarsin fd990da71e Fixup! Execute request in fragment's lifecycle coroutine scope, Resolve #628 2023-01-05 15:52:23 +08:00
小白-白 4a70c89a4d Restore download dir to avoid redownload
Resolve #547
2023-01-05 15:19:01 +08:00
小白-白 7e3143678f Drop clear download path cache
Useless and will cause redownload
2023-01-05 15:19:01 +08:00
renovate[bot] d80e09dfba Update plugin com.android.application to v8.0.0-alpha11 2023-01-04 23:13:36 +00:00
tarsin 3b3b251c21 Reformat code 2023-01-04 17:04:12 +08:00
tarsin 1e848bd70f Refine request api 2023-01-04 16:52:47 +08:00
tarsin fab2295445 Reorganize request code and fix bugs 2023-01-04 16:24:59 +08:00
tarsin a913bccc9e Rename .java to .kt 2023-01-04 16:24:59 +08:00
tarsin 57e7f89814 Execute request in fragment's lifecycle coroutine scope 2023-01-04 15:02:16 +08:00
tarsin f98fd5e5f1 Handle Call cancellation internally 2023-01-04 14:31:40 +08:00
tarsin e6af2e5c6b Make EhEngine task well cancelled in coroutine 2023-01-04 13:43:51 +08:00
tarsin d219989ec7 Rename .java to .kt 2023-01-04 13:43:51 +08:00
小白-白 b9daf608ff Add more info for torrent download 2023-01-04 12:26:46 +08:00
tarsin 869da61fd0 Use coroutine CancellationException 2023-01-04 12:26:09 +08:00
tarsin 9f79e2505c Coroutinize EhClient (#622)
* Rename .java to .kt

* Coroutinize EhClient to improve performance and clarity

* Nullability Compatibility

* Refine control flow

* Fix nullability
2023-01-04 11:51:31 +08:00
tarsin 3000b36dbd Exclude unused dependency 2023-01-03 16:23:03 +08:00
tarsin 1d21b0e5c4 Use ktx dependency 2023-01-03 15:56:59 +08:00
tarsin 8d6b0926d6 Convert to kotlin-parcelize 2023-01-03 15:33:04 +08:00
tarsin 4c39d13c80 Rename .java to .kt 2023-01-03 15:33:04 +08:00
tarsin 327dd1c73f Minor Refactor 2023-01-03 12:29:39 +08:00
tarsin b36b8f3234 Rename .java to .kt 2023-01-03 12:29:39 +08:00
FooIbar 5f6da284fb fixup! Create download dir only in download mode 2023-01-03 11:12:28 +08:00
tarsin 83d27e18cb Migrate to kotlin-parcelize 2023-01-03 09:56:37 +08:00
FooIbar e291bd58a8 Check if ehTags is initialized 2023-01-03 08:56:30 +08:00
FooIbar 23acdb6492 Support non-H@H archive download 2023-01-03 08:53:25 +08:00
FooIbar 3b5e56b049 Create download dir only in download mode
Do not try to find download dir, should use restore download instead
2023-01-02 22:10:27 +08:00
tarsin 57d4967f2a Implement #612 2023-01-02 21:39:46 +08:00
tarsin 80fcda149b Resolve #615 2023-01-02 21:03:47 +08:00
o0kam1 1a86eef52c add copy image 2023-01-02 20:45:24 +08:00
FooIbar 98d401f9c3 Fix callback order 2023-01-02 15:31:25 +08:00
FooIbar 0f2fae886e fixup! Set toolbar text when exiting search view 2023-01-02 15:31:25 +08:00
tarsin f30a9093af Remove databinding runtime
It is introduced when we migrated to tachiyomi reader
Seems tachiyomi's other parts uses databinding but not its reader
2023-01-02 13:26:03 +08:00
tarsin b4334da202 Fixup! Convert all kotlin fragment to viewbinding 2023-01-02 13:17:47 +08:00
tarsin 416fb3bda9 Fixup! Convert all kotlin fragment to databinding 2023-01-02 12:52:54 +08:00
tarsin 7e0fc2e364 Convert all kotlin fragment to databinding 2023-01-02 12:46:48 +08:00
tarsin 540d290be2 Convert HistoryScene to databinding 2023-01-02 11:32:55 +08:00
小白-白 53974ad88d Don't cancel task if mData is empty
Resolve #587
2023-01-02 11:18:58 +08:00
tarsin cd9b5e5a44 Simplify onNavigationClick 2023-01-02 10:56:20 +08:00
tarsin d9959bbcb2 Remove onBackPressed support on scene 2023-01-02 10:47:41 +08:00
tarsin d7dfd53244 Handle GalleryListScene SearchMode OnBackPressedCallback 2023-01-02 10:29:01 +08:00
tarsin 84489bf61b Handle GalleryCommentsScene EditPanel OnBackPressedCallback 2023-01-02 10:16:20 +08:00
tarsin 3fdb56390e Use colorAccent for Filled heart button 2023-01-02 09:44:22 +08:00
tarsin a34e766f70 Fix drawer OnBackPressedCallback 2023-01-02 09:35:53 +08:00
FooIbar d74023d4fa Resolve #605 2023-01-02 08:44:31 +08:00
FooIbar 41572e98ba Resolve #604 2023-01-02 08:44:31 +08:00
FooIbar 063087c87e Add horizontal clip spacing to avoid layout change 2023-01-02 08:44:31 +08:00
FooIbar 4a5d53d713 Set toolbar text when exiting search view 2023-01-02 08:44:31 +08:00
tarsin 9a262525ec Revert "Exit activity directly when back-pressed"
This reverts commit e327b4e84b.
2023-01-01 17:53:25 +08:00
tarsin e327b4e84b Exit activity directly when back-pressed 2023-01-01 17:26:39 +08:00
tarsin c128a2b518 Fix preference flow being cancelled 2023-01-01 17:12:05 +08:00
tarsin a3263ab4af Avoid unnecessary coroutine context switch 2023-01-01 16:58:34 +08:00
tarsin 4a4efe4725 Convert All activity to kotlin 2023-01-01 16:11:28 +08:00
tarsin 89d2433369 Rename .java to .kt 2023-01-01 16:11:28 +08:00
tarsin 023175ce6d Fixup convert to kotlin 2023-01-01 15:34:06 +08:00
FooIbar 21c8d24d57 Add size and cost for archive download 2023-01-01 15:27:22 +08:00
tarsin e49f5c95f4 Handle fablayout expand backpress on its callback 2023-01-01 15:26:32 +08:00
tarsin 6912e578ca Convert fabLayout to kotlin and refine nullability 2023-01-01 14:36:17 +08:00
tarsin 8680cff2b2 Rename .java to .kt 2023-01-01 14:36:17 +08:00
tarsin aea75d44e3 Happy New Year 2023!
Releases / Build (push) Has been cancelled
2023-01-01 08:38:50 +08:00
tarsin 80481f1778 Update copyleft year 2023-01-01 08:37:32 +08:00
FooIbar 3ea7ff8202 Resolve #598 2022-12-31 19:37:33 +08:00
小白-白 b5979c0528 Fix comments covered by fab 2022-12-31 16:47:02 +08:00
小白-白 f0f79a52b8 Support filter comments
Resolve #516
2022-12-31 16:47:02 +08:00
小白-白 b549acc1de Add page number limit 2022-12-31 16:47:02 +08:00
小白-白 2505cc5d00 Don't show progress bar on last page 2022-12-31 16:47:02 +08:00
小白-白 d5af66b3fc Fix search bar set selection 2022-12-31 16:47:02 +08:00
小白-白 f680dc7e46 Fix copy trans 2022-12-31 16:47:02 +08:00
tarsin ffd8bbb1ce Refine missing comments 2022-12-31 12:38:50 +08:00
tarsin 216a043094 Set bar style correctly 2022-12-31 12:35:20 +08:00
tarsin 021eb5f828 Fix 3-button navigation 2022-12-31 12:27:47 +08:00
tarsin f3bf8849e9 Cleanup unused res 2022-12-31 11:48:58 +08:00
tarsin 128f7100d7 Minor cleanup 2022-12-31 11:19:34 +08:00
FooIbar 2cf53dd00f Disable landscape zoom by default 2022-12-30 17:27:01 +08:00
tarsin 4a674e46e6 Fixup! Handle drawer on back-pressed 2022-12-30 16:40:46 +08:00
tarsin f6b0134fdb Remove redundant comment 2022-12-30 14:33:20 +08:00
tarsin 675a6b863e Handle searchview backpress in separated callback 2022-12-30 13:43:39 +08:00
tarsin b570eeef93 Don't allow open drawer in search mode 2022-12-30 13:12:45 +08:00
tarsin 71a8b4e607 Handle drawer on back-pressed 2022-12-30 12:35:19 +08:00
renovate[bot] 89b866b0fc Update dependency gradle to v8.0-rc-1 2022-12-29 18:34:17 +00:00
tarsin bcce20d9ac Remove unnecessary okhttpclient rebuild 2022-12-29 21:18:47 +08:00
tarsin 420e760fb3 fixup! Convert spiderInfo cache to Coil DiskCache 2022-12-29 20:38:48 +08:00
FooIbar a168c95d79 fixup! Refactor getPageData 2022-12-29 20:28:16 +08:00
tarsin d8ccec45f9 Convert spiderInfo cache to Coil DiskCache 2022-12-29 20:27:33 +08:00
renovate[bot] 1316875602 Update plugin com.google.devtools.ksp to v1.8.0-1.0.8 2022-12-29 09:05:19 +08:00
FooIbar a85387c7c8 Add edit button for download label 2022-12-28 22:20:53 +08:00
renovate[bot] d357e83d06 Update plugin org.jetbrains.kotlin.android to v1.8.0 2022-12-28 14:17:07 +00:00
FooIbar 51546f6fea Add option for saving progress in quick search 2022-12-28 16:32:05 +08:00
tarsin b4aa5ec593 Revert "Get download dir in download mode only"
This reverts commit 241d9daae9.
2022-12-28 12:20:49 +08:00
tarsin 909380f592 Remove unused dirloader 2022-12-28 12:09:13 +08:00
tarsin d5f26643bf Support wide-color-gamut as config 2022-12-28 11:34:17 +08:00
tarsin a15dc4afdf Minor cleanup image decode codes 2022-12-28 09:57:50 +08:00
FooIbar b317bc334e Store GID in quick search name
Resolve #447
2022-12-27 15:44:02 +08:00
FooIbar d0a8d08b26 fixup! Support jump to last page 2022-12-27 15:43:01 +08:00
FooIbar f73a922a8e Cancel current task before saving instance state
Resolve #210
2022-12-26 20:33:19 +08:00
tarsin c6682ce718 Fix SwipeRefreshLayout theme 2022-12-26 19:01:35 +08:00
tarsin 764d1137ae Fixup sync with tachiyomi res 2022-12-26 17:29:02 +08:00
tarsin 02fc972a79 Remove confusing hint 2022-12-26 17:11:55 +08:00
tarsin 7fc727cf2e Resolve #579 2022-12-26 17:05:17 +08:00
FooIbar d77e1e6820 Remove redundant context parameter 2022-12-26 16:38:22 +08:00
FooIbar 241d9daae9 Get download dir in download mode only
Resolve? #381
2022-12-26 16:38:22 +08:00
FooIbar 462e0257a9 Save images to download location by default 2022-12-26 16:37:38 +08:00
renovate[bot] 66fbc7e821 Update dependency com.squareup.okhttp3:okhttp-bom to v5.0.0-alpha.11 2022-12-24 22:23:31 +00:00
FooIbar 3c37f22027 Use getBindingAdapterPosition() to get item position
Better solution for #201
2022-12-24 16:52:37 +08:00
tarsin 0d24bc6602 Thou shalt not neutralize nullability check 2022-12-24 12:20:55 +08:00
tarsin 5a50df8179 Upgrade dependencies 2022-12-24 11:53:58 +08:00
tarsin f1251fd150 Remove gradle properties enabled by default 2022-12-24 11:52:05 +08:00
tarsin 16202efc7b Allow obfuscate settings 2022-12-24 10:35:37 +08:00
tarsin 26c6052286 Rename .java to .kt 2022-12-24 10:35:37 +08:00
tarsin 7ce386a7b8 Cleanup OpenGL usage
Our bitmap/hwui may use vulkan ctx
2022-12-24 09:42:16 +08:00
tarsin e43cd0a039 Minor refactor 2022-12-23 19:12:39 +08:00
tarsin 70be405032 Free native source in advance if image is not Animated 2022-12-23 18:58:03 +08:00
tarsin 9f3ad8142a Remove largeHeap flag
Now that pixels are stored in GraphicBuffer, we no longer need large dalvik or native heap
2022-12-23 18:52:48 +08:00
FooIbar 51d9ece30c Init readingModeSettings before showing
Resolve #539
2022-12-22 21:45:07 +08:00
FooIbar d8e79f879f Fix missing space in copyright 2022-12-22 21:45:07 +08:00
FooIbar 76d50b56ee Show progress on goTo 2022-12-22 13:37:34 +08:00
renovate[bot] c8d1e957a1 Update plugin com.android.application to v8.0.0-alpha10 2022-12-22 03:14:33 +00:00
FooIbar b287af4824 Refactor getPageData 2022-12-21 18:28:52 +08:00
FooIbar 416ef8d6d7 fixup! Remove unused fab 2022-12-21 18:28:52 +08:00
renovate[bot] 7c71abb985 Update plugin com.google.devtools.ksp to v1.8.0-RC2-1.0.8 2022-12-21 11:37:05 +08:00
renovate[bot] 900f2462a7 Update plugin org.jetbrains.kotlin.android to v1.8.0-RC2 2022-12-20 18:56:50 +00:00
txyyh dd982c6cb9 Release 1.8.5.1
Releases / Build (push) Has been cancelled
2022-12-20 14:01:10 +00:00
tarsin 927a011c9d Upload mapping for release as well 2022-12-20 21:58:50 +08:00
tarsin ad0ad47a40 Support RTL Slider
But Idk whether we really support RTL :(
2022-12-20 21:30:20 +08:00
tarsin 71a424c15d Remove unused fab 2022-12-20 21:20:42 +08:00
FooIbar 145b7049a8 Support jump to last page
Resolve #466
2022-12-20 21:03:43 +08:00
tarsin 323d258bc2 Fix RTL load 2022-12-20 20:57:45 +08:00
tarsin 56ba52d7b9 Do not recycle bitmap in pager, add back lrucache
TODO: use coil instead
2022-12-20 20:43:15 +08:00
tarsin 54bd6f6ed8 Fix memleak for archive 2022-12-20 19:58:40 +08:00
tarsin f06167ba4b Sync with tachiyomi, support load progress 2022-12-20 19:03:57 +08:00
renovate[bot] 60438ae1ed Update dependency gradle to v8.0-milestone-6 (#562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-20 17:23:02 +08:00
FooIbar 1b87750b87 fixup! Fix isTranslatable (#555)
* Use appcompat to auto store locales

* fixup! Fix isTranslatable #553

* Code cleanup
2022-12-20 08:36:55 +08:00
FooIbar 40cdc566c8 Upload mapping files in ci 2022-12-20 08:33:56 +08:00
txyyh 428ae07e7f Release 1.8.5.0
Releases / Build (push) Has been cancelled
2022-12-19 14:59:35 +00:00
tarsin 481ad22b53 Code cleanup 2022-12-19 13:00:06 +08:00
FooIbar 65f58b93ed Update mCurrentIndex on page move
Resolve #538
2022-12-19 12:09:31 +08:00
FooIbar 45411fa14d Use notifyAll() instead of notify() on mDecodeRequestQueue
Resolve #549
2022-12-19 08:13:31 +08:00
tarsin 8ba1ba4fb1 fixup! Minor Refactor 2022-12-18 14:06:55 +08:00
tarsin 8427e1b3a5 fixup! Minor Refactor 2022-12-18 13:55:16 +08:00
tarsin 32f0f2a0cf Fix isTranslatable 2022-12-18 13:02:12 +08:00
tarsin 8b94997cde Minor Refactor 2022-12-18 12:52:30 +08:00
tarsin bd2d54f774 fixup! Rewrite search suggestion with kotlin flow, remove suggestion limit 2022-12-18 11:31:49 +08:00
FooIbar 34be285510 Do not add neighbors on page success
Might resolve #536 #540
2022-12-18 09:13:38 +08:00
FooIbar 7b906f3c3e Update tag database format 2022-12-18 07:55:37 +08:00
tarsin 82ba22cda2 Resolve #542 2022-12-17 14:24:24 +08:00
tarsin b1a9601ae9 fixup! Rewrite search suggestion with kotlin flow, remove suggestion limit 2022-12-17 13:08:08 +08:00
tarsin 7c1a297a99 Remove unused shared libs 2022-12-17 12:59:02 +08:00
FooIbar a8195c0e7c Map archive index with filename order
Try to resolve #427
2022-12-17 12:53:13 +08:00
FooIbar 08713e3922 Use SummaryProvider instead of String formatting marker 2022-12-17 12:53:13 +08:00
tarsin 0485d52e3e Rewrite search suggestion with kotlin flow, remove suggestion limit 2022-12-17 12:47:37 +08:00
tarsin 8b1723e19b Rename .java to .kt 2022-12-17 12:47:37 +08:00
tarsin 9cb683dc55 Fix crash when zoomed and try to swipe to next page 2022-12-16 22:07:09 +08:00
tarsin dce1589c19 Fix remaining request not cancelled for detached image 2022-12-16 21:35:28 +08:00
FooIbar 107cf16d36 Remove unused maven repo due to a677d95 2022-12-16 03:50:35 +08:00
FooIbar 2bab97c829 Better way to handle tag translation flag 2022-12-16 03:50:35 +08:00
tarsin 5242d825c2 Change location 2022-12-15 11:43:42 +08:00
FooIbar 11d3799b03 Change tag database format to json for better maintainability 2022-12-15 11:38:32 +08:00
FooIbar 55c7f6b896 Support tag suggestion for all languages 2022-12-15 06:43:50 +08:00
tarsin a09d607177 Do colorspace trans if needed 2022-12-14 17:24:19 +08:00
txyyh 626a101208 Rename Repository 2022-12-14 07:39:04 +00:00
tarsin f4ca3acbf6 Fix statusbar 2022-12-14 14:32:40 +08:00
tarsin 86835a3bd7 Remove toolbar and fix theme 2022-12-14 14:26:15 +08:00
tarsin 0ef46e2e11 Move image blobs to another repo 2022-12-14 12:24:17 +08:00
tarsin 04878447b7 Fixup translation import 2022-12-14 12:07:35 +08:00
tarsin 3669d5d90e Resolve #527 2022-12-14 11:25:06 +08:00
tarsin ee5e222a13 Resolve #525 2022-12-14 11:11:20 +08:00
tarsin 9ea44ff59a Remove blur when adjust color filter 2022-12-14 10:58:32 +08:00
tarsin 0474e2c0f5 Kill read settings in preference, clean up res 2022-12-14 10:39:06 +08:00
tarsin 4a909d4b93 Support readWithVolumeKeys 2022-12-14 10:32:46 +08:00
txyyh 8dbe24ab55 Release 1.8.5.0-preview
Releases / Build (push) Has been cancelled
2022-12-13 14:34:43 +00:00
tarsin 25f7b005cb Mark reader support wideColorGamut 2022-12-13 21:36:26 +08:00
tarsin 23f55ea208 Update gradle wrapper 2022-12-13 21:31:43 +08:00
tarsin 5f19fa0f1f Restore config 2022-12-13 21:18:11 +08:00
tarsin 58fbdcfcdd Sync tachiyomi reader translations 2022-12-13 21:02:54 +08:00
tarsin e33cca9fd9 Reformat code 2022-12-13 20:54:45 +08:00
tarsin b25e3d5454 Remove unused res 2022-12-13 20:51:01 +08:00
tarsin 583f272daa Remove unsupported setting 2022-12-13 20:46:22 +08:00
tarsin 2a83ad63be Inline DirectionalViewPager 2022-12-13 20:29:44 +08:00
tarsin 6fdc908ad1 Support save cursor 2022-12-13 18:48:48 +08:00
tarsin 5213caac49 Drop glgallery (#519)
* Neutralize GlGallery

* Enable dataBinding and viewBinding

* Add tachiyomi reader layout

* Cleanup Dead-code

* Rename .java to .kt

* Fix activity rename

* Nullability changes

* Add basic tachiyomi reader imageviews

* Fix size increase

* Let's cleanup later

* Add some tachiyomi webtoon widgets

* Import tachiyomi webtoon pager without chapter transition

* Remove rx dependency

* Rewrite GalleryProvider interface with flow

* groundwork for image load

* Refine webtoon pageholder

* Force us hw bitmap, refine recycle

* Strip tachiyomi decoder usage

* Keep our own SubsamplingScaleImageView, strip tachiyomi decoder to avoid apk bloated

* Import other PageViewers

* Add webtoon viewer config

* groundFix ReaderActivity

* Add tachiyomi Preferences

* Readpreference

* Fix fullscreen

* Move related class to tachiyomi package

* Fix pageload

* Add readerconfig

* Add ViewerConfig hook

* Add Reader bottomsheet settings

* Fix reader setting apply

* Refine

* Fix save share image

* Refine theme
2022-12-13 15:51:27 +08:00
FooIbar ca0518c747 Verify cookies through getProfile (#523)
Verify cookies through getProfile, Resolve? #283
2022-12-13 09:00:56 +08:00
FooIbar 298cd29537 Disable proxy when domain fronting is enabled (#522) 2022-12-13 08:26:53 +08:00
FooIbar 1a963a3239 Notify next items changed after items inserted (#520)
* Notify next items changed after items inserted, Resolve #201

* Code cleanup
2022-12-12 19:09:05 +08:00
FooIbar 683c8dc5c5 Fix onApplySearch getting called twice 2022-12-12 08:42:21 +08:00
FooIbar 2087970de6 Use MODE for favoriteSlot in HistoryInfo 2022-12-12 01:03:09 +08:00
FooIbar 5bd270fe6a Parse favoriteSlot for GalleryDetail 2022-12-12 01:03:09 +08:00
FooIbar 0992fcfcb0 Fix local favorites search 2022-12-11 20:11:30 +08:00
tarsin fc12395f7e Limit coil threads 2022-12-11 17:21:02 +08:00
tarsin 57b291a4e8 Checkout xz-utils to 5.2.9 2022-12-11 16:54:17 +08:00
tarsin 3adf391478 Checkout libarchive to 3.8.2 2022-12-11 16:49:57 +08:00
tarsin a859941845 Reformat Code 2022-12-11 12:26:22 +08:00
tarsin 8d1fc85638 Fix filter chip 2022-12-11 12:22:31 +08:00
tarsin 51ece1c472 Adjust block ips 2022-12-11 11:59:33 +08:00
tarsin 45f67ec37f Fix webtoon down sampling 2022-12-11 11:57:39 +08:00
FooIbar 85b559c989 Generate simpleLanguage for local favorites 2022-12-11 11:50:26 +08:00
FooIbar 1382050b5f Request UConfig on login 2022-12-11 11:50:26 +08:00
FooIbar fb970752fd Remove unused parameter 2022-12-11 11:50:26 +08:00
FooIbar 3d256a402f Auto close response to avoid resource leak 2022-12-11 11:50:26 +08:00
tarsin a677d95d34 Use mca 1.8.0beta01 2022-12-11 08:11:16 +08:00
FooIbar 452c0f2846 Use compose BOM instead 2022-12-10 07:36:24 +08:00
442 changed files with 22319 additions and 29527 deletions

No files matched your search

+7
View File
@@ -56,3 +56,10 @@ jobs:
with:
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
- name: Update mapping
uses: actions/upload-artifact@v3
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/release/mapping.txt
retention-days: 5
+1
View File
@@ -49,6 +49,7 @@ jobs:
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
- name: Collect Apks
run: |
+5 -5
View File
@@ -11,7 +11,7 @@
</p>
<h1 align="center">
<img src="art/launcher_icon-web.svg" width="200">
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200">
<br>EhViewer<br>
</h1>
@@ -66,12 +66,12 @@ with Material Design 3 and Dynamic Color Support
# Download
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="art/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Git it on F-Droid" src="art/get-it-on-fdroid.svg" width="200px"/></a>
<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="Git it on F-Droid" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-fdroid.svg" width="200px"/></a>
# Screenshot
![screenshot-01](art/screenshot-01.png)
![screenshot-02](art/screenshot-02.png)
![screenshot-01](https://github.com/Ehviewer-Overhauled/Art/blob/master/screenshot-01.png)
![screenshot-02](https://github.com/Ehviewer-Overhauled/Art/blob/master/screenshot-02.png)
# Thanks
@@ -94,7 +94,7 @@ Here is the libraries
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
Copyright 2022 Tarsin Norbin
Copyright 2022-2023 Tarsin Norbin
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.
+27 -27
View File
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-android'
id 'kotlin-parcelize'
id 'com.google.devtools.ksp'
}
@@ -31,8 +32,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 28
targetSdkVersion 33
versionCode 180026
versionName "1.8.4.0-alpha02"
versionCode 180030
versionName "1.8.5.3-alpha02"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -52,10 +53,6 @@ android {
freeCompilerArgs += [
// https://kotlinlang.org/docs/compiler-reference.html#progressive
"-progressive",
// Generate smaller bytecode by not generating runtime not-null assertions.
"-Xno-call-assertions",
"-Xno-param-assertions",
"-Xno-receiver-assertions",
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
]
@@ -94,6 +91,7 @@ android {
buildFeatures {
buildConfig true
compose true
viewBinding true
}
composeOptions {
@@ -105,12 +103,11 @@ android {
dependencies {
def activity_version = "1.6.1"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.annotation:annotation-experimental:1.3.0"
implementation "androidx.appcompat:appcompat:1.6.0-rc01"
implementation "androidx.appcompat:appcompat:1.7.0-alpha01"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
@@ -123,28 +120,25 @@ dependencies {
implementation "androidx.collection:collection:$collection_version"
implementation "androidx.collection:collection-ktx:$collection_version"
def compose_version = "1.3.1"
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
implementation "androidx.compose.runtime:runtime:$compose_version"
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material3:material3:1.0.1"
implementation platform("androidx.compose:compose-bom:2022.12.00")
implementation "androidx.compose.animation:animation"
implementation "androidx.compose.foundation:foundation"
implementation "androidx.compose.foundation:foundation-layout"
implementation "androidx.compose.material3:material3"
implementation "androidx.compose.runtime:runtime"
implementation "androidx.compose.runtime:runtime-saveable"
implementation "androidx.compose.ui:ui"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
def core_version = "1.9.0"
implementation "androidx.core:core:$core_version"
implementation "androidx.core:core-ktx:$core_version"
implementation "androidx.customview:customview:1.2.0-alpha02"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.6.0-alpha04"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.6.0-alpha03"
@@ -158,19 +152,17 @@ dependencies {
implementation "androidx.loader:loader:1.1.0"
def nav_version = "2.5.3"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.paging:paging-runtime:3.2.0-alpha03"
implementation "androidx.paging:paging-runtime-ktx:3.2.0-alpha03"
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
def room_version = "2.5.0-rc01"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
ksp "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-paging:$room_version"
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
@@ -182,10 +174,11 @@ dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.8.0-SNAPSHOT'
implementation 'com.google.android.material:material:1.8.0-beta01'
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11'
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:okhttp-coroutines'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
@@ -195,10 +188,17 @@ dependencies {
implementation 'io.coil-kt:coil:2.2.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jsoup:jsoup:1.15.3'
// Tachiyomi Reader
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'dev.chrisbanes.insetter:insetter:0.6.1'
}
configurations.all {
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
exclude group: 'dev.rikka.rikkax.material', module: 'material'
exclude group: 'androidx.compose.material'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-android-extensions-runtime'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
+4 -9
View File
@@ -2,20 +2,15 @@
public static final ** CREATOR;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void check*(...);
public static void throw*(...);
}
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
-renamesourcefileattribute
-repackageclasses
-allowaccessmodification
-mergeinterfacesaggressively
-overloadaggressively
+15 -8
View File
@@ -14,7 +14,8 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
@@ -32,10 +33,6 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<application
android:name="com.hippo.ehviewer.EhApplication"
android:allowBackup="true"
@@ -45,12 +42,13 @@
android:fullBackupContent="@xml/backup_scheme"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:label="@string/app_name"
android:largeHeap="true"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:targetApi="tiramisu">
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
@@ -102,7 +100,7 @@
</activity>
<activity
android:name="com.hippo.ehviewer.ui.GalleryActivity"
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask">
@@ -160,6 +158,15 @@
android:taskAffinity=""
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
<service
android:name="com.hippo.ehviewer.download.DownloadService"
android:foregroundServiceType="dataSync"
+2 -2
View File
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
add_subdirectory(libarchive)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED archive.c image.c fdutils.c)
add_library(${PROJECT_NAME} SHARED archive.c fdutils.c)
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
target_link_libraries(${PROJECT_NAME} archive_static log -Wl,--gc-sections)
+53 -2
View File
@@ -43,6 +43,11 @@ typedef struct {
struct archive_entry *entry;
} archive_ctx;
typedef struct {
const char *filename;
int index;
} entry;
pthread_mutex_t ctx_lock;
static archive_ctx **ctx_pool;
#define CTX_POOL_SIZE 20
@@ -51,6 +56,8 @@ static bool need_encrypt = false;
static char *passwd = NULL;
static void *archiveAddr = NULL;
static size_t archiveSize = 0;
static entry *entries = NULL;
static size_t entryCount = 0;
#define POPULATE_THRESHOLD (512 * 1024 * 1024) // 512 MiB
@@ -77,6 +84,30 @@ static inline int filename_is_playable_file(const char *name) {
return false;
}
static inline int compare_entries(const void *a, const void *b) {
const char *fa = ((entry *) a)->filename;
const char *fb = ((entry *) b)->filename;
int a_len = (int) strlen(fa);
int b_len = (int) strlen(fb);
return a_len == b_len ? strcmp(fa, fb) : a_len - b_len;
}
static long archive_map_entries_index(archive_ctx *ctx) {
long count = 0;
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
const char *name = archive_entry_pathname(ctx->entry);
if (filename_is_playable_file(name)) {
entries[count].filename = strdup(name);
entries[count].index = count;
count++;
}
}
if (!count)
LOGE("%s", archive_error_string(ctx->arc));
qsort(entries, entryCount, sizeof(entry), compare_entries);
return count;
}
static long archive_list_all_entries(archive_ctx *ctx) {
long count = 0;
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
@@ -221,10 +252,9 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
if (r) {
if (r == -ENOMEM)
LOGE("%s", "Mem alloc failed");
r = 0;
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
} else {
r = archive_list_all_entries(ctx);
entryCount = archive_list_all_entries(ctx);
LOGI("%s%ld%s", "Found ", r, " image entries in archive");
// We must read through the file|vm then we can know whether it is encrypted
@@ -251,6 +281,19 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
}
}
archive_release_ctx(ctx);
r = archive_alloc_ctx(&ctx);
if (r) {
if (r == -ENOMEM)
LOGE("%s", "Mem alloc failed");
r = 0;
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
} else {
entries = calloc(entryCount, sizeof(entry));
r = archive_map_entries_index(ctx);
}
archive_release_ctx(ctx);
return r;
}
@@ -259,6 +302,7 @@ JNIEXPORT jobject JNICALL
Java_com_hippo_UriArchiveAccessor_extractToByteBuffer(JNIEnv *env, jobject thiz, jint index) {
EH_UNUSED(env);
EH_UNUSED(thiz);
index = entries[index].index;
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
@@ -295,6 +339,10 @@ Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
passwd = NULL;
need_encrypt = false;
munmap(archiveAddr, archiveSize);
for (int i = 0; i < entryCount; ++i) {
free((void *) entries[i].filename);
}
free(entries);
}
JNIEXPORT jboolean JNICALL
@@ -338,6 +386,7 @@ JNIEXPORT jstring JNICALL
Java_com_hippo_UriArchiveAccessor_getFilename(JNIEnv *env, jobject thiz, jint index) {
EH_UNUSED(env);
EH_UNUSED(thiz);
index = entries[index].index;
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
@@ -352,6 +401,7 @@ JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint index, jint fd) {
EH_UNUSED(env);
EH_UNUSED(thiz);
index = entries[index].index;
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
@@ -363,6 +413,7 @@ Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint in
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_releaseByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) {
EH_UNUSED(thiz);
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
free(addr);
}
-143
View File
@@ -1,143 +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/>.
*/
#include <stdlib.h>
#include <string.h>
#include <android/bitmap.h>
#include <android/data_space.h>
#include <android/log.h>
#include <GLES2/gl2.h>
#include <jni.h>
#define TAG "ImageDecoder_wrapper"
#include "ehviewer.h"
#define IMAGE_TILE_MAX_SIZE (512 * 512)
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
int width, int height) {
int left;
int line;
size_t line_stride;
int src_stride;
int src_pos;
int dst_pos;
size_t dst_blank_length;
// Sanitize
if (src_x < 0) {
width -= src_x;
dst_x -= src_x;
src_x = 0;
}
if (dst_x < 0) {
width -= dst_x;
src_x -= dst_x;
dst_x = 0;
}
if (width <= 0) {
return false;
}
if (src_y < 0) {
height -= src_y;
dst_y -= src_y;
src_y = 0;
}
if (dst_y < 0) {
height -= dst_y;
src_y -= dst_y;
dst_y = 0;
}
if (height <= 0) {
return false;
}
left = src_x + width - src_w;
if (left > 0) {
width -= left;
}
left = dst_x + width - dst_w;
if (left > 0) {
width -= left;
}
if (width <= 0) {
return false;
}
left = src_y + height - src_h;
if (left > 0) {
height -= left;
}
left = dst_y + height - dst_h;
if (left > 0) {
height -= left;
}
if (height <= 0) {
return false;
}
// Init
line_stride = (size_t) (width * 4);
src_stride = src_w * 4;
src_pos = src_y * src_stride + src_x * 4;
dst_pos = 0;
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
// First line
dst_pos += (int) dst_blank_length;
memcpy(dst + dst_pos, src + src_pos, line_stride);
dst_pos += (int) line_stride;
src_pos += src_stride;
// Other lines
dst_blank_length = (size_t) ((dst_w - width) * 4);
for (line = 1; line < height; line++) {
dst_pos += (int) dst_blank_length;
memcpy(dst + dst_pos, src + src_pos, line_stride);
dst_pos += (int) line_stride;
src_pos += src_stride;
}
return true;
}
JNIEXPORT void JNICALL
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jobject bitmap, jboolean init,
jint offset_x, jint offset_y, jint width, jint height) {
if (width * height > IMAGE_TILE_MAX_SIZE)
return;
AndroidBitmapInfo info;
void *pixels = NULL;
AndroidBitmap_lockPixels(env, bitmap, &pixels);
AndroidBitmap_getInfo(env, bitmap, &info);
copy_pixels(pixels, info.width, info.height, offset_x, offset_y, tile_buffer, width, height, 0, 0, width, height);
AndroidBitmap_unlockPixels(env, bitmap);
if (init) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
tile_buffer);
} else {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
tile_buffer);
}
}
@@ -0,0 +1,25 @@
package androidx.swiperefreshlayout.widget
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.ShapeDrawable
import android.util.AttributeSet
import androidx.core.graphics.ColorUtils
import rikka.core.res.resolveColor
@SuppressLint("PrivateResource")
class ThemedSwipeRefreshLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : SwipeRefreshLayout(context, attrs) {
init {
val surfaceColor =
context.theme.resolveColor(com.google.android.material.R.attr.colorSurface)
val overlayColor =
context.getColor(com.google.android.material.R.color.m3_popupmenu_overlay_color)
val backgroundColor = ColorUtils.compositeColors(overlayColor, surfaceColor)
(mCircleView.background as ShapeDrawable).paint.color = backgroundColor
setColorSchemeColors(context.theme.resolveColor(com.google.android.material.R.attr.colorAccent))
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,160 @@
package com.davemorrissey.labs.subscaleview
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
import android.net.Uri
import com.davemorrissey.labs.subscaleview.provider.AssetInputProvider
import com.davemorrissey.labs.subscaleview.provider.InputProvider
import com.davemorrissey.labs.subscaleview.provider.OpenStreamProvider
import com.davemorrissey.labs.subscaleview.provider.ResourceInputProvider
import com.davemorrissey.labs.subscaleview.provider.UriInputProvider
import java.io.InputStream
/**
* Helper class used to set the source and additional attributes from a variety of sources. Supports
* use of a bitmap, asset, resource, external file or any other URI.
*
*
* When you are using a preview image, you must set the dimensions of the full size image on the
* ImageSource object for the full size image using the [.dimensions] method.
*/
class ImageSource {
val bitmap: Bitmap?
val provider: InputProvider?
private var sWidth = 0
private var sHeight = 0
var sRegion = Rect(0, 0, 0, 0)
private set
var isCached = false
private set
private constructor(bitmap: Bitmap, cached: Boolean) {
this.bitmap = bitmap
provider = null
sWidth = bitmap.width
sHeight = bitmap.height
isCached = cached
}
private constructor(provider: InputProvider) {
bitmap = null
this.provider = provider
}
/**
* Use a region of the source image. Region must be set independently for the full size image and the preview if
* you are using one.
*
* @param sRegion the region of the source image to be displayed.
* @return this instance for chaining.
*/
fun region(sRegion: Rect): ImageSource {
this.sRegion = sRegion
setInvariants()
return this
}
/**
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
*
* @param sWidth width of the source image.
* @param sHeight height of the source image.
* @return this instance for chaining.
*/
fun dimensions(sWidth: Int, sHeight: Int): ImageSource {
if (bitmap == null) {
this.sWidth = sWidth
this.sHeight = sHeight
}
setInvariants()
return this
}
private fun setInvariants() {
if (!sRegion.isEmpty) {
sWidth = sRegion.width()
sHeight = sRegion.height()
}
}
companion object {
/**
* Create an instance from a resource. The correct resource for the device screen resolution will be used.
*
* @param resId resource ID.
* @return an [ImageSource] instance.
*/
@JvmStatic
fun resource(context: Context, resId: Int): ImageSource {
return ImageSource(ResourceInputProvider(context, resId))
}
/**
* Create an instance from an asset name.
*
* @param assetName asset name.
* @return an [ImageSource] instance.
*/
@JvmStatic
fun asset(context: Context, assetName: String): ImageSource {
return ImageSource(AssetInputProvider(context, assetName))
}
/**
* Create an instance from a URI.
*
* @param uri image URI.
* @return an [ImageSource] instance.
*/
fun uri(context: Context, uri: Uri): ImageSource {
return ImageSource(UriInputProvider(context, uri))
}
/**
* Create an instance from an input provider.
*
* @param provider input stream provider.
* @return an [ImageSource] instance.
*/
fun provider(provider: InputProvider): ImageSource {
return ImageSource(provider)
}
/**
* Create an instance from an input stream.
*
* @param stream open input stream.
* @return an [ImageSource] instance.
*/
fun inputStream(stream: InputStream): ImageSource {
return ImageSource(OpenStreamProvider(stream))
}
/**
* Provide a loaded bitmap for display.
*
* @param bitmap bitmap to be displayed.
* @return an [ImageSource] instance.
*/
fun bitmap(bitmap: Bitmap): ImageSource {
return ImageSource(bitmap, false)
}
/**
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
* or Volley.
*
* @param bitmap bitmap to be displayed.
* @return an [ImageSource] instance.
*/
fun cachedBitmap(bitmap: Bitmap): ImageSource {
return ImageSource(bitmap, true)
}
}
}
@@ -0,0 +1,15 @@
package com.davemorrissey.labs.subscaleview
import android.graphics.PointF
import java.io.Serializable
/**
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
*/
class ImageViewState(val scale: Float, center: PointF) : Serializable {
private val centerX: Float = center.x
private val centerY: Float = center.y
val center: PointF
get() = PointF(centerX, centerY)
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,53 @@
package com.davemorrissey.labs.subscaleview.decoder
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
import kotlin.Throws
import com.davemorrissey.labs.subscaleview.provider.InputProvider
import java.lang.Exception
/**
* Interface for image decoding classes.
*/
interface Decoder {
/**
* Initialise the decoder. When possible, perform initial setup work once in this method. The
* dimensions of the image must be returned.
*
* @param context Application context. A reference may be held, but must be cleared on recycle.
* @param provider Provider of the image.
* @return Dimensions of the image.
* @throws Exception if initialisation fails.
*/
@Throws(Exception::class)
fun init(context: Context, provider: InputProvider): Point
/**
* Decode a region of the image with the given sample size. This method is called off the UI
* thread so it can safely load the image on the current thread. It is called from
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
* implementations must be thread safe. Adding `synchronized` to the method signature
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
* called concurrently.
*
* @param sRect Source image rectangle to decode.
* @param sampleSize Sample size.
* @return The decoded region. It is safe to return null if decoding fails.
*/
fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap
/**
* Status check. Should return false before initialisation and after recycle.
*
* @return true if the decoder is ready to be used.
*/
val isReady: Boolean
/**
* This method will be called when the decoder is no longer required. It should clean up any
* resources still in use.
*/
fun recycle()
}
@@ -0,0 +1,64 @@
package com.davemorrissey.labs.subscaleview.decoder
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
import kotlin.Throws
import com.davemorrissey.labs.subscaleview.provider.InputProvider
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import java.lang.Exception
class ImageDecoder @JvmOverloads constructor(
private val cropBorders: Boolean,
bitmapConfig: Bitmap.Config? = null
) : Decoder {
init {
val globalBitmapConfig = SubsamplingScaleImageView.preferredBitmapConfig
}
/**
* Initialise the decoder. When possible, perform initial setup work once in this method. The
* dimensions of the image must be returned.
*
* @param context Application context. A reference may be held, but must be cleared on recycle.
* @param provider Provider of the image.
* @return Dimensions of the image.
* @throws Exception if initialisation fails.
*/
@Throws(Exception::class)
override fun init(context: Context, provider: InputProvider): Point {
TODO()
}
/**
* Decode a region of the image with the given sample size. This method is called off the UI
* thread so it can safely load the image on the current thread. It is called from
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
* implementations must be thread safe. Adding `synchronized` to the method signature
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
* called concurrently.
*
* @param sRect Source image rectangle to decode.
* @param sampleSize Sample size.
* @return The decoded region. It is safe to return null if decoding fails.
*/
override fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap {
TODO()
}
/**
* Status check. Should return false before initialisation and after recycle.
*
* @return true if the decoder is ready to be used.
*/
override val isReady: Boolean = TODO()
/**
* This method will be called when the decoder is no longer required. It should clean up any
* resources still in use.
*/
override fun recycle() {
TODO()
}
}
@@ -0,0 +1,11 @@
package com.davemorrissey.labs.subscaleview.provider
import android.content.Context
import java.io.IOException
class AssetInputProvider(context: Context, val assetName: String) : InputProvider {
private val assets = context.assets
@Throws(IOException::class)
override fun openStream() = assets.open(assetName)
}
@@ -0,0 +1,10 @@
package com.davemorrissey.labs.subscaleview.provider
import java.io.IOException
import java.io.InputStream
import kotlin.Throws
interface InputProvider {
@Throws(IOException::class)
fun openStream(): InputStream?
}
@@ -0,0 +1,16 @@
package com.davemorrissey.labs.subscaleview.provider
import java.io.IOException
import java.io.InputStream
import kotlin.Throws
/**
* Provider from an already open input stream.
*
*
* Please keep in mind this provider is not reusable.
*/
class OpenStreamProvider(private val stream: InputStream) : InputProvider {
@Throws(IOException::class)
override fun openStream() = stream
}
@@ -0,0 +1,11 @@
package com.davemorrissey.labs.subscaleview.provider
import android.content.Context
import java.io.IOException
class ResourceInputProvider(context: Context, val resource: Int) : InputProvider {
private val resources = context.resources
@Throws(IOException::class)
override fun openStream() = resources.openRawResource(resource)
}
@@ -0,0 +1,13 @@
package com.davemorrissey.labs.subscaleview.provider
import android.content.Context
import android.net.Uri
import java.io.IOException
import kotlin.Throws
class UriInputProvider(context: Context, val uri: Uri) : InputProvider {
private val resolver = context.contentResolver
@Throws(IOException::class)
override fun openStream() = resolver.openInputStream(uri)
}
@@ -29,8 +29,6 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
private external fun openArchive(fd: Int, size: Long): Int
external fun extractToByteBuffer(index: Int): ByteBuffer?
external fun releaseByteBuffer(buffer: ByteBuffer)
external fun extractToFd(index: Int, fd: Int)
external fun getFilename(index: Int): String
external fun needPassword(): Boolean
@@ -40,4 +38,9 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
closeArchive()
pfd.close()
}
companion object {
@JvmStatic
external fun releaseByteBuffer(buffer: ByteBuffer)
}
}
@@ -250,21 +250,7 @@ public final class DiskLruCache implements Closeable {
private static String inputStreamToString(InputStream in) throws IOException {
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
}
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
@@ -366,7 +352,21 @@ public final class DiskLruCache implements Closeable {
i.remove();
}
}
}
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
/**
* Creates a new journal that omits redundant information. This replaces the
@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
import com.hippo.util.HashCodeUtils;
import java.io.File;
import java.io.IOException;
@@ -85,32 +86,13 @@ public class SimpleDiskCache {
try {
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(key.getBytes());
cacheKey = bytesToHexString(digest.digest());
cacheKey = HashCodeUtils.bytesToHexString(digest.digest());
} catch (final NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
/**
* http://stackoverflow.com/questions/332079
*
* @param bytes The bytes to convert.
* @return A {@link String} converted from the bytes of a hashable key used
* to store a filename on the disk, to hex digits.
*/
private static String bytesToHexString(final byte[] bytes) {
final StringBuilder builder = new StringBuilder();
for (final byte b : bytes) {
final String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
builder.append('0');
}
builder.append(hex);
}
return builder.toString();
}
public synchronized boolean isValid() {
return mDiskLruCache != null;
}
@@ -1,176 +0,0 @@
/*
* Copyright (C) 2014 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.drawable;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import com.hippo.yorozuya.MathUtils;
public class BatteryDrawable extends Drawable {
public static final int WARN_LIMIT = 15;
@SuppressWarnings("unused")
private static final String TAG = BatteryDrawable.class.getSimpleName();
private final Paint mPaint;
private final Rect mTopRect;
private final Rect mBottomRect;
private final Rect mRightRect;
private final Rect mHeadRect;
private final Rect mElectRect;
private int mColor = Color.WHITE;
private int mWarningColor = Color.RED;
private int mElect = -1;
private int mStart;
private int mStop;
public BatteryDrawable() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setStyle(Paint.Style.FILL);
mTopRect = new Rect();
mBottomRect = new Rect();
mRightRect = new Rect();
mHeadRect = new Rect();
mElectRect = new Rect();
updatePaint();
}
@Override
@SuppressWarnings("SuspiciousNameCombination")
protected void onBoundsChange(Rect bounds) {
int width = bounds.width();
int height = bounds.height();
int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f);
int turn1 = width * 6 / 7;
int turn2 = height / 3;
int secBottom = height - strokeWidth;
mStart = strokeWidth;
mStop = turn1 - strokeWidth;
mTopRect.set(0, 0, turn1, strokeWidth);
mBottomRect.set(0, secBottom, turn1, height);
mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom);
mHeadRect.set(turn1, turn2, width, height - turn2);
mElectRect.set(0, strokeWidth, mStop, secBottom);
}
/**
* How to draw:<br>
* |------------------------------|<br>
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
* |------------------------------|---|<br>
* |/////////////////| |//|\\\|<br>
* |/////////////////| |//|\\\|<br>
* |------------------------------|---|<br>
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
* |------------------------------|
*/
@Override
public void draw(Canvas canvas) {
if (mElect == -1) {
return;
}
mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f);
canvas.drawRect(mTopRect, mPaint);
canvas.drawRect(mBottomRect, mPaint);
canvas.drawRect(mRightRect, mPaint);
canvas.drawRect(mHeadRect, mPaint);
canvas.drawRect(mElectRect, mPaint);
}
private boolean isWarn() {
return mElect <= WARN_LIMIT;
}
public void setColor(int color) {
if (mColor == color) {
return;
}
mColor = color;
if (!isWarn()) {
mPaint.setColor(mColor);
invalidateSelf();
}
}
public void setWarningColor(int color) {
if (mWarningColor == color) {
return;
}
mWarningColor = color;
if (isWarn()) {
mPaint.setColor(mWarningColor);
invalidateSelf();
}
}
public void setElect(int elect) {
if (mElect == elect) {
return;
}
mElect = elect;
updatePaint();
}
public void setElect(int elect, boolean warn) {
if (mElect == elect) {
return;
}
mElect = elect;
updatePaint(warn);
}
private void updatePaint() {
updatePaint(isWarn());
}
private void updatePaint(boolean warn) {
if (warn) {
mPaint.setColor(mWarningColor);
} else {
mPaint.setColor(mColor);
}
invalidateSelf();
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
@@ -18,29 +18,28 @@ package com.hippo.ehviewer
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.content.ComponentCallbacks2
import android.os.Build
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.LruCache
import androidx.core.os.LocaleListCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import coil.Coil
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import com.hippo.Native
import com.hippo.app.BaseDialogBuilder
import com.hippo.beerbelly.SimpleDiskCache
import com.hippo.ehviewer.client.EhClient
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhDns
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhRequestBuilder
import com.hippo.ehviewer.client.EhSSLSocketFactory
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhX509TrustManager
import com.hippo.ehviewer.client.data.GalleryDetail
@@ -52,24 +51,29 @@ import com.hippo.ehviewer.ui.EhActivity
import com.hippo.scene.SceneApplication
import com.hippo.util.BitmapUtils
import com.hippo.util.ExceptionUtils
import com.hippo.util.IoThreadPoolExecutor
import com.hippo.util.ReadableTime
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IntIdGenerator
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import java.io.File
import java.net.Proxy
import java.security.KeyStore
import java.util.Arrays
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
class EhApplication : SceneApplication() {
class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderFactory {
private val mIdGenerator = IntIdGenerator()
private val mGlobalStuffMap = HashMap<Int, Any>()
private val mActivityList = ArrayList<Activity>()
private var initialized = false
val topActivity: EhActivity?
get() = if (mActivityList.isNotEmpty()) {
mActivityList[mActivityList.size - 1] as EhActivity
@@ -81,9 +85,10 @@ class EhApplication : SceneApplication() {
mActivityList.forEach { it.recreate() }
}
@OptIn(DelicateCoroutinesApi::class)
@SuppressLint("StaticFieldLeak")
override fun onCreate() {
ProcessLifecycleOwner.get().lifecycle.addObserver(EhlifecycleObserver())
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
application = this
val handler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
@@ -95,47 +100,51 @@ class EhApplication : SceneApplication() {
}
handler?.uncaughtException(t, e)
}
super.onCreate()
super<SceneApplication>.onCreate()
Native.initialize()
GetText.initialize(this)
Settings.initialize(this)
Settings.initialize()
ReadableTime.initialize(this)
AppConfig.initialize(this)
SpiderDen.initialize(this)
EhDB.initialize(this)
EhEngine.initialize()
BitmapUtils.initialize(this)
Coil.setImageLoader(ImageLoader.Builder(this).okHttpClient(okHttpClient).build())
// Locales can be managed by system automatically above Snow Cone v2
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()))
EhTagDatabase.update()
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
IoThreadPoolExecutor.getInstance().execute {
try {
val downloadLocation = Settings.getDownloadLocation()
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation)
} else {
CommonOperations.ensureNoMediaFile(downloadLocation)
}
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
launchIO {
launchIO {
downloadManager
}
// Clear temp files
try {
clearTempDir()
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
launchIO {
cleanupDownload()
}
launchIO {
theDawnOfNewDay()
}
}
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0))
initialized = true
theDawnOfNewDay()
}
private fun cleanupDownload() {
try {
val downloadLocation = Settings.getDownloadLocation()
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation)
} else {
CommonOperations.ensureNoMediaFile(downloadLocation)
}
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
// Clear temp files
try {
clearTempDir()
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
}
private fun theDawnOfNewDay() {
@@ -150,28 +159,27 @@ class EhApplication : SceneApplication() {
EhCookieStore.KEY_IPD_PASS_HASH
)
) {
IoThreadPoolExecutor.getInstance().execute {
val referer = EhUrl.REFERER_E
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
val call = okHttpClient.newCall(request)
try {
val response = call.execute()
val referer = EhUrl.REFERER_E
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
val call = okHttpClient.newCall(request)
try {
call.execute().use { response ->
val responseBody = response.body
val body = responseBody.string()
val html = EventPaneParser.parse(body)
if (html != null) {
showEventPane(html)
}
} catch (e: Throwable) {
e.printStackTrace()
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
fun showEventPane(html: String) {
if (Settings.getHideHvEvents() && html.contains("You have encountered a monster!")) {
return;
return
}
val activity = topActivity
activity?.runOnUiThread {
@@ -242,12 +250,22 @@ class EhApplication : SceneApplication() {
mActivityList.remove(activity)
}
internal class EhlifecycleObserver : DefaultLifecycleObserver {
override fun onPause(owner: LifecycleOwner) {
if (!locked)
locked_last_leave_time = System.currentTimeMillis() / 1000
locked = true
}
override fun onPause(owner: LifecycleOwner) {
if (!locked)
locked_last_leave_time = System.currentTimeMillis() / 1000
locked = true
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this).apply {
okHttpClient(okHttpClient)
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
// Coil spawns a new thread for every image load by default
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8))
decoderDispatcher(Dispatchers.IO.limitedParallelism(2))
transformationDispatcher(Dispatchers.IO.limitedParallelism(2))
}.build()
}
companion object {
@@ -262,9 +280,6 @@ class EhApplication : SceneApplication() {
@JvmStatic
val ehCookieStore by lazy { EhCookieStore(application) }
@JvmStatic
val ehClient by lazy { EhClient(application) }
@JvmStatic
val ehProxySelector by lazy { EhProxySelector() }
@@ -273,7 +288,7 @@ class EhApplication : SceneApplication() {
val builder = OkHttpClient.Builder()
.cookieJar(ehCookieStore)
.cache(Cache(File(application.cacheDir, "http_cache"), 50L * 1024L * 1024L))
.dns(EhDns())
.dns(EhDns)
.proxySelector(ehProxySelector)
if (Settings.getDF()) {
@@ -292,10 +307,11 @@ class EhApplication : SceneApplication() {
trustManager = trustManagers[0] as X509TrustManager
} catch (e: Exception) {
e.printStackTrace()
trustManager = EhX509TrustManager()
trustManager = EhX509TrustManager
}
builder.sslSocketFactory(EhSSLSocketFactory(), trustManager)
builder.sslSocketFactory(EhSSLSocketFactory, trustManager)
builder.proxy(Proxy.NO_PROXY)
}
builder.build()
}
@@ -311,19 +327,20 @@ class EhApplication : SceneApplication() {
@JvmStatic
val spiderInfoCache by lazy {
SimpleDiskCache(
File(application.cacheDir, "spider_info"),
20 * 1024 * 1024
)
DiskCache.Builder().directory(File(application.cacheDir, "spider_info"))
.maxSizeBytes(20 * 1024 * 1024).build()
}
@JvmStatic
val downloadManager by lazy { DownloadManager(application) }
val downloadManager by lazy { DownloadManager() }
@JvmStatic
val hosts by lazy { Hosts(application, "hosts.db") }
@JvmStatic
val favouriteStatusRouter by lazy { FavouriteStatusRouter() }
@JvmStatic
val readerPreferences by lazy { ReaderPreferences(AndroidPreferenceStore(application)) }
}
}
+15 -23
View File
@@ -234,7 +234,7 @@ public class EhDB {
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
LocalFavoritesDao dao = db.localFavoritesDao();
List<LocalFavoriteInfo> list = dao.list();
List<LocalFavoriteInfo> list = dao.list("%" + query + "%");
return new ArrayList<>(list);
}
@@ -281,9 +281,9 @@ public class EhDB {
public static synchronized void insertQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = db.quickSearchDao();
quickSearch.id = null;
quickSearch.time = System.currentTimeMillis();
quickSearch.id = dao.insert(quickSearch);
quickSearch.setId(null);
quickSearch.setTime(System.currentTimeMillis());
quickSearch.setId(dao.insert(quickSearch));
}
public static synchronized void importQuickSearch(List<QuickSearch> quickSearchList) {
@@ -293,11 +293,6 @@ public class EhDB {
}
}
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = db.quickSearchDao();
dao.update(quickSearch);
}
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = db.quickSearchDao();
dao.delete(quickSearch);
@@ -327,25 +322,22 @@ public class EhDB {
dao.update(list);
}
public static synchronized List<HistoryInfo> getHistoryList() {
return db.historyDao().list();
}
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
return db.historyDao().listLazy();
}
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
HistoryDao dao = db.historyDao();
HistoryInfo info = dao.load(galleryInfo.gid);
if (null != info) {
// Update time
info.time = System.currentTimeMillis();
HistoryInfo info;
if (galleryInfo instanceof HistoryInfo) {
info = (HistoryInfo) galleryInfo;
} else {
info = new HistoryInfo(galleryInfo);
}
info.time = System.currentTimeMillis();
if (null != dao.load(info.gid)) {
dao.update(info);
} else {
// New history
info = new HistoryInfo(galleryInfo);
info.time = System.currentTimeMillis();
dao.insert(info);
}
}
@@ -394,7 +386,7 @@ public class EhDB {
}
public static synchronized void triggerFilter(Filter filter) {
filter.setEnable(!filter.enable);
filter.enable = !filter.enable;
db.filterDao().update(filter);
}
@@ -529,9 +521,9 @@ public class EhDB {
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
List<QuickSearch> importList = new ArrayList<>();
for (QuickSearch quickSearch : quickSearchList) {
String name = quickSearch.name;
String name = quickSearch.getName();
for (QuickSearch q : currentQuickSearchList) {
if (ObjectUtils.equal(q.name, name)) {
if (ObjectUtils.equal(q.getName(), name)) {
// The same name
name = null;
break;
@@ -16,7 +16,6 @@
package com.hippo.ehviewer;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
@@ -27,19 +26,13 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.scene.GalleryListScene;
import com.hippo.glgallery.GalleryView;
import com.hippo.unifile.UniFile;
import com.hippo.util.ExceptionUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.NumberUtils;
import java.io.File;
import java.util.Locale;
import java.util.Set;
@@ -53,10 +46,7 @@ public class Settings {
public static final String KEY_ACCOUNT = "account";
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
public static final int THEME_LIGHT = 1;
public static final int THEME_SYSTEM = -1;
public static final int THEME_BLACK = 2;
public static final String KEY_APPLY_NAV_BAR_THEME_COLOR = "apply_nav_bar_theme_color";
public static final String KEY_GALLERY_SITE = "gallery_site";
public static final String KEY_LIST_MODE = "list_mode";
public static final String KEY_DETAIL_SIZE = "detail_size";
@@ -67,8 +57,6 @@ public class Settings {
public static final int DEFAULT_DEFAULT_CATEGORIES = EhConfig.ALL_CATEGORY;
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
/********************
****** Privacy and Security
********************/
@@ -87,45 +75,35 @@ public class Settings {
public static final String KEY_IMAGE_RESOLUTION = "image_size";
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
public static final String KEY_ENABLE_ANALYTICS = "enable_analytics";
/********************
****** Advanced
********************/
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
public static final String KEY_SECURITY = "require_unlock";
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
public static final String DEFAULT_SECURITY = "";
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
public static final int DEFAULT_READ_CACHE_SIZE = 320;
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts_2";
public static final String KEY_DOMAIN_FRONTING = "domain_fronting";
public static final String KEY_APP_LANGUAGE = "app_language";
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
private static final String KEY_SHOW_COMMENTS = "show_gallery_comments";
private static final boolean DEFAULT_SHOW_COMMENTS = true;
private static final String KEY_PREVIEW_NUM = "preview_num";
private static final int DEFAULT_PREVIEW_NUM = 60;
private static final String TAG = Settings.class.getSimpleName();
private static final String KEY_VERSION_CODE = "version_code";
private static final int DEFAULT_VERSION_CODE = 0;
private static final String KEY_DISPLAY_NAME = "display_name";
private static final String DEFAULT_DISPLAY_NAME = null;
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
private static final String KEY_AVATAR = "avatar";
private static final String DEFAULT_AVATAR = null;
private static final String KEY_SHOW_WARNING = "show_warning";
private static final boolean DEFAULT_SHOW_WARNING = true;
private static final String KEY_REMOVE_IMAGE_FILES = "include_pic";
private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true;
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
private static final boolean DEFAULT_NEED_SIGN_IN = true;
private static final String KEY_SELECT_SITE = "select_site";
private static final boolean DEFAULT_SELECT_SITE = true;
private static final String KEY_QUICK_SEARCH_TIP = "quick_search_tip";
private static final boolean DEFAULT_QUICK_SEARCH_TIP = true;
private static final int DEFAULT_THEME = THEME_SYSTEM;
private static final boolean DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
private static final int DEFAULT_GALLERY_SITE = 1;
private static final String KEY_LAUNCH_PAGE = "launch_page";
private static final int DEFAULT_LAUNCH_PAGE = 0;
@@ -144,41 +122,6 @@ public class Settings {
private static final boolean DEFAULT_METERED_NETWORK_WARNING = false;
private static final String KEY_APP_LINK_VERIFY_TIP = "app_link_verify_tip";
private static final boolean DEFAULT_APP_LINK_VERIFY_TIP = false;
private static final String KEY_NIGHT_MODE = "night_mode";
private static final String DEFAULT_NIGHT_MODE = "-1";
private static final String KEY_E_INK_MODE = "e_ink_mode_2";
private static final boolean DEFAULT_E_INK_MODE = false;
/********************
****** Read
********************/
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
private static final int DEFAULT_SCREEN_ROTATION = 0;
private static final String KEY_READING_DIRECTION = "reading_direction";
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
private static final String KEY_PAGE_SCALING = "page_scaling";
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
private static final String KEY_START_POSITION = "start_position";
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
private static final boolean DEFAULT_SHOW_CLOCK = true;
private static final String KEY_SHOW_PROGRESS = "gallery_show_progress";
private static final boolean DEFAULT_SHOW_PROGRESS = true;
private static final String KEY_SHOW_BATTERY = "gallery_show_battery";
private static final boolean DEFAULT_SHOW_BATTERY = true;
private static final String KEY_SHOW_PAGE_INTERVAL = "gallery_show_page_interval";
private static final boolean DEFAULT_SHOW_PAGE_INTERVAL = true;
private static final String KEY_VOLUME_PAGE = "volume_page";
private static final boolean DEFAULT_VOLUME_PAGE = false;
private static final String KEY_REVERSE_VOLUME_PAGE = "reserve_volume_page";
private static final boolean DEFAULT_REVERSE_VOLUME_PAGE = false;
private static final String KEY_READING_FULLSCREEN = "reading_fullscreen";
private static final boolean VALUE_READING_FULLSCREEN = true;
private static final String KEY_CUSTOM_SCREEN_LIGHTNESS = "custom_screen_lightness";
private static final boolean DEFAULT_CUSTOM_SCREEN_LIGHTNESS = false;
private static final String KEY_SCREEN_LIGHTNESS = "screen_lightness";
private static final int DEFAULT_SCREEN_LIGHTNESS = 50;
private static final boolean DEFAULT_MEDIA_SCAN = false;
private static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label";
private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null;
@@ -192,8 +135,6 @@ public class Settings {
private static final int DEFAULT_PRELOAD_IMAGE = 5;
private static final String KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image";
private static final boolean DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false;
private static final String KEY_READ_THEME = "read_theme";
private static final int DEFAULT_READ_THEME = 1;
/********************
****** Favorites
********************/
@@ -235,46 +176,17 @@ public class Settings {
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
private static final String KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
private static final int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
/********************
****** Analytics
********************/
private static final String KEY_ASK_ANALYTICS = "ask_analytics";
private static final boolean DEFAULT_ASK_ANALYTICS = true;
private static final boolean DEFAULT_ENABLE_ANALYTICS = false;
private static final String KEY_USER_ID = "user_id";
private static final String FILENAME_USER_ID = ".user_id";
private static final int LENGTH_USER_ID = 32;
/********************
****** Update
********************/
private static final String KEY_BETA_UPDATE_CHANNEL = "beta_update_channel";
private static final String KEY_SKIP_UPDATE_VERSION = "skip_update_version";
private static final int DEFAULT_SKIP_UPDATE_VERSION = 0;
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = true;
private static final String KEY_SAVE_CRASH_LOG = "save_crash_log";
private static final boolean DEFAULT_SAVE_CRASH_LOG = true;
private static final boolean DEFAULT_BUILT_IN_HOSTS = false;
private static final boolean DEFAULT_FRONTING = false;
private static final String DEFAULT_APP_LANGUAGE = "system";
private static final String KEY_PROXY_TYPE = "proxy_type";
private static final int DEFAULT_PROXY_TYPE = EhProxySelector.TYPE_SYSTEM;
private static final String KEY_PROXY_IP = "proxy_ip";
private static final String DEFAULT_PROXY_IP = null;
private static final String KEY_PROXY_PORT = "proxy_port";
private static final int DEFAULT_PROXY_PORT = -1;
/********************
****** Guide
********************/
private static final String KEY_GUIDE_QUICK_SEARCH = "guide_quick_search";
private static final boolean DEFAULT_GUIDE_QUICK_SEARCH = true;
private static final String KEY_GUIDE_COLLECTIONS = "guide_collections";
private static final boolean DEFAULT_GUIDE_COLLECTIONS = true;
private static final String KEY_GUIDE_DOWNLOAD_THUMB = "guide_download_thumb";
private static final boolean DEFAULT_GUIDE_DOWNLOAD_THUMB = true;
private static final String KEY_GUIDE_DOWNLOAD_LABELS = "guide_download_labels";
private static final boolean DEFAULT_GUIDE_DOWNLOAD_LABELS = true;
private static final String KEY_GUIDE_GALLERY = "guide_gallery";
private static final boolean DEFAULT_GUIDE_GALLERY = true;
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
private static final int DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0;
private static final String KEY_DOWNLOAD_DELAY = "download_delay";
@@ -282,20 +194,20 @@ public class Settings {
private static final String KEY_REQUEST_NEWS = "request_news";
private static final boolean DEFAULT_REQUEST_NEWS = true;
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
private static final String KEY_QS_SAVE_PROGRESS = "qs_save_progress";
private static final boolean DEFAULT_QS_SAVE_PROGRESS = true;
public static boolean LIST_THUMB_SIZE_INITED = false;
public static SharedPreferences sSettingsPre;
private static int LIST_THUMB_SIZE = 40;
private static Context sContext;
private static SharedPreferences sSettingsPre;
private static EhConfig sEhConfig;
public static void initialize(Context context) {
sContext = context.getApplicationContext();
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
public static void initialize() {
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(EhApplication.getApplication());
sEhConfig = loadEhConfig();
fixDefaultValue(context);
fixDefaultValue();
}
private static void fixDefaultValue(Context context) {
private static void fixDefaultValue() {
if ("CN".equals(Locale.getDefault().getCountry())) {
// Enable domain fronting if the country is CN
if (!sSettingsPre.contains(KEY_BUILT_IN_HOSTS)) {
@@ -312,16 +224,6 @@ public class Settings {
}
}
public static Locale getLocale() {
Locale locale = null;
String language = Settings.getAppLanguage();
if (language != null && !language.equals("system")) {
return Locale.forLanguageTag(language);
} else {
return Locale.getDefault();
}
}
private static EhConfig loadEhConfig() {
EhConfig ehConfig = new EhConfig();
ehConfig.imageSize = getImageResolution();
@@ -358,32 +260,6 @@ public class Settings {
sSettingsPre.edit().putInt(key, value).apply();
}
public static long getLong(String key, long defValue) {
try {
return sSettingsPre.getLong(key, defValue);
} catch (ClassCastException e) {
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
return defValue;
}
}
public static void putLong(String key, long value) {
sSettingsPre.edit().putLong(key, value).apply();
}
public static float getFloat(String key, float defValue) {
try {
return sSettingsPre.getFloat(key, defValue);
} catch (ClassCastException e) {
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
return defValue;
}
}
public static void putFloat(String key, float value) {
sSettingsPre.edit().putFloat(key, value).apply();
}
public static String getString(String key, String defValue) {
try {
return sSettingsPre.getString(key, defValue);
@@ -434,22 +310,10 @@ public class Settings {
putString(KEY_DISPLAY_NAME, value);
}
public static String getAvatar() {
return getString(KEY_AVATAR, DEFAULT_AVATAR);
}
public static void putAvatar(String value) {
putString(KEY_AVATAR, value);
}
public static boolean getShowWarning() {
return getBoolean(KEY_SHOW_WARNING, DEFAULT_SHOW_WARNING);
}
public static void putShowWarning(boolean value) {
putBoolean(KEY_SHOW_WARNING, value);
}
public static boolean getRemoveImageFiles() {
return getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES);
}
@@ -482,14 +346,6 @@ public class Settings {
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
}
public static void putTheme(int theme) {
putIntToStr(KEY_THEME, theme);
}
public static boolean getApplyNavBarThemeColor() {
return getBoolean(KEY_APPLY_NAV_BAR_THEME_COLOR, DEFAULT_APPLY_NAV_BAR_THEME_COLOR);
}
public static int getGallerySite() {
return getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE);
}
@@ -500,17 +356,13 @@ public class Settings {
public static String getLaunchPageGalleryListSceneAction() {
int value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE);
switch (value) {
default:
case 0:
return GalleryListScene.ACTION_HOMEPAGE;
case 1:
return GalleryListScene.ACTION_SUBSCRIPTION;
case 2:
return GalleryListScene.ACTION_WHATS_HOT;
case 3:
return GalleryListScene.ACTION_TOP_LIST;
}
return switch (value) {
case 0 -> GalleryListScene.ACTION_HOMEPAGE;
case 1 -> GalleryListScene.ACTION_SUBSCRIPTION;
case 2 -> GalleryListScene.ACTION_WHATS_HOT;
case 3 -> GalleryListScene.ACTION_TOP_LIST;
default -> throw new IllegalStateException("Unexpected value: " + value);
};
}
public static int getListMode() {
@@ -523,13 +375,11 @@ public class Settings {
@DimenRes
public static int getDetailSizeResId() {
switch (getDetailSize()) {
default:
case 0:
return R.dimen.gallery_list_column_width_long;
case 1:
return R.dimen.gallery_list_column_width_short;
}
return switch (getDetailSize()) {
case 0 -> R.dimen.gallery_list_column_width_long;
case 1 -> R.dimen.gallery_list_column_width_short;
default -> throw new IllegalStateException("Unexpected value: " + getDetailSize());
};
}
public static int getThumbSize() {
@@ -537,7 +387,7 @@ public class Settings {
}
public static int dip2px(int dpValue) {
final float scale = sContext.getResources().getDisplayMetrics().density;
final float scale = EhApplication.getApplication().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@@ -581,32 +431,14 @@ public class Settings {
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
}
public static void putDefaultCategories(int value) {
sEhConfig.defaultCategories = value;
sEhConfig.setDirty();
putInt(KEY_DEFAULT_CATEGORIES, value);
}
public static int getExcludedTagNamespaces() {
return getInt(KEY_EXCLUDED_TAG_NAMESPACES, DEFAULT_EXCLUDED_TAG_NAMESPACES);
}
public static void putExcludedTagNamespaces(int value) {
sEhConfig.excludedNamespaces = value;
sEhConfig.setDirty();
putInt(KEY_EXCLUDED_TAG_NAMESPACES, value);
}
public static String getExcludedLanguages() {
return getString(KEY_EXCLUDED_LANGUAGES, DEFAULT_EXCLUDED_LANGUAGES);
}
public static void putExcludedLanguages(String value) {
sEhConfig.excludedLanguages = value;
sEhConfig.setDirty();
putString(KEY_EXCLUDED_LANGUAGES, value);
}
public static boolean getMeteredNetworkWarning() {
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
}
@@ -619,152 +451,20 @@ public class Settings {
putBoolean(KEY_APP_LINK_VERIFY_TIP, value);
}
public static int getScreenRotation() {
return getIntFromStr(KEY_SCREEN_ROTATION, DEFAULT_SCREEN_ROTATION);
}
public static void putScreenRotation(int value) {
putIntToStr(KEY_SCREEN_ROTATION, value);
}
@GalleryView.LayoutMode
public static int getReadingDirection() {
return GalleryView.sanitizeLayoutMode(getIntFromStr(KEY_READING_DIRECTION, DEFAULT_READING_DIRECTION));
}
public static void putReadingDirection(int value) {
putIntToStr(KEY_READING_DIRECTION, value);
}
@GalleryView.ScaleMode
public static int getPageScaling() {
return GalleryView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
}
public static void putPageScaling(int value) {
putIntToStr(KEY_PAGE_SCALING, value);
}
@GalleryView.StartPosition
public static int getStartPosition() {
return GalleryView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
}
public static void putStartPosition(int value) {
putIntToStr(KEY_START_POSITION, value);
}
public static boolean getKeepScreenOn() {
return getBoolean(KEY_KEEP_SCREEN_ON, DEFAULT_KEEP_SCREEN_ON);
}
public static void putKeepScreenOn(boolean value) {
putBoolean(KEY_KEEP_SCREEN_ON, value);
}
public static boolean getShowClock() {
return getBoolean(KEY_SHOW_CLOCK, DEFAULT_SHOW_CLOCK);
}
public static void putShowClock(boolean value) {
putBoolean(KEY_SHOW_CLOCK, value);
}
public static boolean getShowProgress() {
return getBoolean(KEY_SHOW_PROGRESS, DEFAULT_SHOW_PROGRESS);
}
public static void putShowProgress(boolean value) {
putBoolean(KEY_SHOW_PROGRESS, value);
}
public static boolean getShowBattery() {
return getBoolean(KEY_SHOW_BATTERY, DEFAULT_SHOW_BATTERY);
}
public static void putShowBattery(boolean value) {
putBoolean(KEY_SHOW_BATTERY, value);
}
public static boolean getShowPageInterval() {
return getBoolean(KEY_SHOW_PAGE_INTERVAL, DEFAULT_SHOW_PAGE_INTERVAL);
}
public static void putShowPageInterval(boolean value) {
putBoolean(KEY_SHOW_PAGE_INTERVAL, value);
}
public static boolean getVolumePage() {
return getBoolean(KEY_VOLUME_PAGE, DEFAULT_VOLUME_PAGE);
}
public static void putVolumePage(boolean value) {
putBoolean(KEY_VOLUME_PAGE, value);
}
public static boolean getReverseVolumePage() {
return getBoolean(KEY_REVERSE_VOLUME_PAGE, DEFAULT_REVERSE_VOLUME_PAGE);
}
public static void putReverseVolumePage(boolean value) {
putBoolean(KEY_REVERSE_VOLUME_PAGE, value);
}
public static boolean getReadingFullscreen() {
return getBoolean(KEY_READING_FULLSCREEN, VALUE_READING_FULLSCREEN);
}
public static void putReadingFullscreen(boolean value) {
putBoolean(KEY_READING_FULLSCREEN, value);
}
public static boolean getCustomScreenLightness() {
return getBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, DEFAULT_CUSTOM_SCREEN_LIGHTNESS);
}
public static void putCustomScreenLightness(boolean value) {
putBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, value);
}
public static int getScreenLightness() {
return getInt(KEY_SCREEN_LIGHTNESS, DEFAULT_SCREEN_LIGHTNESS);
}
public static void putScreenLightness(int value) {
putInt(KEY_SCREEN_LIGHTNESS, value);
}
public static int getReadTheme() {
return getIntFromStr(KEY_READ_THEME, DEFAULT_READ_THEME);
}
public static void putReadTheme(int value) {
putIntToStr(KEY_READ_THEME, value);
}
public static boolean getEnabledSecurity() {
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
}
public static void putEnabledSecurity(boolean value) {
putBoolean(KEY_READING_FULLSCREEN, value);
}
@Nullable
public static UniFile getDownloadLocation() {
UniFile dir = null;
try {
Uri.Builder builder = new 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(sContext, builder.build());
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
UniFile dir;
Uri.Builder builder = new 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(EhApplication.getApplication(), builder.build());
return dir != null ? dir : UniFile.fromFile(AppConfig.getDefaultDownloadDir());
}
@@ -815,26 +515,14 @@ public class Settings {
return getIntFromStr(KEY_MULTI_THREAD_DOWNLOAD, DEFAULT_MULTI_THREAD_DOWNLOAD);
}
public static void putMultiThreadDownload(int value) {
putIntToStr(KEY_MULTI_THREAD_DOWNLOAD, value);
}
public static int getDownloadDelay() {
return getIntFromStr(KEY_DOWNLOAD_DELAY, DEFAULT_DOWNLOAD_DELAY);
}
public static void putDownloadDelay(int value) {
putIntToStr(KEY_DOWNLOAD_DELAY, value);
}
public static int getPreloadImage() {
return getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE);
}
public static void putPreloadImage(int value) {
putIntToStr(KEY_PRELOAD_IMAGE, value);
}
public static String getImageResolution() {
return getString(KEY_IMAGE_RESOLUTION, DEFAULT_IMAGE_RESOLUTION);
}
@@ -849,10 +537,6 @@ public class Settings {
return getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE);
}
public static void putDownloadOriginImage(boolean value) {
putBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, value);
}
public static String[] getFavCat() {
String[] favCat = new String[10];
favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0);
@@ -947,100 +631,18 @@ public class Settings {
putInt(KEY_DEFAULT_FAV_SLOT, value);
}
public static boolean getAskAnalytics() {
return getBoolean(KEY_ASK_ANALYTICS, DEFAULT_ASK_ANALYTICS);
public static boolean getQSSaveProgress() {
return getBoolean(KEY_QS_SAVE_PROGRESS, DEFAULT_QS_SAVE_PROGRESS);
}
public static void putAskAnalytics(boolean value) {
putBoolean(KEY_ASK_ANALYTICS, value);
}
public static boolean getEnableAnalytics() {
return getBoolean(KEY_ENABLE_ANALYTICS, DEFAULT_ENABLE_ANALYTICS);
}
public static void putEnableAnalytics(boolean value) {
putBoolean(KEY_ENABLE_ANALYTICS, value);
}
public static String getUserID() {
boolean writeXml = false;
boolean writeFile = false;
String userID = getString(KEY_USER_ID, null);
File file = AppConfig.getFileInExternalAppDir(FILENAME_USER_ID);
if (!isValidUserID(userID)) {
writeXml = true;
// Get use ID from out sd card file
userID = FileUtils.read(file);
if (!isValidUserID(userID)) {
writeFile = true;
userID = generateUserID();
}
} else {
writeFile = true;
}
if (writeXml) {
putString(KEY_USER_ID, userID);
}
if (writeFile) {
FileUtils.write(file, userID);
}
return userID;
}
@NonNull
private static String generateUserID() {
int length = LENGTH_USER_ID;
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
if (MathUtils.random(0, ('9' - '0' + 1) + ('z' - 'a' + 1)) <= '9' - '0') {
sb.append((char) MathUtils.random('0', '9' + 1));
} else {
sb.append((char) MathUtils.random('a', 'z' + 1));
}
}
return sb.toString();
}
private static boolean isValidUserID(@Nullable String userID) {
if (null == userID || LENGTH_USER_ID != userID.length()) {
return false;
}
for (int i = 0; i < LENGTH_USER_ID; i++) {
char ch = userID.charAt(i);
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z')) {
return false;
}
}
return true;
}
public static void putBetaUpdateChannel(boolean value) {
putBoolean(KEY_BETA_UPDATE_CHANNEL, value);
}
public static int getSkipUpdateVersion() {
return getInt(KEY_SKIP_UPDATE_VERSION, DEFAULT_SKIP_UPDATE_VERSION);
}
public static void putSkipUpdateVersion(int value) {
putInt(KEY_SKIP_UPDATE_VERSION, value);
public static void putQSSaveProgress(boolean value) {
putBoolean(KEY_QS_SAVE_PROGRESS, value);
}
public static boolean getSaveParseErrorBody() {
return getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY);
}
public static void putSaveParseErrorBody(boolean value) {
putBoolean(KEY_SAVE_PARSE_ERROR_BODY, value);
}
public static boolean getSaveCrashLog() {
return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG);
}
@@ -1057,10 +659,6 @@ public class Settings {
return getInt(KEY_SECURITY_DELAY, 0);
}
public static boolean getEnableFingerprint() {
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
}
public static int getReadCacheSize() {
return getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE);
}
@@ -1081,14 +679,6 @@ public class Settings {
putBoolean(KEY_DOMAIN_FRONTING, value);
}
public static String getAppLanguage() {
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
}
public static void putAppLanguage(String value) {
putString(KEY_APP_LANGUAGE, value);
}
public static int getProxyType() {
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
}
@@ -1113,46 +703,6 @@ public class Settings {
putInt(KEY_PROXY_PORT, value);
}
public static boolean getGuideQuickSearch() {
return getBoolean(KEY_GUIDE_QUICK_SEARCH, DEFAULT_GUIDE_QUICK_SEARCH);
}
public static void putGuideQuickSearch(boolean value) {
putBoolean(KEY_GUIDE_QUICK_SEARCH, value);
}
public static boolean getGuideCollections() {
return getBoolean(KEY_GUIDE_COLLECTIONS, DEFAULT_GUIDE_COLLECTIONS);
}
public static void putGuideCollections(boolean value) {
putBoolean(KEY_GUIDE_COLLECTIONS, value);
}
public static boolean getGuideDownloadThumb() {
return getBoolean(KEY_GUIDE_DOWNLOAD_THUMB, DEFAULT_GUIDE_DOWNLOAD_THUMB);
}
public static void putGuideDownloadThumb(boolean value) {
putBoolean(KEY_GUIDE_DOWNLOAD_THUMB, value);
}
public static boolean getGuideDownloadLabels() {
return getBoolean(KEY_GUIDE_DOWNLOAD_LABELS, DEFAULT_GUIDE_DOWNLOAD_LABELS);
}
public static void puttGuideDownloadLabels(boolean value) {
putBoolean(KEY_GUIDE_DOWNLOAD_LABELS, value);
}
public static boolean getGuideGallery() {
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
}
public static void putGuideGallery(boolean value) {
putBoolean(KEY_GUIDE_GALLERY, value);
}
public static int getClipboardTextHashCode() {
return getInt(KEY_CLIPBOARD_TEXT_HASH_CODE, DEFAULT_CLIPBOARD_TEXT_HASH_CODE);
}
@@ -29,11 +29,12 @@ import androidx.browser.customtabs.CustomTabsIntent;
import com.hippo.ehviewer.client.EhUrlOpener;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
import com.hippo.scene.StageActivity;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
public final class UrlOpener {
private UrlOpener() {
@@ -56,19 +57,19 @@ public final class UrlOpener {
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
if (result != null) {
if (result.gid == galleryDetail.gid) {
intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(GalleryActivity.KEY_PAGE, result.page);
intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(ReaderActivity.KEY_PAGE, result.page);
context.startActivity(intent);
return;
}
} else if (url.startsWith("#c")) {
try {
intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(GalleryActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(ReaderActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
context.startActivity(intent);
return;
} catch (NumberFormatException e) {
@@ -1,212 +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.content.Context;
import android.os.AsyncTask;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.client.exception.CancelledException;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.yorozuya.SimpleHandler;
import java.io.File;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.Call;
import okhttp3.OkHttpClient;
public class EhClient {
public static final String TAG = EhClient.class.getSimpleName();
public static final int METHOD_SIGN_IN = 0;
public static final int METHOD_GET_GALLERY_LIST = 1;
public static final int METHOD_GET_GALLERY_DETAIL = 3;
public static final int METHOD_GET_PREVIEW_SET = 4;
public static final int METHOD_GET_RATE_GALLERY = 5;
public static final int METHOD_GET_COMMENT_GALLERY = 6;
public static final int METHOD_GET_GALLERY_TOKEN = 7;
public static final int METHOD_GET_FAVORITES = 8;
public static final int METHOD_ADD_FAVORITES = 9;
public static final int METHOD_ADD_FAVORITES_RANGE = 10;
public static final int METHOD_MODIFY_FAVORITES = 11;
public static final int METHOD_GET_TORRENT_LIST = 12;
public static final int METHOD_GET_PROFILE = 14;
public static final int METHOD_VOTE_COMMENT = 15;
public static final int METHOD_IMAGE_SEARCH = 16;
public static final int METHOD_ARCHIVE_LIST = 17;
public static final int METHOD_DOWNLOAD_ARCHIVE = 18;
public static final int METHOD_VOTE_TAG = 19;
private final ThreadPoolExecutor mRequestThreadPool;
private final OkHttpClient mOkHttpClient;
public EhClient(Context context) {
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
mOkHttpClient = EhApplication.getOkHttpClient();
}
public void execute(EhRequest request) {
if (!request.isCancelled()) {
Task task = new Task(request.getMethod(), request.getCallback(), request.getEhConfig());
task.executeOnExecutor(mRequestThreadPool, request.getArgs());
request.task = task;
} else {
request.getCallback().onCancel();
}
}
public interface Callback<E> {
void onSuccess(E result);
void onFailure(Exception e);
void onCancel();
}
public class Task extends AsyncTask<Object, Void, Object> {
private final int mMethod;
private final AtomicReference<Call> mCall = new AtomicReference<>();
private final AtomicBoolean mStop = new AtomicBoolean();
private Callback mCallback;
private EhConfig mEhConfig;
public Task(int method, Callback callback, EhConfig ehConfig) {
mMethod = method;
mCallback = callback;
mEhConfig = ehConfig;
}
// Called in Job thread
public void setCall(Call call) throws CancelledException {
if (mStop.get()) {
// Stopped Job thread
throw new CancelledException();
} else {
mCall.lazySet(call);
}
}
public EhConfig getEhConfig() {
return mEhConfig;
}
public void stop() {
if (!mStop.get()) {
mStop.lazySet(true);
if (mCallback != null) {
// TODO Avoid new runnable
SimpleHandler.getInstance().post(mCallback::onCancel);
}
Status status = getStatus();
if (status == Status.PENDING) {
cancel(false);
} else if (status == Status.RUNNING) {
// It is running, cancel call if it is created
Call call = mCall.get();
if (call != null) {
call.cancel();
}
}
// Clear
mCallback = null;
mEhConfig = null;
mCall.lazySet(null);
}
}
@Override
protected Object doInBackground(Object... params) {
try {
switch (mMethod) {
case METHOD_SIGN_IN:
return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
case METHOD_GET_GALLERY_LIST:
return EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_GALLERY_DETAIL:
return EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_PREVIEW_SET:
return EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_RATE_GALLERY:
return EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
case METHOD_GET_COMMENT_GALLERY:
return EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
case METHOD_GET_GALLERY_TOKEN:
return EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
case METHOD_GET_FAVORITES:
return EhEngine.getFavorites(this, mOkHttpClient, (String) params[0], (Boolean) params[1]);
case METHOD_ADD_FAVORITES:
return EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
case METHOD_ADD_FAVORITES_RANGE:
return EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
case METHOD_MODIFY_FAVORITES:
return EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2], (Boolean) params[3]);
case METHOD_GET_TORRENT_LIST:
return EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_GET_PROFILE:
return EhEngine.getProfile(this, mOkHttpClient);
case METHOD_VOTE_COMMENT:
return EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
case METHOD_IMAGE_SEARCH:
return EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
case METHOD_ARCHIVE_LIST:
return EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_DOWNLOAD_ARCHIVE:
return EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
case METHOD_VOTE_TAG:
return EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
default:
return new IllegalStateException("Can't detect method " + mMethod);
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
return e;
}
}
@SuppressWarnings("unchecked")
@Override
protected void onPostExecute(Object result) {
if (mCallback != null) {
//noinspection StatementWithEmptyBody
if (!(result instanceof CancelledException)) {
if (result instanceof Exception) {
mCallback.onFailure((Exception) result);
} else {
mCallback.onSuccess(result);
}
} else {
// onCancel is called in stop
}
}
// Clear
mCallback = null;
mEhConfig = null;
mCall.lazySet(null);
}
}
}
@@ -0,0 +1,196 @@
/*
* 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.okHttpClient
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import java.io.File
object EhClient {
internal fun enqueue(request: EhRequest, scope: CoroutineScope) {
check(!request.isActive) // Abort if attempt to execute an active request
request.job = scope.launchIO {
val method = request.method
val params = request.args
val callback: Callback<Any?>? = request.callback
try {
val result: Any? = when (method) {
METHOD_SIGN_IN -> EhEngine.signIn(
mOkHttpClient,
params!![0] as String,
params[1] as String
)
METHOD_GET_GALLERY_LIST -> EhEngine.getGalleryList(
mOkHttpClient,
params!![0] as String
)
METHOD_GET_GALLERY_DETAIL -> EhEngine.getGalleryDetail(
mOkHttpClient,
params!![0] as String?
)
METHOD_GET_PREVIEW_SET -> EhEngine.getPreviewSet(
mOkHttpClient,
params!![0] as String?
)
METHOD_GET_RATE_GALLERY -> EhEngine.rateGallery(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as Float
)
METHOD_GET_COMMENT_GALLERY -> EhEngine.commentGallery(
mOkHttpClient,
params!![0] as String?,
params[1] as String,
params[2] as String?
)
METHOD_GET_GALLERY_TOKEN -> EhEngine.getGalleryToken(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Int
)
METHOD_GET_FAVORITES -> EhEngine.getFavorites(
mOkHttpClient,
params!![0] as String
)
METHOD_ADD_FAVORITES -> EhEngine.addFavorites(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Int,
params[3] as String?
)
METHOD_ADD_FAVORITES_RANGE -> @Suppress("UNCHECKED_CAST") EhEngine.addFavoritesRange(
mOkHttpClient,
params!![0] as LongArray,
params[1] as Array<String?>,
params[2] as Int
)
METHOD_MODIFY_FAVORITES -> EhEngine.modifyFavorites(
mOkHttpClient,
params!![0] as String,
params[1] as LongArray,
params[2] as Int
)
METHOD_GET_TORRENT_LIST -> EhEngine.getTorrentList(
mOkHttpClient,
params!![0] as String?,
params[1] as Long,
params[2] as String?
)
METHOD_GET_PROFILE -> EhEngine.getProfile(mOkHttpClient)
METHOD_VOTE_COMMENT -> EhEngine.voteComment(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as Long,
params[5] as Int
)
METHOD_IMAGE_SEARCH -> EhEngine.imageSearch(
mOkHttpClient,
params!![0] as File,
params[1] as Boolean,
params[2] as Boolean,
params[3] as Boolean
)
METHOD_ARCHIVE_LIST -> EhEngine.getArchiveList(
mOkHttpClient,
params!![0] as String?,
params[1] as Long,
params[2] as String?
)
METHOD_DOWNLOAD_ARCHIVE -> EhEngine.downloadArchive(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as String?,
params[3] as String?,
params[4] as Boolean
)
METHOD_VOTE_TAG -> EhEngine.voteTag(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as String?,
params[5] as Int
)
METHOD_GET_UCONFIG -> EhEngine.getUConfig(mOkHttpClient)
else -> throw IllegalStateException("Can't detect method $method")
}
withUIContext { callback?.onSuccess(result) }
} catch (e: Exception) {
if (e is CancellationException)
throw e // Don't catch coroutine CancellationException
e.printStackTrace()
withUIContext { callback?.onFailure(e) }
}
}
}
interface Callback<E> {
fun onSuccess(result: E)
fun onFailure(e: Exception)
fun onCancel()
}
const val METHOD_SIGN_IN = 0
const val METHOD_GET_GALLERY_LIST = 1
const val METHOD_GET_GALLERY_DETAIL = 3
const val METHOD_GET_PREVIEW_SET = 4
const val METHOD_GET_RATE_GALLERY = 5
const val METHOD_GET_COMMENT_GALLERY = 6
const val METHOD_GET_GALLERY_TOKEN = 7
const val METHOD_GET_FAVORITES = 8
const val METHOD_ADD_FAVORITES = 9
const val METHOD_ADD_FAVORITES_RANGE = 10
const val METHOD_MODIFY_FAVORITES = 11
const val METHOD_GET_TORRENT_LIST = 12
const val METHOD_GET_PROFILE = 14
const val METHOD_VOTE_COMMENT = 15
const val METHOD_IMAGE_SEARCH = 16
const val METHOD_ARCHIVE_LIST = 17
const val METHOD_DOWNLOAD_ARCHIVE = 18
const val METHOD_VOTE_TAG = 19
const val METHOD_GET_UCONFIG = 20
private val mOkHttpClient = okHttpClient
}
@@ -22,7 +22,7 @@ import okhttp3.Dns
import java.net.InetAddress
import java.net.UnknownHostException
class EhDns : Dns {
object EhDns : Dns {
private val hosts = EhApplication.hosts
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
@@ -42,7 +42,7 @@ class EhDns : Dns {
put(
builtInHosts,
"e-hentai.org",
Pair("104.20.134.21", true),
Pair("104.20.134.21", false),
Pair("104.20.135.21", false),
Pair("172.67.0.127", false),
)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -18,12 +18,16 @@ package com.hippo.ehviewer.client;
import android.util.Log;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.dao.Filter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class EhFilter {
@@ -31,41 +35,43 @@ public final class EhFilter {
public static final int MODE_UPLOADER = 1;
public static final int MODE_TAG = 2;
public static final int MODE_TAG_NAMESPACE = 3;
public static final int MODE_COMMENTER = 4;
public static final int MODE_COMMENT = 5;
private static final String TAG = EhFilter.class.getSimpleName();
private static EhFilter sInstance;
private final List<Filter> mTitleFilterList = new ArrayList<>();
private final List<Filter> mUploaderFilterList = new ArrayList<>();
private final List<Filter> mTagFilterList = new ArrayList<>();
private final List<Filter> mTagNamespaceFilterList = new ArrayList<>();
private final List<Filter> mCommenterFilterList = new ArrayList<>();
private final List<Filter> mCommentFilterList = new ArrayList<>();
private EhFilter() {
List<Filter> list = EhDB.getAllFilter();
for (int i = 0, n = list.size(); i < n; i++) {
Filter filter = list.get(i);
switch (filter.mode) {
case MODE_TITLE:
case MODE_TITLE -> {
filter.text = filter.text.toLowerCase();
mTitleFilterList.add(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.add(filter);
break;
case MODE_TAG:
}
case MODE_TAG -> {
filter.text = filter.text.toLowerCase();
mTagFilterList.add(filter);
break;
case MODE_TAG_NAMESPACE:
}
case MODE_TAG_NAMESPACE -> {
filter.text = filter.text.toLowerCase();
mTagNamespaceFilterList.add(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
}
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
case MODE_COMMENT -> mCommentFilterList.add(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
}
}
public static EhFilter getInstance() {
public static @NonNull EhFilter getInstance() {
if (sInstance == null) {
sInstance = new EhFilter();
}
@@ -88,30 +94,36 @@ public final class EhFilter {
return mTagNamespaceFilterList;
}
public List<Filter> getCommenterFilterList() {
return mCommenterFilterList;
}
public List<Filter> getCommentFilterList() {
return mCommentFilterList;
}
public synchronized boolean addFilter(Filter filter) {
// enable filter by default before it is added to database
filter.enable = true;
if (!EhDB.addFilter(filter)) return false;
switch (filter.mode) {
case MODE_TITLE:
case MODE_TITLE -> {
filter.text = filter.text.toLowerCase();
mTitleFilterList.add(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.add(filter);
break;
case MODE_TAG:
}
case MODE_TAG -> {
filter.text = filter.text.toLowerCase();
mTagFilterList.add(filter);
break;
case MODE_TAG_NAMESPACE:
}
case MODE_TAG_NAMESPACE -> {
filter.text = filter.text.toLowerCase();
mTagNamespaceFilterList.add(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
}
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
case MODE_COMMENT -> mCommentFilterList.add(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
return true;
}
@@ -124,21 +136,13 @@ public final class EhFilter {
EhDB.deleteFilter(filter);
switch (filter.mode) {
case MODE_TITLE:
mTitleFilterList.remove(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.remove(filter);
break;
case MODE_TAG:
mTagFilterList.remove(filter);
break;
case MODE_TAG_NAMESPACE:
mTagNamespaceFilterList.remove(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
case MODE_TITLE -> mTitleFilterList.remove(filter);
case MODE_TAG -> mTagFilterList.remove(filter);
case MODE_TAG_NAMESPACE -> mTagNamespaceFilterList.remove(filter);
case MODE_UPLOADER -> mUploaderFilterList.remove(filter);
case MODE_COMMENTER -> mCommenterFilterList.remove(filter);
case MODE_COMMENT -> mCommentFilterList.remove(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
}
@@ -272,4 +276,40 @@ public final class EhFilter {
return true;
}
public synchronized boolean filterCommenter(String commenter) {
if (null == commenter) {
return false;
}
List<Filter> filters = mCommenterFilterList;
if (filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && commenter.equals(filters.get(i).text)) {
return false;
}
}
}
return true;
}
public synchronized boolean filterComment(String comment) {
if (null == comment) {
return false;
}
List<Filter> filters = mCommentFilterList;
if (filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable) {
Pattern p = Pattern.compile(filters.get(i).text);
Matcher m = p.matcher(comment);
if (m.find()) return false;
}
}
}
return true;
}
}
@@ -1,79 +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.Settings;
public class EhRequest {
EhClient.Task task;
private int mMethod;
private Object[] mArgs;
private EhClient.Callback mCallback;
private EhConfig mEhConfig;
private boolean mCancel = false;
public int getMethod() {
return mMethod;
}
public EhRequest setMethod(int method) {
mMethod = method;
return this;
}
public Object[] getArgs() {
return mArgs;
}
public EhRequest setArgs(Object... args) {
mArgs = args;
return this;
}
public EhClient.Callback getCallback() {
return mCallback;
}
public EhRequest setCallback(EhClient.Callback callback) {
mCallback = callback;
return this;
}
public EhConfig getEhConfig() {
return mEhConfig != null ? mEhConfig : Settings.getEhConfig();
}
public EhRequest setEhConfig(EhConfig ehConfig) {
mEhConfig = ehConfig;
return this;
}
public void cancel() {
if (!mCancel) {
mCancel = true;
if (task != null) {
task.stop();
task = null;
}
}
}
public boolean isCancelled() {
return mCancel;
}
}
@@ -0,0 +1,80 @@
/*
* 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.app.Activity
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
class EhRequest {
internal var job: Job? = null
val isActive
get() = job?.isActive ?: false
var method = 0
private set
var args: Array<out Any>? = null
private set
var callback: EhClient.Callback<Any?>? = null
private set
fun setMethod(method: Int): EhRequest {
this.method = method
return this
}
fun setArgs(vararg args: Any): EhRequest {
this.args = args
return this
}
@Suppress("UNCHECKED_CAST")
fun setCallback(callback: EhClient.Callback<*>?): EhRequest {
this.callback = callback as EhClient.Callback<Any?>?
return this
}
fun enqueue(scope: CoroutineScope) {
EhClient.enqueue(this, scope)
}
@DelicateCoroutinesApi
fun enqueue() {
EhClient.enqueue(this, GlobalScope)
}
fun enqueue(fragment: Fragment) {
enqueue(fragment.lifecycleScope)
}
fun enqueue(activity: Activity) {
check(activity is FragmentActivity)
enqueue(activity.lifecycleScope)
}
@MainThread
fun cancel() {
if (isActive) {
job?.cancel()
callback?.onCancel()
}
}
}
@@ -23,7 +23,7 @@ import java.net.Socket
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
class EhSSLSocketFactory : SSLSocketFactory() {
object EhSSLSocketFactory : SSLSocketFactory() {
private val sslSocketFactory = getDefault() as SSLSocketFactory
override fun getDefaultCipherSuites(): Array<String> {
return sslSocketFactory.defaultCipherSuites
@@ -1,436 +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;
import android.content.Context;
import android.util.Base64;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSource;
import okio.Okio;
public class EhTagDatabase {
private static final Map<String, String> NAMESPACE_TO_PREFIX = new HashMap<>();
// TODO more lock for different language
private static final Lock lock = new ReentrantLock();
private static volatile EhTagDatabase instance;
static {
NAMESPACE_TO_PREFIX.put("artist", "a:");
NAMESPACE_TO_PREFIX.put("cosplayer", "cos:");
NAMESPACE_TO_PREFIX.put("character", "c:");
NAMESPACE_TO_PREFIX.put("female", "f:");
NAMESPACE_TO_PREFIX.put("group", "g:");
NAMESPACE_TO_PREFIX.put("language", "l:");
NAMESPACE_TO_PREFIX.put("male", "m:");
NAMESPACE_TO_PREFIX.put("mixed", "x:");
NAMESPACE_TO_PREFIX.put("other", "o:");
NAMESPACE_TO_PREFIX.put("parody", "p:");
NAMESPACE_TO_PREFIX.put("reclass", "r:");
}
private final String name;
private final byte[] tags;
public EhTagDatabase(String name, BufferedSource source) throws IOException {
this.name = name;
String[] tmp;
StringBuilder buffer = new StringBuilder();
source.readInt();
for (String i : source.readUtf8().split("\n")) {
tmp = i.split("\r", 2);
buffer.append(tmp[0]);
buffer.append("\r");
try {
buffer.append(new String(Base64.decode(tmp[1], Base64.DEFAULT), StandardCharsets.UTF_8));
} catch (Exception e) {
buffer.append(tmp[1]);
}
buffer.append("\n");
}
byte[] b = buffer.toString().getBytes(StandardCharsets.UTF_8);
int totalBytes = b.length;
tags = new byte[totalBytes];
System.arraycopy(b, 0, tags, 0, totalBytes);
}
@Nullable
public static EhTagDatabase getInstance(Context context) {
if (isPossible(context)) {
return instance;
} else {
instance = null;
return null;
}
}
@Nullable
public static String namespaceToPrefix(String namespace) {
return NAMESPACE_TO_PREFIX.get(namespace);
}
private static String[] getMetadata(Context context) {
String[] metadata = context.getResources().getStringArray(R.array.tag_translation_metadata);
if (metadata.length == 4) {
return metadata;
} else {
return null;
}
}
public static boolean isPossible(Context context) {
return getMetadata(context) != null;
}
@Nullable
private static byte[] getFileContent(File file, int length) {
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
byte[] content = new byte[length];
source.readFully(content);
return content;
} catch (IOException e) {
return null;
}
}
@Nullable
private static byte[] getFileSha1(File file) {
try (InputStream is = new FileInputStream(file)) {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
int n;
byte[] buffer = new byte[4 * 1024];
while ((n = is.read(buffer)) != -1) {
digest.update(buffer, 0, n);
}
return digest.digest();
} catch (IOException | NoSuchAlgorithmException e) {
return null;
}
}
private static boolean equals(byte[] b1, byte[] b2) {
if (b1 == null && b2 == null) {
return true;
}
if (b1 == null || b2 == null) {
return false;
}
if (b1.length != b2.length) {
return false;
}
for (int i = 0; i < b1.length; i++) {
if (b1[i] != b2[i]) {
return false;
}
}
return true;
}
private static boolean checkData(File sha1File, File dataFile) {
byte[] s1 = getFileContent(sha1File, 20);
if (s1 == null) {
return false;
}
byte[] s2 = getFileSha1(dataFile);
if (s2 == null) {
return false;
}
return equals(s1, s2);
}
private static boolean save(OkHttpClient client, String url, File file) {
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
try (Response response = call.execute()) {
if (!response.isSuccessful()) {
return false;
}
ResponseBody body = response.body();
if (body == null) {
return false;
}
try (InputStream is = body.byteStream(); OutputStream os = new FileOutputStream(file)) {
IOUtils.copy(is, os);
}
return true;
} catch (Throwable t) {
t.printStackTrace();
ExceptionUtils.throwIfFatal(t);
return false;
}
}
public static void update(Context context) {
String[] urls = getMetadata(context);
if (urls == null || urls.length != 4) {
// Clear tags if it's not possible
instance = null;
return;
}
String sha1Name = urls[0];
String sha1Url = urls[1];
String dataName = urls[2];
String dataUrl = urls[3];
// Clear tags if name if different
EhTagDatabase tmp = instance;
if (tmp != null && !tmp.name.equals(dataName)) {
instance = null;
}
IoThreadPoolExecutor.getInstance().execute(() -> {
if (!lock.tryLock()) {
return;
}
try {
File dir = AppConfig.getFilesDir("tag-translations");
if (dir == null) {
return;
}
// Check current sha1 and current data
File sha1File = new File(dir, sha1Name);
File dataFile = new File(dir, dataName);
if (!checkData(sha1File, dataFile)) {
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
}
// Read current EhTagDatabase
if (instance == null && dataFile.exists()) {
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
instance = new EhTagDatabase(dataName, source);
} catch (IOException e) {
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
}
}
OkHttpClient client = EhApplication.getOkHttpClient();
// Save new sha1
File tempSha1File = new File(dir, sha1Name + ".tmp");
if (!save(client, sha1Url, tempSha1File)) {
FileUtils.delete(tempSha1File);
return;
}
// Check new sha1 and current data
if (checkData(tempSha1File, dataFile)) {
// The data is the same
FileUtils.delete(tempSha1File);
return;
}
// Save new data
File tempDataFile = new File(dir, dataName + ".tmp");
if (!save(client, dataUrl, tempDataFile)) {
FileUtils.delete(tempDataFile);
return;
}
// Check new sha1 and new data
if (!checkData(tempSha1File, tempDataFile)) {
FileUtils.delete(tempSha1File);
FileUtils.delete(tempDataFile);
return;
}
// Replace current sha1 and current data with new sha1 and new data
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
tempSha1File.renameTo(sha1File);
tempDataFile.renameTo(dataFile);
// Read new EhTagDatabase
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
instance = new EhTagDatabase(dataName, source);
} catch (IOException e) {
// Ignore
}
} finally {
lock.unlock();
}
});
}
public String getTranslation(String tag) {
return search(tags, tag.getBytes(StandardCharsets.UTF_8));
}
@Nullable
private String search(byte[] tags, byte[] tag) {
int low = 0;
int high = tags.length;
while (low < high) {
int start = (low + high) / 2;
// Look for the starting '\n'
while (start > -1 && tags[start] != '\n') {
start--;
}
start++;
// Look for the middle '\r'.
int middle = 1;
while (tags[start + middle] != '\r') {
middle++;
}
// Look for the ending '\n'
int end = middle + 1;
while (tags[start + end] != '\n') {
end++;
}
int compare;
int tagIndex = 0;
int curIndex = start;
for (; ; ) {
int tagByte = tag[tagIndex] & 0xff;
int curByte = tags[curIndex] & 0xff;
compare = tagByte - curByte;
if (compare != 0) {
break;
}
tagIndex++;
curIndex++;
if (tagIndex == tag.length && curIndex == start + middle) {
break;
}
if (tagIndex == tag.length) {
compare = -1;
break;
}
if (curIndex == start + middle) {
compare = 1;
break;
}
}
if (compare < 0) {
high = start - 1;
} else if (compare > 0) {
low = start + end + 1;
} else {
return new String(tags, start + middle + 1, end - middle - 1, StandardCharsets.UTF_8);
}
}
return null;
}
public ArrayList<Pair<String, String>> suggest(String keyword) {
return searchTag(tags, keyword);
}
private ArrayList<Pair<String, String>> searchTag(byte[] tags, String keyword) {
ArrayList<Pair<String, String>> searchHints = new ArrayList<>();
int begin = 0;
while (begin < tags.length - 1) {
int start = begin;
// Look for the starting '\n'
while (tags[start] != '\n') {
start++;
}
// Look for the middle '\r'.
int middle = 1;
while (tags[start + middle] != '\r') {
middle++;
}
// Look for the ending '\n'
int end = middle + 1;
while (tags[start + end] != '\n') {
end++;
}
begin = start + end;
byte[] hintBytes = new byte[end - middle - 1];
System.arraycopy(tags, start + middle + 1, hintBytes, 0, end - middle - 1);
String hint = new String(hintBytes, StandardCharsets.UTF_8);
byte[] tagBytes = new byte[middle];
System.arraycopy(tags, start + 1, tagBytes, 0, middle);
String tag = new String(tagBytes, StandardCharsets.UTF_8);
int index = tag.indexOf(':');
boolean keywordMatches;
if (index == -1 || index >= tag.length() - 1 || keyword.length() > 2) {
keywordMatches = containsIgnoreSpace(tag, keyword);
} else {
keywordMatches = containsIgnoreSpace(tag.substring(index + 1), keyword);
}
if (keywordMatches || containsIgnoreSpace(hint, keyword)) {
Pair<String, String> pair = new Pair<>(hint, tag);
if (!searchHints.contains(pair)) {
searchHints.add(pair);
}
}
if (searchHints.size() > 20) {
break;
}
}
return searchHints;
}
private boolean containsIgnoreSpace(String text, String key) {
return text.replace(" ", "").contains(key.replace(" ", ""));
}
}
@@ -0,0 +1,269 @@
/*
* 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
import android.content.Context
import com.hippo.ehviewer.AppConfig
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
import com.hippo.ehviewer.R
import com.hippo.util.ExceptionUtils
import com.hippo.util.HashCodeUtils
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IOUtils
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.BufferedSource
import okio.buffer
import okio.source
import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
object EhTagDatabase {
private lateinit var tags: JSONObject
private lateinit var tagList: ArrayList<Pair<String, String>>
fun isInitialized(): Boolean {
return this::tags.isInitialized && this::tagList.isInitialized
}
private fun updateData(source: BufferedSource) {
try {
val tmpTags = JSONObject(source.readString(StandardCharsets.UTF_8))
val tmpTagList = arrayListOf<Pair<String, String>>()
tmpTags.keys().forEachRemaining { prefix: String ->
val values = tmpTags.optJSONObject(prefix)
values?.let {
it.keys().forEachRemaining { tag: String ->
tmpTagList.add(Pair("$prefix:$tag", values.optString(tag)))
}
}
}
tags = tmpTags
tagList = tmpTagList
} catch (e: JSONException) {
e.printStackTrace()
}
}
fun getTranslation(prefix: String?, tag: String?): String? {
return tags.optJSONObject(prefix)?.optString(tag)?.trim()?.takeIf { it.isNotEmpty() }
}
/* Construct a cold flow for tag database suggestions */
fun suggestFlow(
keyword: String,
translate: Boolean,
exactly: Boolean = false
): Flow<Pair<String?, String>> = flow {
tagList.forEach {
val tag = it.first
val hint = if (translate) it.second else null
val index = tag.indexOf(':')
if (tag.substring(index + 1).trim() == keyword) {
if (exactly)
emit(Pair(hint, tag))
return@forEach
}
if (exactly)
return@forEach
val keywordMatches: Boolean =
if (index == -1 || index >= tag.length - 1 || keyword.length > 2) {
containsIgnoreSpace(tag, keyword)
} else {
containsIgnoreSpace(tag.substring(index + 1), keyword)
}
if (keywordMatches || containsIgnoreSpace(hint, keyword)) {
emit(Pair(hint, tag))
}
}
}
private fun containsIgnoreSpace(text: String?, key: String): Boolean {
return text != null && text.replace(" ", "").contains(key.replace(" ", ""))
}
private val NAMESPACE_TO_PREFIX = HashMap<String, String>().also {
it["artist"] = "a"
it["cosplayer"] = "cos"
it["character"] = "c"
it["female"] = "f"
it["group"] = "g"
it["language"] = "l"
it["male"] = "m"
it["mixed"] = "x"
it["other"] = "o"
it["parody"] = "p"
it["reclass"] = "r"
}
@JvmStatic
fun namespaceToPrefix(namespace: String): String? {
return NAMESPACE_TO_PREFIX[namespace]
}
private fun getMetadata(context: Context): Array<String>? {
return context.resources.getStringArray(R.array.tag_translation_metadata)
.takeIf { it.size == 4 }
}
fun isTranslatable(context: Context): Boolean {
return context.resources.getBoolean(R.bool.tag_translatable)
}
private fun getFileContent(file: File): String? {
try {
file.source().buffer().use { return it.readString(StandardCharsets.UTF_8) }
} catch (e: IOException) {
return null
}
}
private fun getFileSha1(file: File): String? {
try {
FileInputStream(file).use { stream ->
val digest = MessageDigest.getInstance("SHA-1")
var n: Int
val buffer = ByteArray(4 * 1024)
while (stream.read(buffer).also { n = it } != -1) {
digest.update(buffer, 0, n)
}
return HashCodeUtils.bytesToHexString(digest.digest())
}
} catch (e: IOException) {
return null
} catch (e: NoSuchAlgorithmException) {
return null
}
}
private fun checkData(sha1File: File, dataFile: File): Boolean {
val s1 = getFileContent(sha1File) ?: return false
val s2 = getFileSha1(dataFile) ?: return false
return s1 == s2
}
private fun save(client: OkHttpClient, url: String, file: File): Boolean {
val request: Request = Request.Builder().url(url).build()
val call = client.newCall(request)
try {
call.execute().use { response ->
if (!response.isSuccessful) {
return false
}
val body = response.body
body.byteStream()
.use { ins -> FileOutputStream(file).use { os -> IOUtils.copy(ins, os) } }
return true
}
} catch (t: Throwable) {
FileUtils.delete(file)
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
return false
}
}
@OptIn(DelicateCoroutinesApi::class)
@JvmStatic
fun update() {
launchIO {
updateInternal()
}
}
@Synchronized
private fun updateInternal() {
val urls = getMetadata(EhApplication.application)
urls?.let {
val sha1Name = urls[0]
val sha1Url = urls[1]
val dataName = urls[2]
val dataUrl = urls[3]
try {
val dir = AppConfig.getFilesDir("tag-translations")
checkNotNull(dir)
// Check current sha1 and current data
val sha1File = File(dir, sha1Name)
val dataFile = File(dir, dataName)
if (!checkData(sha1File, dataFile)) {
FileUtils.delete(sha1File)
FileUtils.delete(dataFile)
}
// Read current EhTagDatabase
if (!isInitialized() && dataFile.exists()) {
try {
dataFile.source().buffer().use { updateData(it) }
} catch (e: IOException) {
FileUtils.delete(sha1File)
FileUtils.delete(dataFile)
}
}
val client = okHttpClient
// Save new sha1
val tempSha1File = File(dir, "$sha1Name.tmp")
check(save(client, sha1Url, tempSha1File))
// Check new sha1 and current data
if (checkData(tempSha1File, dataFile)) {
// The data is the same
FileUtils.delete(tempSha1File)
return
}
// Save new data
val tempDataFile = File(dir, "$dataName.tmp")
check(save(client, dataUrl, tempDataFile))
// Check new sha1 and new data
if (!checkData(tempSha1File, tempDataFile)) {
FileUtils.delete(tempSha1File)
FileUtils.delete(tempDataFile)
return
}
// Replace current sha1 and current data with new sha1 and new data
FileUtils.delete(sha1File)
FileUtils.delete(dataFile)
tempSha1File.renameTo(sha1File)
tempDataFile.renameTo(dataFile)
// Read new EhTagDatabase
try {
dataFile.source().buffer().use { updateData(it) }
} catch (_: IOException) {
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}
@@ -16,7 +16,6 @@
package com.hippo.ehviewer.client;
import android.content.Context;
import android.graphics.Color;
import android.text.TextUtils;
@@ -49,10 +48,10 @@ public class EhUtils {
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
public static final Pattern PATTERN_TITLE_PREFIX = Pattern.compile(
"^(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*");
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*");
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
public static final Pattern PATTERN_TITLE_SUFFIX = Pattern.compile(
"(?:\\s+ch.[\\s\\d-]+)?(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*$",
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
Pattern.CASE_INSENSITIVE);
private static final int[] CATEGORY_VALUES = {
@@ -106,40 +105,29 @@ public class EhUtils {
}
public static int getCategoryColor(int category) {
switch (category) {
case EhConfig.DOUJINSHI:
return BG_COLOR_DOUJINSHI;
case EhConfig.MANGA:
return BG_COLOR_MANGA;
case EhConfig.ARTIST_CG:
return BG_COLOR_ARTIST_CG;
case EhConfig.GAME_CG:
return BG_COLOR_GAME_CG;
case EhConfig.WESTERN:
return BG_COLOR_WESTERN;
case EhConfig.NON_H:
return BG_COLOR_NON_H;
case EhConfig.IMAGE_SET:
return BG_COLOR_IMAGE_SET;
case EhConfig.COSPLAY:
return BG_COLOR_COSPLAY;
case EhConfig.ASIAN_PORN:
return BG_COLOR_ASIAN_PORN;
case EhConfig.MISC:
return BG_COLOR_MISC;
default:
return BG_COLOR_UNKNOWN;
}
return switch (category) {
case EhConfig.DOUJINSHI -> BG_COLOR_DOUJINSHI;
case EhConfig.MANGA -> BG_COLOR_MANGA;
case EhConfig.ARTIST_CG -> BG_COLOR_ARTIST_CG;
case EhConfig.GAME_CG -> BG_COLOR_GAME_CG;
case EhConfig.WESTERN -> BG_COLOR_WESTERN;
case EhConfig.NON_H -> BG_COLOR_NON_H;
case EhConfig.IMAGE_SET -> BG_COLOR_IMAGE_SET;
case EhConfig.COSPLAY -> BG_COLOR_COSPLAY;
case EhConfig.ASIAN_PORN -> BG_COLOR_ASIAN_PORN;
case EhConfig.MISC -> BG_COLOR_MISC;
default -> BG_COLOR_UNKNOWN;
};
}
public static void signOut(Context context) {
public static void signOut() {
EhApplication.getEhCookieStore().signOut();
Settings.putAvatar(null);
Settings.putDisplayName(null);
Settings.putNeedSignIn(true);
}
public static boolean needSignedIn(Context context) {
public static boolean needSignedIn() {
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore().hasSignedIn();
}
@@ -22,7 +22,7 @@ import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
@SuppressLint("CustomX509TrustManager")
class EhX509TrustManager : X509TrustManager {
object EhX509TrustManager : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}
@@ -1,150 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.network.UrlBuilder;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class FavListUrlBuilder implements Parcelable {
public static final int FAV_CAT_ALL = -1;
public static final int FAV_CAT_LOCAL = -2;
public static final Parcelable.Creator<FavListUrlBuilder> CREATOR = new Parcelable.Creator<FavListUrlBuilder>() {
@Override
public FavListUrlBuilder createFromParcel(Parcel source) {
return new FavListUrlBuilder(source);
}
@Override
public FavListUrlBuilder[] newArray(int size) {
return new FavListUrlBuilder[size];
}
};
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
private String mPrev;
private String mNext;
private String mJumpTo;
private String mKeyword;
private int mFavCat = FAV_CAT_ALL;
public FavListUrlBuilder() {
}
protected FavListUrlBuilder(Parcel in) {
this.mPrev = in.readString();
this.mNext = in.readString();
this.mJumpTo = in.readString();
this.mKeyword = in.readString();
this.mFavCat = in.readInt();
}
public static boolean isValidFavCat(int favCat) {
return favCat >= 0 && favCat <= 9;
}
public void setPrev(String prev) {
mPrev = prev;
}
public void setNext(String next) {
mNext = next;
}
public void setJumpTo(String jumpTo) {
mJumpTo = jumpTo;
}
public String getKeyword() {
return mKeyword;
}
public void setKeyword(String keyword) {
mKeyword = keyword;
}
public int getFavCat() {
return mFavCat;
}
public void setFavCat(int favCat) {
mFavCat = favCat;
}
public boolean isLocalFavCat() {
return mFavCat == FAV_CAT_LOCAL;
}
public String build() {
UrlBuilder ub = new UrlBuilder(EhUrl.getFavoritesUrl());
if (isValidFavCat(mFavCat)) {
ub.addQuery("favcat", Integer.toString(mFavCat));
} else if (mFavCat == FAV_CAT_ALL) {
ub.addQuery("favcat", "all");
}
if (!TextUtils.isEmpty(mKeyword)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
} else {
try {
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8.displayName()));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
// Name
ub.addQuery("sn", "on");
// Tags
ub.addQuery("st", "on");
// Note
ub.addQuery("sf", "on");
}
if (mPrev != null && !mPrev.isEmpty()) {
ub.addQuery("prev", mPrev);
}
if (mNext != null && !mNext.isEmpty()) {
ub.addQuery("next", mNext);
}
if (mJumpTo != null && !mJumpTo.isEmpty()) {
ub.addQuery("seek", mJumpTo);
}
return ub.build();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.mPrev);
dest.writeString(this.mNext);
dest.writeString(this.mJumpTo);
dest.writeString(this.mKeyword);
dest.writeInt(this.mFavCat);
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import com.hippo.ehviewer.client.EhUrl
import com.hippo.network.UrlBuilder
import com.hippo.util.encodeUTF8
import kotlinx.parcelize.Parcelize
@Parcelize
class FavListUrlBuilder(
private var mPrev: String? = null,
private var mNext: String? = null,
var jumpTo: String? = null,
var keyword: String? = null,
var favCat: Int = FAV_CAT_ALL,
) : Parcelable {
fun setIndex(index: String?, isNext: Boolean) {
mNext = index.takeIf { isNext }
mPrev = index.takeUnless { isNext }
}
fun build(): String {
val ub = UrlBuilder(EhUrl.getFavoritesUrl())
if (isValidFavCat(favCat)) {
ub.addQuery("favcat", favCat.toString())
} else if (favCat == FAV_CAT_ALL) {
ub.addQuery("favcat", "all")
}
keyword?.takeIf { it.isNotBlank() }?.let { ub.addQuery("f_search", encodeUTF8(it)) }
mPrev?.takeIf { it.isNotEmpty() }?.let { ub.addQuery("prev", it) }
mNext?.takeIf { it.isNotEmpty() }?.let { ub.addQuery("next", it) }
jumpTo?.takeIf { it.isNotEmpty() }?.let { ub.addQuery("seek", it) }
return ub.build()
}
companion object {
const val FAV_CAT_ALL = -1
const val FAV_CAT_LOCAL = -2
fun isValidFavCat(favCat: Int): Boolean {
return favCat in 0..9
}
}
}
@@ -1,98 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
public class GalleryApiInfo implements Parcelable {
public static final Parcelable.Creator<GalleryApiInfo> CREATOR = new Parcelable.Creator<GalleryApiInfo>() {
@Override
public GalleryApiInfo createFromParcel(Parcel source) {
return new GalleryApiInfo(source);
}
@Override
public GalleryApiInfo[] newArray(int size) {
return new GalleryApiInfo[size];
}
};
public long gid;
public String token;
public String archiverKey;
public String title;
public String titleJpn;
public int category;
public String thumb;
public String uploader;
public long posted;
public int filecount;
public long filesize;
public boolean expunged;
public float rating;
public int torrentcount;
@Nullable
public String[] tags;
public GalleryApiInfo() {
}
protected GalleryApiInfo(Parcel in) {
this.gid = in.readLong();
this.token = in.readString();
this.archiverKey = in.readString();
this.title = in.readString();
this.titleJpn = in.readString();
this.category = in.readInt();
this.thumb = in.readString();
this.uploader = in.readString();
this.posted = in.readLong();
this.filecount = in.readInt();
this.filesize = in.readLong();
this.expunged = in.readByte() != 0;
this.rating = in.readFloat();
this.torrentcount = in.readInt();
this.tags = in.createStringArray();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.gid);
dest.writeString(this.token);
dest.writeString(this.archiverKey);
dest.writeString(this.title);
dest.writeString(this.titleJpn);
dest.writeInt(this.category);
dest.writeString(this.thumb);
dest.writeString(this.uploader);
dest.writeLong(this.posted);
dest.writeInt(this.filecount);
dest.writeLong(this.filesize);
dest.writeByte(expunged ? (byte) 1 : (byte) 0);
dest.writeFloat(this.rating);
dest.writeInt(this.torrentcount);
dest.writeStringArray(this.tags);
}
}
@@ -13,24 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
package com.hippo.ehviewer.ui.fragment;
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.R;
public class ReadFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.read_settings);
}
@Override
public int getFragmentTitle() {
return R.string.settings_read;
}
}
@Parcelize
class GalleryApiInfo(
var gid: Long = 0,
var token: String? = null,
var archiverKey: String? = null,
var title: String? = null,
var titleJpn: String? = null,
var category: Int = 0,
var thumb: String? = null,
var uploader: String? = null,
var posted: Long = 0,
var filecount: Int = 0,
var filesize: Long = 0,
var expunged: Boolean = false,
var rating: Float = 0f,
var torrentcount: Int = 0,
var tags: Array<String>?
) : Parcelable
@@ -1,90 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
public class GalleryComment implements Parcelable {
public static final Creator<GalleryComment> CREATOR = new Creator<GalleryComment>() {
@Override
public GalleryComment createFromParcel(Parcel source) {
return new GalleryComment(source);
}
@Override
public GalleryComment[] newArray(int size) {
return new GalleryComment[size];
}
};
// 0 for uploader comment. can't vote
public long id;
public int score;
public boolean editable;
public boolean voteUpAble;
public boolean voteUpEd;
public boolean voteDownAble;
public boolean voteDownEd;
public boolean uploader;
public String voteState;
public long time;
public String user;
public String comment;
public long lastEdited;
public GalleryComment() {
}
protected GalleryComment(Parcel in) {
this.id = in.readLong();
this.score = in.readInt();
this.editable = in.readByte() != 0;
this.voteUpAble = in.readByte() != 0;
this.voteUpEd = in.readByte() != 0;
this.voteDownAble = in.readByte() != 0;
this.voteDownEd = in.readByte() != 0;
this.uploader = in.readByte() != 0;
this.voteState = in.readString();
this.time = in.readLong();
this.user = in.readString();
this.comment = in.readString();
this.lastEdited = in.readLong();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.id);
dest.writeInt(this.score);
dest.writeByte(editable ? (byte) 1 : (byte) 0);
dest.writeByte(voteUpAble ? (byte) 1 : (byte) 0);
dest.writeByte(voteUpEd ? (byte) 1 : (byte) 0);
dest.writeByte(voteDownAble ? (byte) 1 : (byte) 0);
dest.writeByte(voteDownEd ? (byte) 1 : (byte) 0);
dest.writeByte(uploader ? (byte) 1 : (byte) 0);
dest.writeString(this.voteState);
dest.writeLong(this.time);
dest.writeString(this.user);
dest.writeString(this.comment);
dest.writeLong(this.lastEdited);
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
class GalleryComment(
// 0 for uploader comment. can't vote
@JvmField
var id: Long = 0,
@JvmField
var score: Int = 0,
@JvmField
var editable: Boolean = false,
@JvmField
var voteUpAble: Boolean = false,
@JvmField
var voteUpEd: Boolean = false,
@JvmField
var voteDownAble: Boolean = false,
@JvmField
var voteDownEd: Boolean = false,
@JvmField
var uploader: Boolean = false,
@JvmField
var voteState: String? = null,
@JvmField
var time: Long = 0,
@JvmField
var user: String? = null,
@JvmField
var comment: String? = null,
@JvmField
var lastEdited: Long = 0
) : Parcelable
@@ -1,65 +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.data;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
public class GalleryCommentList implements Parcelable {
public static final Creator<GalleryCommentList> CREATOR = new Creator<GalleryCommentList>() {
@Override
public GalleryCommentList createFromParcel(Parcel in) {
return new GalleryCommentList(in);
}
@Override
public GalleryCommentList[] newArray(int size) {
return new GalleryCommentList[size];
}
};
public GalleryComment[] comments;
public boolean hasMore;
public GalleryCommentList(GalleryComment[] comments, boolean hasMore) {
this.comments = comments;
this.hasMore = hasMore;
}
protected GalleryCommentList(Parcel in) {
Parcelable[] array = in.readParcelableArray(getClass().getClassLoader());
if (array != null) {
comments = Arrays.copyOf(array, array.length, GalleryComment[].class);
} else {
comments = null;
}
hasMore = in.readByte() != 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelableArray(comments, flags);
dest.writeByte(hasMore ? (byte) 1 : (byte) 0);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 Hippo Seven
* 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.
@@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
package com.hippo.ehviewer.client.exception;
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
public class EhException extends Exception {
public EhException(String detailMessage) {
super(detailMessage);
}
public EhException(String detailMessage, Throwable cause) {
super(detailMessage, cause);
}
}
@Parcelize
class GalleryCommentList(
@JvmField
var comments: Array<GalleryComment>?,
@JvmField
var hasMore: Boolean
) : Parcelable
@@ -1,111 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Arrays;
public class GalleryDetail extends GalleryInfo {
public static final Creator<GalleryDetail> CREATOR = new Creator<GalleryDetail>() {
@Override
public GalleryDetail createFromParcel(Parcel source) {
return new GalleryDetail(source);
}
@Override
public GalleryDetail[] newArray(int size) {
return new GalleryDetail[size];
}
};
public long apiUid = -1L;
public String apiKey;
public int torrentCount;
public String torrentUrl;
public String archiveUrl;
public String parent;
public ArrayList<GalleryInfo> newerVersions = new ArrayList<>();
public String visible;
public String language;
public String size;
public int pages;
public int favoriteCount;
public boolean isFavorited;
public int ratingCount;
public GalleryTagGroup[] tags;
public GalleryCommentList comments;
public int previewPages;
public PreviewSet previewSet;
public GalleryDetail() {
}
protected GalleryDetail(Parcel in) {
super(in);
this.torrentCount = in.readInt();
this.torrentUrl = in.readString();
this.archiveUrl = in.readString();
this.parent = in.readString();
//noinspection unchecked
this.newerVersions = in.readArrayList(GalleryInfo.class.getClassLoader());
this.visible = in.readString();
this.language = in.readString();
this.size = in.readString();
this.pages = in.readInt();
this.favoriteCount = in.readInt();
this.isFavorited = in.readByte() != 0;
this.ratingCount = in.readInt();
Parcelable[] array = in.readParcelableArray(GalleryTagGroup.class.getClassLoader());
if (array != null) {
this.tags = Arrays.copyOf(array, array.length, GalleryTagGroup[].class);
} else {
this.tags = null;
}
this.comments = in.readParcelable(getClass().getClassLoader());
this.previewPages = in.readInt();
this.previewSet = in.readParcelable(PreviewSet.class.getClassLoader());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.torrentCount);
dest.writeString(this.torrentUrl);
dest.writeString(this.archiveUrl);
dest.writeString(this.parent);
dest.writeList(this.newerVersions);
dest.writeString(this.visible);
dest.writeString(this.language);
dest.writeString(this.size);
dest.writeInt(this.pages);
dest.writeInt(this.favoriteCount);
dest.writeByte(isFavorited ? (byte) 1 : (byte) 0);
dest.writeInt(this.ratingCount);
dest.writeParcelableArray(this.tags, flags);
dest.writeParcelable(this.comments, flags);
dest.writeInt(this.previewPages);
dest.writeParcelable(previewSet, flags);
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
// This is wrong Parcelize usage, TODO: use delegate and move galleryinfo to primary constructor
class GalleryDetail(
@JvmField
var apiUid: Long = -1L,
@JvmField
var apiKey: String? = null,
@JvmField
var torrentCount: Int = 0,
@JvmField
var torrentUrl: String? = null,
@JvmField
var archiveUrl: String? = null,
@JvmField
var parent: String? = null,
@JvmField
var newerVersions: ArrayList<GalleryInfo> = ArrayList(),
@JvmField
var visible: String? = null,
@JvmField
var language: String? = null,
@JvmField
var size: String? = null,
@JvmField
var favoriteCount: Int = 0,
@JvmField
var isFavorited: Boolean = false,
@JvmField
var ratingCount: Int = 0,
@JvmField
var tags: Array<GalleryTagGroup>? = null,
@JvmField
var comments: GalleryCommentList? = null,
@JvmField
var previewPages: Int = 0,
@JvmField
var previewSet: PreviewSet? = null,
) : GalleryInfo()
@@ -1,247 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import java.util.regex.Pattern;
public class GalleryInfo implements Parcelable {
/**
* ISO 639-1
*/
@SuppressWarnings("unused")
public static final String S_LANG_JA = "JA";
public static final String S_LANG_EN = "EN";
public static final String S_LANG_ZH = "ZH";
public static final String S_LANG_NL = "NL";
public static final String S_LANG_FR = "FR";
public static final String S_LANG_DE = "DE";
public static final String S_LANG_HU = "HU";
public static final String S_LANG_IT = "IT";
public static final String S_LANG_KO = "KO";
public static final String S_LANG_PL = "PL";
public static final String S_LANG_PT = "PT";
public static final String S_LANG_RU = "RU";
public static final String S_LANG_ES = "ES";
public static final String S_LANG_TH = "TH";
public static final String S_LANG_VI = "VI";
public static final String[] S_LANGS = {
S_LANG_EN,
S_LANG_ZH,
S_LANG_ES,
S_LANG_KO,
S_LANG_RU,
S_LANG_FR,
S_LANG_PT,
S_LANG_TH,
S_LANG_DE,
S_LANG_IT,
S_LANG_VI,
S_LANG_PL,
S_LANG_HU,
S_LANG_NL,
};
public static final Pattern[] S_LANG_PATTERNS = {
Pattern.compile("[(\\[]eng(?:lish)?[)\\]]|英訳", Pattern.CASE_INSENSITIVE),
// [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
Pattern.compile("[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]korean?[)\\]]|韓国翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]portuguese|ポルトガル翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]german[)\\]]|ドイツ翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]italiano?[)\\]]|イタリア翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]polish[)\\]]|ポーランド翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE),
};
public static final String[] S_LANG_TAGS = {
"language:english",
"language:chinese",
"language:spanish",
"language:korean",
"language:russian",
"language:french",
"language:portuguese",
"language:thai",
"language:german",
"language:italian",
"language:vietnamese",
"language:polish",
"language:hungarian",
"language:dutch",
};
public static final Parcelable.Creator<GalleryInfo> CREATOR = new Parcelable.Creator<GalleryInfo>() {
@Override
public GalleryInfo createFromParcel(Parcel source) {
return new GalleryInfo(source);
}
@Override
public GalleryInfo[] newArray(int size) {
return new GalleryInfo[size];
}
};
@PrimaryKey
@ColumnInfo(name = "GID")
public long gid;
@ColumnInfo(name = "TOKEN")
public String token;
@ColumnInfo(name = "TITLE")
public String title;
@ColumnInfo(name = "TITLE_JPN")
public String titleJpn;
@ColumnInfo(name = "THUMB")
public String thumb;
@ColumnInfo(name = "CATEGORY")
public int category;
@ColumnInfo(name = "POSTED")
public String posted;
@ColumnInfo(name = "UPLOADER")
public String uploader;
@Ignore
public boolean disowned;
@ColumnInfo(name = "RATING")
public float rating;
@Ignore
public boolean rated;
@Nullable
@Ignore
public String[] simpleTags;
@Ignore
public int pages;
@Ignore
public int thumbWidth;
@Ignore
public int thumbHeight;
@Ignore
public int spanSize;
@Ignore
public int spanIndex;
@Ignore
public int spanGroupIndex;
/**
* language from title
*/
@ColumnInfo(name = "SIMPLE_LANGUAGE")
public String simpleLanguage;
@Ignore
public int favoriteSlot = -2;
@Ignore
public String favoriteName;
public GalleryInfo() {
}
protected GalleryInfo(Parcel in) {
this.gid = in.readLong();
this.token = in.readString();
this.title = in.readString();
this.titleJpn = in.readString();
this.thumb = in.readString();
this.category = in.readInt();
this.posted = in.readString();
this.uploader = in.readString();
this.rating = in.readFloat();
this.rated = in.readByte() != 0;
this.simpleLanguage = in.readString();
this.simpleTags = in.createStringArray();
this.thumbWidth = in.readInt();
this.thumbHeight = in.readInt();
this.spanSize = in.readInt();
this.spanIndex = in.readInt();
this.spanGroupIndex = in.readInt();
this.favoriteSlot = in.readInt();
this.favoriteName = in.readString();
this.disowned = in.readByte() != 0;
}
public final void generateSLang() {
if (simpleTags != null) {
generateSLangFromTags();
}
if (simpleLanguage == null && title != null) {
generateSLangFromTitle();
}
}
private void generateSLangFromTags() {
assert simpleTags != null;
for (String tag : simpleTags) {
for (int i = 0; i < S_LANGS.length; i++) {
if (S_LANG_TAGS[i].equals(tag)) {
simpleLanguage = S_LANGS[i];
return;
}
}
}
}
private void generateSLangFromTitle() {
for (int i = 0; i < S_LANGS.length; i++) {
if (S_LANG_PATTERNS[i].matcher(title).find()) {
simpleLanguage = S_LANGS[i];
return;
}
}
simpleLanguage = null;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.gid);
dest.writeString(this.token);
dest.writeString(this.title);
dest.writeString(this.titleJpn);
dest.writeString(this.thumb);
dest.writeInt(this.category);
dest.writeString(this.posted);
dest.writeString(this.uploader);
dest.writeFloat(this.rating);
dest.writeByte(this.rated ? (byte) 1 : (byte) 0);
dest.writeString(this.simpleLanguage);
dest.writeStringArray(this.simpleTags);
dest.writeInt(this.thumbWidth);
dest.writeInt(this.thumbHeight);
dest.writeInt(this.spanSize);
dest.writeInt(this.spanIndex);
dest.writeInt(this.spanGroupIndex);
dest.writeInt(this.favoriteSlot);
dest.writeString(this.favoriteName);
dest.writeByte(this.disowned ? (byte) 1 : (byte) 0);
}
}
@@ -0,0 +1,222 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Ignore
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import java.util.regex.Pattern
@Parcelize
open class GalleryInfo(
@JvmField
@PrimaryKey
@ColumnInfo(name = "GID")
var gid: Long = 0,
@JvmField
@ColumnInfo(name = "TOKEN")
var token: String? = null,
@JvmField
@ColumnInfo(name = "TITLE")
var title: String? = null,
@JvmField
@ColumnInfo(name = "TITLE_JPN")
var titleJpn: String? = null,
@JvmField
@ColumnInfo(name = "THUMB")
var thumb: String? = null,
@JvmField
@ColumnInfo(name = "CATEGORY")
var category: Int = 0,
@JvmField
@ColumnInfo(name = "POSTED")
var posted: String? = null,
@JvmField
@ColumnInfo(name = "UPLOADER")
var uploader: String? = null,
@JvmField
@Ignore
var disowned: Boolean = false,
@JvmField
@ColumnInfo(name = "RATING")
var rating: Float = 0f,
@JvmField
@Ignore
var rated: Boolean = false,
@JvmField
@Ignore
var simpleTags: Array<String>? = null,
@JvmField
@Ignore
var pages: Int = 0,
@JvmField
@Ignore
var thumbWidth: Int = 0,
@JvmField
@Ignore
var thumbHeight: Int = 0,
@Ignore
var spanSize: Int = 0,
@Ignore
var spanIndex: Int = 0,
@Ignore
var spanGroupIndex: Int = 0,
/**
* language from title
*/
@JvmField
@ColumnInfo(name = "SIMPLE_LANGUAGE")
var simpleLanguage: String? = null,
@JvmField
@Ignore
var favoriteSlot: Int = -2,
@JvmField
@Ignore
var favoriteName: String? = null
) : Parcelable {
fun generateSLang() {
simpleLanguage = simpleTags?.let { generateSLangFromTags(it) } ?: title?.let { generateSLangFromTitle(it) }
}
private fun generateSLangFromTags(simpleTags: Array<String>): String? {
for (tag in simpleTags) {
for (i in S_LANGS.indices) {
if (S_LANG_TAGS[i] == tag) {
return S_LANGS[i]
}
}
}
return null
}
private fun generateSLangFromTitle(title: String): String? {
for (i in S_LANGS.indices) {
if (S_LANG_PATTERNS[i].matcher(title).find()) {
return S_LANGS[i]
}
}
return null
}
companion object {
/**
* ISO 639-1
*/
const val S_LANG_JA = "JA"
const val S_LANG_EN = "EN"
const val S_LANG_ZH = "ZH"
const val S_LANG_NL = "NL"
const val S_LANG_FR = "FR"
const val S_LANG_DE = "DE"
const val S_LANG_HU = "HU"
const val S_LANG_IT = "IT"
const val S_LANG_KO = "KO"
const val S_LANG_PL = "PL"
const val S_LANG_PT = "PT"
const val S_LANG_RU = "RU"
const val S_LANG_ES = "ES"
const val S_LANG_TH = "TH"
const val S_LANG_VI = "VI"
@JvmField
val S_LANGS = arrayOf(
S_LANG_EN,
S_LANG_ZH,
S_LANG_ES,
S_LANG_KO,
S_LANG_RU,
S_LANG_FR,
S_LANG_PT,
S_LANG_TH,
S_LANG_DE,
S_LANG_IT,
S_LANG_VI,
S_LANG_PL,
S_LANG_HU,
S_LANG_NL
)
val S_LANG_PATTERNS = arrayOf(
Pattern.compile(
"[(\\[]eng(?:lish)?[)\\]]|英訳",
Pattern.CASE_INSENSITIVE
), // [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
Pattern.compile(
"[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳",
Pattern.CASE_INSENSITIVE
),
Pattern.compile(
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳",
Pattern.CASE_INSENSITIVE
),
Pattern.compile("[(\\[]korean?[)\\]]|韓国翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]portuguese|ポルトガル翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile(
"[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳",
Pattern.CASE_INSENSITIVE
),
Pattern.compile("[(\\[]german[)\\]]|ドイツ翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]italiano?[)\\]]|イタリア翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile(
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳",
Pattern.CASE_INSENSITIVE
),
Pattern.compile("[(\\[]polish[)\\]]|ポーランド翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE)
)
val S_LANG_TAGS = arrayOf(
"language:english",
"language:chinese",
"language:spanish",
"language:korean",
"language:russian",
"language:french",
"language:portuguese",
"language:thai",
"language:german",
"language:italian",
"language:vietnamese",
"language:polish",
"language:hungarian",
"language:dutch"
)
}
}
@@ -1,83 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import com.hippo.widget.LoadImageView;
public class GalleryPreview implements Parcelable {
public static final Parcelable.Creator<GalleryPreview> CREATOR = new Parcelable.Creator<GalleryPreview>() {
@Override
public GalleryPreview createFromParcel(Parcel source) {
return new GalleryPreview(source);
}
@Override
public GalleryPreview[] newArray(int size) {
return new GalleryPreview[size];
}
};
String imageKey;
String imageUrl;
String pageUrl;
int position;
int offsetX = Integer.MIN_VALUE;
int offsetY = Integer.MIN_VALUE;
int clipWidth = Integer.MIN_VALUE;
int clipHeight = Integer.MIN_VALUE;
public GalleryPreview() {
}
protected GalleryPreview(Parcel in) {
this.imageUrl = in.readString();
this.pageUrl = in.readString();
this.position = in.readInt();
this.offsetX = in.readInt();
this.offsetY = in.readInt();
this.clipWidth = in.readInt();
this.clipHeight = in.readInt();
}
public int getPosition() {
return position;
}
public void load(LoadImageView view) {
view.setClip(offsetX, offsetY, clipWidth, clipHeight);
view.load(imageKey, imageUrl);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.imageUrl);
dest.writeString(this.pageUrl);
dest.writeInt(this.position);
dest.writeInt(this.offsetX);
dest.writeInt(this.offsetY);
dest.writeInt(this.clipWidth);
dest.writeInt(this.clipHeight);
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import com.hippo.widget.LoadImageView
import kotlinx.parcelize.Parcelize
@Parcelize
class GalleryPreview(
@JvmField
var imageKey: String? = null,
@JvmField
var imageUrl: String? = null,
@JvmField
var pageUrl: String? = null,
@JvmField
var position: Int = 0,
@JvmField
var offsetX: Int = Int.MIN_VALUE,
@JvmField
var offsetY: Int = Int.MIN_VALUE,
@JvmField
var clipWidth: Int = Int.MIN_VALUE,
@JvmField
var clipHeight: Int = Int.MIN_VALUE,
) : Parcelable {
fun load(view: LoadImageView) {
view.setClip(offsetX, offsetY, clipWidth, clipHeight)
view.load(imageKey!!, imageUrl!!)
}
}
@@ -1,72 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
// TODO Add url field?
public class GalleryTagGroup implements Parcelable {
public static final Parcelable.Creator<GalleryTagGroup> CREATOR = new Parcelable.Creator<GalleryTagGroup>() {
@Override
public GalleryTagGroup createFromParcel(Parcel source) {
return new GalleryTagGroup(source);
}
@Override
public GalleryTagGroup[] newArray(int size) {
return new GalleryTagGroup[size];
}
};
private final ArrayList<String> mTagList;
public String groupName;
public GalleryTagGroup() {
mTagList = new ArrayList<>();
}
protected GalleryTagGroup(Parcel in) {
this.groupName = in.readString();
this.mTagList = in.createStringArrayList();
}
public void addTag(String tag) {
mTagList.add(tag);
}
public int size() {
return mTagList.size();
}
public String getTagAt(int position) {
return mTagList.get(position);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.groupName);
dest.writeStringList(this.mTagList);
}
}
@@ -13,24 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
package com.hippo.ehviewer.client.exception;
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
public class ParseException extends EhException {
private final String mBody;
public ParseException(String detailMessage, String body) {
super(detailMessage);
mBody = body;
@Parcelize
class GalleryTagGroup(
private val mTagList: ArrayList<String> = arrayListOf(),
@JvmField
var groupName: String? = null
) : Parcelable {
fun addTag(tag: String) {
mTagList.add(tag)
}
public ParseException(String detailMessage, String body, Throwable cause) {
super(detailMessage, cause);
mBody = body;
fun size(): Int {
return mTagList.size
}
public String getBody() {
return mBody;
fun getTagAt(position: Int): String {
return mTagList[position]
}
}
}
@@ -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.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.widget.LoadImageView;
import com.hippo.yorozuya.collect.IntList;
import java.util.ArrayList;
public class LargePreviewSet extends PreviewSet {
public static final Parcelable.Creator<LargePreviewSet> CREATOR = new Parcelable.Creator<LargePreviewSet>() {
@Override
public LargePreviewSet createFromParcel(Parcel source) {
return new LargePreviewSet(source);
}
@Override
public LargePreviewSet[] newArray(int size) {
return new LargePreviewSet[size];
}
};
private final IntList mPositionList;
private final ArrayList<String> mImageUrlList;
private final ArrayList<String> mPageUrlList;
public LargePreviewSet() {
mPositionList = new IntList();
mImageUrlList = new ArrayList<>();
mPageUrlList = new ArrayList<>();
}
protected LargePreviewSet(Parcel in) {
this.mPositionList = in.readParcelable(IntList.class.getClassLoader());
this.mImageUrlList = in.createStringArrayList();
this.mPageUrlList = in.createStringArrayList();
}
public void addItem(int index, String imageUrl, String pageUrl) {
mPositionList.add(index);
mImageUrlList.add(imageUrl);
mPageUrlList.add(pageUrl);
}
@Override
public int size() {
return mImageUrlList.size();
}
@Override
public int getPosition(int index) {
return mPositionList.get(index);
}
@Override
public String getPageUrlAt(int index) {
return mPageUrlList.get(index);
}
@Override
public GalleryPreview getGalleryPreview(long gid, int index) {
GalleryPreview galleryPreview = new GalleryPreview();
galleryPreview.position = mPositionList.get(index);
galleryPreview.imageKey = EhCacheKeyFactory.getLargePreviewKey(gid, galleryPreview.position);
galleryPreview.imageUrl = mImageUrlList.get(index);
galleryPreview.pageUrl = mPageUrlList.get(index);
return galleryPreview;
}
@Override
public void load(LoadImageView view, long gid, int index) {
view.resetClip();
view.load(EhCacheKeyFactory.getLargePreviewKey(gid, mPositionList.get(index)),
mImageUrlList.get(index));
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(this.mPositionList, flags);
dest.writeStringList(this.mImageUrlList);
dest.writeStringList(this.mPageUrlList);
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import com.hippo.ehviewer.client.EhCacheKeyFactory
import com.hippo.widget.LoadImageView
import com.hippo.yorozuya.collect.IntList
import kotlinx.parcelize.Parcelize
@Parcelize
class LargePreviewSet(
private val mPositionList: IntList = IntList(),
private val mImageUrlList: ArrayList<String> = ArrayList(),
private val mPageUrlList: ArrayList<String> = ArrayList()
) : PreviewSet() {
fun addItem(index: Int, imageUrl: String, pageUrl: String) {
mPositionList.add(index)
mImageUrlList.add(imageUrl)
mPageUrlList.add(pageUrl)
}
override fun size(): Int {
return mImageUrlList.size
}
override fun getPosition(index: Int): Int {
return mPositionList[index]
}
override fun getPageUrlAt(index: Int): String {
return mPageUrlList[index]
}
override fun getGalleryPreview(gid: Long, index: Int): GalleryPreview {
val galleryPreview = GalleryPreview()
galleryPreview.position = mPositionList[index]
galleryPreview.imageKey = EhCacheKeyFactory.getLargePreviewKey(gid, galleryPreview.position)
galleryPreview.imageUrl = mImageUrlList[index]
galleryPreview.pageUrl = mPageUrlList[index]
return galleryPreview
}
override fun load(view: LoadImageView, gid: Long, index: Int) {
view.resetClip()
view.load(
EhCacheKeyFactory.getLargePreviewKey(gid, mPositionList[index]),
mImageUrlList[index]
)
}
}
@@ -15,9 +15,7 @@
*/
package com.hippo.ehviewer.client.data
import android.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator
import android.text.TextUtils
import androidx.annotation.IntDef
import com.hippo.ehviewer.client.EhConfig
@@ -26,54 +24,36 @@ import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.dao.QuickSearch
import com.hippo.ehviewer.widget.AdvanceSearchTable
import com.hippo.network.UrlBuilder
import com.hippo.util.encodeUTF8
import com.hippo.yorozuya.NumberUtils
import com.hippo.yorozuya.StringUtils
import kotlinx.parcelize.Parcelize
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import java.net.URLEncoder
open class ListUrlBuilder : Cloneable, Parcelable {
@get:Mode
@Mode
var mode = MODE_NORMAL
private var mPrevGid = 0
private var mNextGid = 0
private var mJumpTo: String? = null
var category = EhUtils.NONE
private var mKeyword: String? = null
private var mSHash: String? = null
var advanceSearch = -1
var minRating = -1
var pageFrom = -1
var pageTo = -1
var imagePath: String? = null
var isUseSimilarityScan = false
var isOnlySearchCovers = false
var isShowExpunged = false
constructor()
protected constructor(parcel: Parcel) {
mode = parcel.readInt()
mPrevGid = parcel.readInt()
mNextGid = parcel.readInt()
mJumpTo = parcel.readString()
this.category = parcel.readInt()
mKeyword = parcel.readString()
advanceSearch = parcel.readInt()
minRating = parcel.readInt()
pageFrom = parcel.readInt()
pageTo = parcel.readInt()
imagePath = parcel.readString()
isUseSimilarityScan = parcel.readByte().toInt() != 0
isOnlySearchCovers = parcel.readByte().toInt() != 0
isShowExpunged = parcel.readByte().toInt() != 0
mSHash = parcel.readString()
}
@Parcelize
open class ListUrlBuilder(
@get:Mode @Mode var mode: Int = MODE_NORMAL,
private var mPrev: String? = null,
private var mNext: String? = null,
private var mJumpTo: String? = null,
var category: Int = EhUtils.NONE,
private var mKeyword: String? = null,
private var mSHash: String? = null,
var advanceSearch: Int = -1,
var minRating: Int = -1,
var pageFrom: Int = -1,
var pageTo: Int = -1,
var imagePath: String? = null,
var isUseSimilarityScan: Boolean = false,
var isOnlySearchCovers: Boolean = false,
var isShowExpunged: Boolean = false,
) : Cloneable, Parcelable {
fun reset() {
mode = MODE_NORMAL
mPrevGid = 0
mNextGid = 0
mPrev = null
mNext = null
mJumpTo = null
this.category = EhUtils.NONE
mKeyword = null
@@ -88,20 +68,9 @@ open class ListUrlBuilder : Cloneable, Parcelable {
mSHash = null
}
public override fun clone(): ListUrlBuilder {
return try {
super.clone() as ListUrlBuilder
} catch (e: CloneNotSupportedException) {
throw IllegalStateException(e)
}
}
fun setPrevGid(prevGid: Int) {
mPrevGid = prevGid
}
fun setNextGid(nextGid: Int) {
mNextGid = nextGid
fun setIndex(index: String?, isNext: Boolean = true) {
mNext = index?.takeIf { isNext }
mPrev = index?.takeUnless { isNext }
}
fun setJumpTo(jumpTo: String?) {
@@ -121,8 +90,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
*/
fun set(lub: ListUrlBuilder) {
mode = lub.mode
mPrevGid = lub.mPrevGid
mNextGid = lub.mNextGid
mPrev = lub.mPrev
mNext = lub.mNext
mJumpTo = lub.mJumpTo
this.category = lub.category
mKeyword = lub.mKeyword
@@ -196,7 +165,7 @@ open class ListUrlBuilder : Cloneable, Parcelable {
if (TextUtils.isEmpty(query)) {
return
}
val querys = StringUtils.split(query, '&')
val queries = StringUtils.split(query, '&')
var category = 0
var keyword: String? = null
var enableAdvanceSearch = false
@@ -206,7 +175,7 @@ open class ListUrlBuilder : Cloneable, Parcelable {
var enablePage = false
var pageFrom = -1
var pageTo = -1
for (str in querys) {
for (str in queries) {
val index = str.indexOf('=')
if (index < 0) {
continue
@@ -338,29 +307,23 @@ open class ListUrlBuilder : Cloneable, Parcelable {
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
}
// Search key
if (mKeyword != null) {
val keyword = mKeyword!!.trim { it <= ' ' }
mKeyword?.run {
val keyword = trim { it <= ' ' }
if (keyword.isNotEmpty()) {
try {
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
}
ub.addQuery("f_search", encodeUTF8(this))
}
}
if (mSHash != null) {
ub.addQuery("f_shash", mSHash!!)
mSHash?.let {
ub.addQuery("f_shash", it)
}
mJumpTo?.let {
ub.addQuery("seek", mJumpTo!!)
mPrevGid = 0
mNextGid = 0
ub.addQuery("seek", it)
}
if (mPrevGid != 0) {
ub.addQuery("prev", mPrevGid)
mPrev?.let {
ub.addQuery("prev", it)
}
if (mNextGid != 0) {
ub.addQuery("next", mNextGid)
mNext?.let {
ub.addQuery("next", it)
}
// Advance search
if (advanceSearch != -1) {
@@ -387,40 +350,36 @@ open class ListUrlBuilder : Cloneable, Parcelable {
MODE_UPLOADER -> {
val sb = StringBuilder(EhUrl.getHost())
sb.append("uploader/")
try {
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
mKeyword?.let {
sb.append("uploader/")
sb.append(encodeUTF8(it))
}
if (mPrevGid != 0) {
sb.append("?prev=").append(mPrevGid)
mPrev?.let {
sb.append("?prev=").append(it)
}
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid)
mNext?.let {
sb.append("?next=").append(it)
}
if (mJumpTo != null) {
sb.append("&seek=").append(mJumpTo)
mJumpTo?.let {
sb.append("&seek=").append(it)
}
sb.toString()
}
MODE_TAG -> {
val sb = StringBuilder(EhUrl.getHost())
sb.append("tag/")
try {
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
mKeyword?.let {
sb.append("tag/")
sb.append(encodeUTF8(it))
}
if (mPrevGid != 0) {
sb.append("?prev=").append(mPrevGid)
mPrev?.let {
sb.append("?prev=").append(it)
}
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid)
mNext?.let {
sb.append("?next=").append(it)
}
if (mJumpTo != null) {
sb.append("&seek=").append(mJumpTo)
mJumpTo?.let {
sb.append("&seek=").append(it)
}
sb.toString()
}
@@ -430,89 +389,19 @@ open class ListUrlBuilder : Cloneable, Parcelable {
MODE_TOPLIST -> {
val sb = StringBuilder(EhUrl.HOST_E)
sb.append("toplist.php?tl=")
try {
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
mKeyword.orEmpty().let {
sb.append(encodeUTF8(it))
}
if (mNextGid != 0) {
sb.append("&p=").append(mNextGid)
mJumpTo?.let {
sb.append("&p=").append(it)
}
sb.toString()
}
else -> {
val url: String = if (mode == MODE_NORMAL) {
EhUrl.getHost()
} else {
EhUrl.getWatchedUrl()
}
val ub = UrlBuilder(url)
if (this.category != EhUtils.NONE) {
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
}
if (mKeyword != null) {
val keyword = mKeyword!!.trim { it <= ' ' }
if (keyword.isNotEmpty()) {
try {
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
} catch (_: UnsupportedEncodingException) {
}
}
}
if (mSHash != null) {
ub.addQuery("f_shash", mSHash!!)
}
if (mPrevGid != 0) {
ub.addQuery("prev", mPrevGid)
}
if (mNextGid != 0) {
ub.addQuery("next", mNextGid)
}
if (advanceSearch != -1) {
ub.addQuery("advsearch", "1")
if (advanceSearch and AdvanceSearchTable.SH != 0) ub.addQuery("f_sh", "on")
if (advanceSearch and AdvanceSearchTable.STO != 0) ub.addQuery("f_sto", "on")
if (advanceSearch and AdvanceSearchTable.SFL != 0) ub.addQuery("f_sfl", "on")
if (advanceSearch and AdvanceSearchTable.SFU != 0) ub.addQuery("f_sfu", "on")
if (advanceSearch and AdvanceSearchTable.SFT != 0) ub.addQuery("f_sft", "on")
if (minRating != -1) {
ub.addQuery("f_sr", "on")
ub.addQuery("f_srdd", minRating)
}
if (pageFrom != -1 || pageTo != -1) {
ub.addQuery("f_sp", "on")
ub.addQuery("f_spf", if (pageFrom != -1) pageFrom.toString() else "")
ub.addQuery("f_spt", if (pageTo != -1) pageTo.toString() else "")
}
}
ub.build()
}
else -> throw IllegalStateException("Unexpected value: $mode")
}
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(mode)
dest.writeInt(mPrevGid)
dest.writeInt(mNextGid)
dest.writeString(mJumpTo)
dest.writeInt(this.category)
dest.writeString(mKeyword)
dest.writeInt(advanceSearch)
dest.writeInt(minRating)
dest.writeInt(pageFrom)
dest.writeInt(pageTo)
dest.writeString(imagePath)
dest.writeByte(if (isUseSimilarityScan) 1.toByte() else 0.toByte())
dest.writeByte(if (isOnlySearchCovers) 1.toByte() else 0.toByte())
dest.writeByte(if (isShowExpunged) 1.toByte() else 0.toByte())
dest.writeString(mSHash)
}
@IntDef(
MODE_NORMAL,
MODE_UPLOADER,
@@ -532,16 +421,5 @@ open class ListUrlBuilder : Cloneable, Parcelable {
const val MODE_IMAGE_SEARCH = 0x4
const val MODE_SUBSCRIPTION = 0x5
const val MODE_TOPLIST = 0x6
@JvmField
val CREATOR: Creator<ListUrlBuilder> = object : Creator<ListUrlBuilder> {
override fun createFromParcel(source: Parcel): ListUrlBuilder {
return ListUrlBuilder(source)
}
override fun newArray(size: Int): Array<ListUrlBuilder?> {
return arrayOfNulls(size)
}
}
}
}
@@ -1,135 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import com.hippo.widget.LoadImageView;
import com.hippo.yorozuya.collect.IntList;
import java.util.ArrayList;
public class NormalPreviewSet extends PreviewSet {
public static final Creator<NormalPreviewSet> CREATOR = new Creator<NormalPreviewSet>() {
@Override
public NormalPreviewSet createFromParcel(Parcel source) {
return new NormalPreviewSet(source);
}
@Override
public NormalPreviewSet[] newArray(int size) {
return new NormalPreviewSet[size];
}
};
private IntList mPositionList = new IntList();
private ArrayList<String> mImageKeyList = new ArrayList<>();
private ArrayList<String> mImageUrlList = new ArrayList<>();
private IntList mOffsetXList = new IntList();
private IntList mOffsetYList = new IntList();
private IntList mClipWidthList = new IntList();
private IntList mClipHeightList = new IntList();
private ArrayList<String> mPageUrlList = new ArrayList<>();
public NormalPreviewSet() {
}
protected NormalPreviewSet(Parcel in) {
this.mPositionList = in.readParcelable(IntList.class.getClassLoader());
this.mImageKeyList = in.createStringArrayList();
this.mImageUrlList = in.createStringArrayList();
this.mOffsetXList = in.readParcelable(IntList.class.getClassLoader());
this.mOffsetYList = in.readParcelable(IntList.class.getClassLoader());
this.mClipWidthList = in.readParcelable(IntList.class.getClassLoader());
this.mClipHeightList = in.readParcelable(IntList.class.getClassLoader());
this.mPageUrlList = in.createStringArrayList();
}
private String getImageKey(String imageUrl) {
int index = imageUrl.indexOf('/');
if (index >= 0) {
return imageUrl.substring(index + 1);
} else {
return imageUrl;
}
}
public void addItem(int position, String imageUrl, int xOffset, int yOffset, int width,
int height, String pageUrl) {
mPositionList.add(position);
mImageKeyList.add(getImageKey(imageUrl));
mImageUrlList.add(imageUrl);
mOffsetXList.add(xOffset);
mOffsetYList.add(yOffset);
mClipWidthList.add(width);
mClipHeightList.add(height);
mPageUrlList.add(pageUrl);
}
@Override
public int size() {
return mPositionList.size();
}
@Override
public int getPosition(int index) {
return mPositionList.get(index);
}
@Override
public String getPageUrlAt(int index) {
return mPageUrlList.get(index);
}
@Override
public GalleryPreview getGalleryPreview(long gid, int index) {
GalleryPreview galleryPreview = new GalleryPreview();
galleryPreview.position = mPositionList.get(index);
galleryPreview.imageKey = mImageKeyList.get(index);
galleryPreview.imageUrl = mImageUrlList.get(index);
galleryPreview.pageUrl = mPageUrlList.get(index);
galleryPreview.offsetX = mOffsetXList.get(index);
galleryPreview.offsetY = mOffsetYList.get(index);
galleryPreview.clipWidth = mClipWidthList.get(index);
galleryPreview.clipHeight = mClipHeightList.get(index);
return galleryPreview;
}
@Override
public void load(LoadImageView view, long gid, int index) {
view.setClip(mOffsetXList.get(index), mOffsetYList.get(index),
mClipWidthList.get(index), mClipHeightList.get(index));
view.load(mImageKeyList.get(index), mImageUrlList.get(index));
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(this.mPositionList, flags);
dest.writeStringList(this.mImageKeyList);
dest.writeStringList(this.mImageUrlList);
dest.writeParcelable(this.mOffsetXList, flags);
dest.writeParcelable(this.mOffsetYList, flags);
dest.writeParcelable(this.mClipWidthList, flags);
dest.writeParcelable(this.mClipHeightList, flags);
dest.writeStringList(this.mPageUrlList);
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import com.hippo.widget.LoadImageView
import com.hippo.yorozuya.collect.IntList
import kotlinx.parcelize.Parcelize
@Parcelize
class NormalPreviewSet(
private var mPositionList: IntList = IntList(),
private var mImageKeyList: ArrayList<String> = ArrayList(),
private var mImageUrlList: ArrayList<String> = ArrayList(),
private var mOffsetXList: IntList = IntList(),
private var mOffsetYList: IntList = IntList(),
private var mClipWidthList: IntList = IntList(),
private var mClipHeightList: IntList = IntList(),
private var mPageUrlList: ArrayList<String> = ArrayList()
) : PreviewSet() {
private fun getImageKey(imageUrl: String): String {
val index = imageUrl.indexOf('/')
return if (index >= 0) {
imageUrl.substring(index + 1)
} else {
imageUrl
}
}
fun addItem(
position: Int, imageUrl: String, xOffset: Int, yOffset: Int, width: Int,
height: Int, pageUrl: String
) {
mPositionList.add(position)
mImageKeyList.add(getImageKey(imageUrl))
mImageUrlList.add(imageUrl)
mOffsetXList.add(xOffset)
mOffsetYList.add(yOffset)
mClipWidthList.add(width)
mClipHeightList.add(height)
mPageUrlList.add(pageUrl)
}
override fun size(): Int {
return mPositionList.size()
}
override fun getPosition(index: Int): Int {
return mPositionList[index]
}
override fun getPageUrlAt(index: Int): String {
return mPageUrlList[index]
}
override fun getGalleryPreview(gid: Long, index: Int): GalleryPreview {
val galleryPreview = GalleryPreview()
galleryPreview.position = mPositionList[index]
galleryPreview.imageKey = mImageKeyList[index]
galleryPreview.imageUrl = mImageUrlList[index]
galleryPreview.pageUrl = mPageUrlList[index]
galleryPreview.offsetX = mOffsetXList[index]
galleryPreview.offsetY = mOffsetYList[index]
galleryPreview.clipWidth = mClipWidthList[index]
galleryPreview.clipHeight = mClipHeightList[index]
return galleryPreview
}
override fun load(view: LoadImageView, gid: Long, index: Int) {
view.setClip(
mOffsetXList[index], mOffsetYList[index],
mClipWidthList[index], mClipHeightList[index]
)
view.load(mImageKeyList[index], mImageUrlList[index])
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data;
import android.os.Parcelable;
import com.hippo.widget.LoadImageView;
public abstract class PreviewSet implements Parcelable {
public abstract int size();
public abstract int getPosition(int index);
public abstract String getPageUrlAt(int index);
public abstract GalleryPreview getGalleryPreview(long gid, int index);
public abstract void load(LoadImageView view, long gid, int index);
}
@@ -0,0 +1,27 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import com.hippo.widget.LoadImageView
abstract class PreviewSet : Parcelable {
abstract fun size(): Int
abstract fun getPosition(index: Int): Int
abstract fun getPageUrlAt(index: Int): String
abstract fun getGalleryPreview(gid: Long, index: Int): GalleryPreview
abstract fun load(view: LoadImageView, gid: Long, index: Int)
}
@@ -13,13 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
package com.hippo.ehviewer.client.exception;
public class NoHAtHClientException extends EhException {
public NoHAtHClientException(String detailMessage) {
super(detailMessage);
}
}
open class EhException : Exception {
constructor(detailMessage: String?) : super(detailMessage)
constructor(detailMessage: String?, cause: Throwable?) : super(detailMessage, cause)
}
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
package com.hippo.ehviewer.client.exception;
public class Image509Exception extends EhException {
public Image509Exception() {
super("509");
}
}
class Image509Exception : EhException("509")
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
package com.hippo.ehviewer.client.exception;
public class CancelledException extends Exception {
public CancelledException() {
super("CANCELED");
}
}
class NoHAtHClientException(detailMessage: String?) : EhException(detailMessage)
@@ -13,15 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception;
package com.hippo.ehviewer.client.exception
/**
* It is an exception for get offensive tip for g.e-hentai.org
*/
public class OffensiveException extends EhException {
public OffensiveException() {
super("OFFENSIVE");
}
}
class OffensiveException : EhException("OFFENSIVE")
@@ -0,0 +1,31 @@
/*
* 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.exception
class ParseException : EhException {
val body: String
constructor(detailMessage: String?, body: String) : super(detailMessage) {
this.body = body
}
constructor(detailMessage: String?, body: String, cause: Throwable?) : super(
detailMessage,
cause
) {
this.body = body
}
}
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.exception
package com.hippo.ehviewer.client.exception;
public class PiningException extends EhException {
public PiningException() {
super("pining for the fjords");
}
}
class PiningException : EhException("pining for the fjords")
@@ -16,33 +16,193 @@
package com.hippo.ehviewer.client.parser;
import android.util.Pair;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.exception.NoHAtHClientException;
import org.jsoup.Jsoup;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.IntFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ArchiveParser {
private static final Pattern PATTERN_FORM = Pattern.compile("<form id=\"hathdl_form\" action=\"[^\"]*?or=([^=\"]*?)\" method=\"post\">");
private static final Pattern PATTERN_ARCHIVE = Pattern.compile("<a href=\"[^\"]*\" onclick=\"return do_hathdl\\('([0-9]+|org)'\\)\">([^<]+)</a>");
private static final Pattern PATTERN_ARCHIVE_URL = Pattern.compile("<strong>(.*)</strong>.*<a href=\"([^\"]*)\">Click Here To Start Downloading</a>");
private static final Pattern PATTERN_HATH_FORM = Pattern.compile("<form id=\"hathdl_form\" action=\"[^\"]*?or=([^=\"]*?)\" method=\"post\">");
private static final Pattern PATTERN_HATH_ARCHIVE = Pattern.compile("<p><a href=\"[^\"]*\" onclick=\"return do_hathdl\\('([0-9]+|org)'\\)\">([^<]+)</a></p>\\s*<p>([\\w. ]+)</p>\\s*<p>([\\w. ]+)</p>");
private static final Pattern PATTERN_NEED_HATH_CLIENT = Pattern.compile("You must have a H@H client assigned to your account to use this feature\\.");
@SuppressWarnings("unchecked")
public static Pair<String, Pair<String, String>[]> parse(String body) {
Matcher m = PATTERN_FORM.matcher(body);
public static Result parse(String body) {
Matcher m = PATTERN_HATH_FORM.matcher(body);
if (!m.find()) {
return new Pair<String, Pair<String, String>[]>("", new Pair[0]);
return new Result(null, null);
}
String paramOr = m.group(1);
List<Pair<String, String>> archiveList = new ArrayList<>();
m = PATTERN_ARCHIVE.matcher(body);
var archiveList = new ArrayList<Archive>();
var d = Jsoup.parse(body);
var es = d.select("#db>div>div");
for (var e : es) {
try {
var res = e.selectFirst("form>input").attr("value");
var name = e.selectFirst("form>div>input").attr("value");
var size = e.selectFirst("p>strong").text();
var cost = e.selectFirst("div>strong").text();
var item = new Archive(res, name, size, cost, false);
archiveList.add(item);
} catch (NullPointerException ex) {
ex.printStackTrace();
}
}
m = PATTERN_HATH_ARCHIVE.matcher(body);
while (m.find()) {
String res = ParserUtils.trim(m.group(1));
String name = ParserUtils.trim(m.group(2));
Pair<String, String> item = new Pair<>(res, name);
String size = ParserUtils.trim(m.group(3));
String cost = ParserUtils.trim(m.group(4));
var item = new Archive(res, name, size, cost, true);
archiveList.add(item);
}
return new Pair<>(paramOr, archiveList.toArray(new Pair[0]));
return new Result(paramOr, archiveList);
}
public static String parseArchiveUrl(String body) throws NoHAtHClientException {
Matcher m = PATTERN_NEED_HATH_CLIENT.matcher(body);
if (m.find()) {
throw new NoHAtHClientException("No H@H client");
}
var d = Jsoup.parse(body);
var a = d.selectFirst("#continue>a[href]");
if (a != null) {
return a.attr("href") + "?start=1";
}
// TODO: Check more errors
return null;
}
public static final class Archive {
private final String res;
private final String name;
private final String size;
private final String cost;
private final boolean isHAtH;
public Archive(String res, String name, String size, String cost, boolean isHAtH) {
this.res = res;
this.name = name;
this.size = size;
this.cost = cost;
this.isHAtH = isHAtH;
}
public String format(IntFunction<String> getString) {
if (isHAtH) {
var costStr = cost.equals("Free") ? getString.apply(R.string.archive_free) : cost;
return String.format("[H@H] %s [%s] [%s]", name, size, costStr);
} else {
var nameStr = getString.apply(res.equals("org") ? R.string.archive_original : R.string.archive_resample);
var costStr = cost.equals("Free!") ? getString.apply(R.string.archive_free) : cost;
return String.format("%s [%s] [%s]", nameStr, size, costStr);
}
}
public String res() {
return res;
}
public String name() {
return name;
}
public String size() {
return size;
}
public String cost() {
return cost;
}
public boolean isHAtH() {
return isHAtH;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Archive) obj;
return Objects.equals(this.res, that.res) &&
Objects.equals(this.name, that.name) &&
Objects.equals(this.size, that.size) &&
Objects.equals(this.cost, that.cost) &&
this.isHAtH == that.isHAtH;
}
@Override
public int hashCode() {
return Objects.hash(res, name, size, cost, isHAtH);
}
@NonNull
@Override
public String toString() {
return "Archive[" +
"res=" + res + ", " +
"name=" + name + ", " +
"size=" + size + ", " +
"cost=" + cost + ", " +
"isHAtH=" + isHAtH + ']';
}
}
public static final class Result {
private final String paramOr;
private final List<Archive> archiveList;
public Result(String paramOr, List<Archive> archiveList) {
this.paramOr = paramOr;
this.archiveList = archiveList;
}
public String paramOr() {
return paramOr;
}
public List<Archive> archiveList() {
return archiveList;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Result) obj;
return Objects.equals(this.paramOr, that.paramOr) &&
Objects.equals(this.archiveList, that.archiveList);
}
@Override
public int hashCode() {
return Objects.hash(paramOr, archiveList);
}
@NonNull
@Override
public String toString() {
return "Result[" +
"paramOr=" + paramOr + ", " +
"archiveList=" + archiveList + ']';
}
}
}
@@ -31,22 +31,17 @@ import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FavoritesParser {
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+(-\\d+)?)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+(-\\d+)?)");
public static Result parse(String body) throws Exception {
if (body.contains("This page requires you to log on.</p>")) {
throw new EhException(GetText.getString(R.string.need_sign_in));
}
Document d = Jsoup.parse(body);
String[] catArray = new String[10];
int[] countArray = new int[10];
try {
Document d = Jsoup.parse(body);
Element ido = JsoupUtils.getElementByClass(d, "ido");
//noinspection ConstantConditions
Elements fps = ido.getElementsByClass("fp");
@@ -64,24 +59,13 @@ public class FavoritesParser {
throw new ParseException("Parse favorites error", body);
}
Result re = new Result();
try {
Element prev = d.getElementById("uprev");
Element next = d.getElementById("unext");
assert prev != null;
assert next != null;
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
if (matcherPrev.find()) re.prevPage = matcherPrev.group(1);
if (matcherNext.find()) re.nextPage = matcherNext.group(1);
} catch (Throwable e) {
e.printStackTrace();
}
GalleryListParser.Result result = GalleryListParser.parse(body);
Result re = new Result();
re.catArray = catArray;
re.countArray = countArray;
re.prev = result.prev;
re.next = result.next;
re.galleryInfoList = result.galleryInfoList;
return re;
@@ -90,8 +74,8 @@ public class FavoritesParser {
public static class Result {
public String[] catArray; // Size 10
public int[] countArray; // Size 10
public String prevPage;
public String nextPage;
public String prev;
public String next;
public List<GalleryInfo> galleryInfoList;
}
}
@@ -19,6 +19,8 @@ package com.hippo.ehviewer.client.parser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryCommentList;
@@ -67,6 +69,7 @@ public class GalleryDetailParser {
private static final Pattern PATTERN_NORMAL_PREVIEW = Pattern.compile("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"");
private static final Pattern PATTERN_LARGE_PREVIEW = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"");
private static final Pattern PATTERN_NEWER_DATE = Pattern.compile(", added (.+?)<br />");
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("/fav.png\\); background-position:0px -(\\d+)px");
private static final GalleryTagGroup[] EMPTY_GALLERY_TAG_GROUP_ARRAY = new GalleryTagGroup[0];
private static final GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
@@ -78,6 +81,23 @@ public class GalleryDetailParser {
private static final String PINING_STRING =
"<p>This gallery is pining for the fjords.</p>";
private static final String[] LANGUAGES = {
"English",
"Chinese",
"Spanish",
"Korean",
"Russian",
"French",
"Portuguese",
"Thai",
"German",
"Italian",
"Vietnamese",
"Polish",
"Hungarian",
"Dutch",
};
public static GalleryDetail parse(String body) throws EhException {
if (body.contains(OFFENSIVE_STRING)) {
throw new OffensiveException();
@@ -100,6 +120,15 @@ public class GalleryDetailParser {
galleryDetail.comments = parseComments(document);
galleryDetail.previewPages = parsePreviewPages(document, body);
galleryDetail.previewSet = parsePreviewSet(document, body);
// Generate simpleLanguage for local favorites
for (int i = 0; i < LANGUAGES.length; i++) {
if (galleryDetail.language.equals(LANGUAGES[i])) {
galleryDetail.simpleLanguage = GalleryInfo.S_LANGS[i];
break;
}
}
return galleryDetail;
}
@@ -191,14 +220,9 @@ public class GalleryDetailParser {
gd.size = "";
gd.pages = 0;
gd.favoriteCount = 0;
try {
Elements es = gdd.child(0).child(0).children();
for (int i = 0, n = es.size(); i < n; i++) {
parseDetailInfo(gd, es.get(i));
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
Elements es = gdd.child(0).child(0).children();
for (int i = 0, n = es.size(); i < n; i++) {
parseDetailInfo(gd, es.get(i));
}
// Rating count
@@ -230,15 +254,24 @@ public class GalleryDetailParser {
// isFavorited
Element gdf = gm.getElementById("gdf");
gd.isFavorited = null != gdf && !StringUtils.trim(gdf.text()).equals("Add to Favorites");
gd.isFavorited = false;
if (gdf != null) {
final String favoriteName = StringUtils.trim(gdf.text());
if (favoriteName.equals("Add to Favorites")) {
gd.favoriteName = null;
} else {
gd.isFavorited = true;
gd.favoriteName = StringUtils.trim(gdf.text());
matcher = PATTERN_FAVORITE_SLOT.matcher(body);
if (matcher.find()) {
gd.favoriteSlot = (NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19;
}
}
}
if (gd.favoriteSlot == -2 && EhDB.containLocalFavorites(gd.gid)) {
gd.favoriteSlot = -1;
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
throw new ParseException("Can't parse gallery detail", body);
@@ -441,6 +474,13 @@ public class GalleryDetailParser {
comment.user = c3.child(0).text();
// comment
comment.comment = JsoupUtils.getElementByClass(element, "c6").html();
// filter comment
if (!comment.uploader) {
EhFilter sEhFilter = EhFilter.getInstance();
if (!sEhFilter.filterCommenter(comment.user) || !sEhFilter.filterComment(comment.comment)) {
return null;
}
}
// last edited
Element c8 = JsoupUtils.getElementByClass(element, "c8");
if (c8 != null) {
@@ -50,8 +50,9 @@ public class GalleryListParser {
private static final Pattern PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px");
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("background-color:rgba\\((\\d+),(\\d+),(\\d+),");
private static final Pattern PATTERN_PAGES = Pattern.compile("(\\d+) page");
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("p=(\\d+)");
private static final Pattern PATTERN_PREV = Pattern.compile("prev=(\\d+(-\\d+)?)");
private static final Pattern PATTERN_NEXT = Pattern.compile("next=(\\d+(-\\d+)?)");
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
new String[]{"0", "0", "0"},
@@ -256,11 +257,7 @@ public class GalleryListParser {
if (div != null) {
gi.disowned = div.attr("style").contains("opacity:0.5");
Element a = div.children().first();
if (a != null) {
gi.uploader = a.text().trim();
} else {
gi.uploader = div.text().trim();
}
gi.uploader = a == null ? div.text().trim() : a.text().trim();
}
}
if (children.size() > pagesIndex) {
@@ -296,28 +293,44 @@ public class GalleryListParser {
Element next = d.getElementById("unext");
assert prev != null;
assert next != null;
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
Matcher matcherPrev = PATTERN_PREV.matcher(prev.attr("href"));
Matcher matcherNext = PATTERN_NEXT.matcher(next.attr("href"));
if (matcherPrev.find())
result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
result.prev = matcherPrev.group(1);
if (matcherNext.find())
result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
result.founds = Integer.MAX_VALUE;
result.next = matcherNext.group(1);
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
if (body.contains("No hits found</p>")) {
result.founds = 0;
//noinspection unchecked
result.galleryInfoList = Collections.EMPTY_LIST;
return result;
} else if (d.getElementsByClass("ptt").isEmpty()) {
result.founds = 1;
} else {
result.founds = Integer.MAX_VALUE;
}
}
try { // For toplists
Element ptt = d.getElementsByClass("ptt").first();
if (ptt != null) {
Elements es = ptt.child(0).child(0).children();
result.pages = Integer.parseInt(es.get(es.size() - 2).text().trim());
Element e = es.get(es.size() - 1);
if (e != null) {
e = e.children().first();
if (e != null) {
String href = e.attr("href");
Matcher matcher = PATTERN_NEXT_PAGE.matcher(href);
if (matcher.find()) {
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0);
}
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
try {
Element itg = d.getElementsByClass("itg").first();
Elements es;
@@ -350,9 +363,10 @@ public class GalleryListParser {
}
public static class Result {
public int founds;
public int prevGid;
public int nextGid;
public int pages;
public int nextPage;
public String prev;
public String next;
public boolean noWatchedTags;
public List<GalleryInfo> galleryInfoList;
}
@@ -16,27 +16,132 @@
package com.hippo.ehviewer.client.parser;
import android.util.Pair;
import androidx.annotation.NonNull;
import org.jsoup.Jsoup;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.IntFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TorrentParser {
private static final Pattern PATTERN_TORRENT = Pattern.compile("<td colspan=\"5\"> &nbsp; <a href=\".*\" onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)</a></td>");
private static final Pattern 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>");
@SuppressWarnings("unchecked")
public static Pair<String, String>[] parse(String body) {
List<Pair<String, String>> torrentList = new ArrayList<>();
Matcher m = PATTERN_TORRENT.matcher(body);
while (m.find()) {
String url = ParserUtils.trim(m.group(1));
String name = ParserUtils.trim(m.group(2));
Pair<String, String> item = new Pair<>(url, name);
torrentList.add(item);
public static List<Result> parse(String body) {
var torrentList = new ArrayList<Result>();
var d = Jsoup.parse(body);
var es = d.select("form>div>table");
for (var e : es) {
Matcher m = PATTERN_TORRENT.matcher(e.html());
if (m.find()) {
String posted = m.group(1);
String size = m.group(2);
int seeds = Integer.parseInt(m.group(3));
int peers = Integer.parseInt(m.group(4));
int downloads = Integer.parseInt(m.group(5));
String uploader = ParserUtils.trim(m.group(6));
String url = ParserUtils.trim(m.group(7));
String name = ParserUtils.trim(m.group(8));
var item = new Result(posted, size, seeds, peers, downloads, uploader, url, name);
torrentList.add(item);
}
}
return torrentList;
}
public static final class Result {
private final String posted;
private final String size;
private final int seeds;
private final int peers;
private final int downloads;
private final String uploader;
private final String url;
private final String name;
public Result(String posted, String size, int seeds, int peers, int downloads, String uploader, String url, String name) {
this.posted = posted;
this.size = size;
this.seeds = seeds;
this.peers = peers;
this.downloads = downloads;
this.uploader = uploader;
this.url = url;
this.name = name;
}
public String format(IntFunction<String> getString) {
return String.format("[%s] %s [%s] [↑%s ↓%s ✓%s]", posted, name, size, seeds, peers, downloads);
}
public String posted() {
return posted;
}
public String size() {
return size;
}
public int seeds() {
return seeds;
}
public int peers() {
return peers;
}
public int downloads() {
return downloads;
}
public String uploader() {
return uploader;
}
public String url() {
return url;
}
public String name() {
return name;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Result) obj;
return Objects.equals(this.posted, that.posted) &&
Objects.equals(this.size, that.size) &&
this.seeds == that.seeds &&
this.peers == that.peers &&
this.downloads == that.downloads &&
Objects.equals(this.uploader, that.uploader) &&
Objects.equals(this.url, that.url) &&
Objects.equals(this.name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(posted, size, seeds, peers, downloads, uploader, url, name);
}
@NonNull
@Override
public String toString() {
return "Result[" +
"posted=" + posted + ", " +
"size=" + size + ", " +
"seeds=" + seeds + ", " +
"peers=" + peers + ", " +
"downloads=" + downloads + ", " +
"uploader=" + uploader + ", " +
"url=" + url + ", " +
"name=" + name + ']';
}
return torrentList.toArray(new Pair[0]);
}
}
@@ -1,9 +0,0 @@
package com.hippo.ehviewer.dao;
import java.util.List;
public interface BasicDao<T> {
List<T> list();
long insert(T t);
}
@@ -0,0 +1,6 @@
package com.hippo.ehviewer.dao
interface BasicDao<T> {
fun list(): List<T>
fun insert(t: T): Long
}
@@ -1,160 +0,0 @@
package com.hippo.ehviewer.dao;
import android.os.Parcel;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import com.hippo.ehviewer.client.data.GalleryInfo;
@Entity(tableName = "BOOKMARKS")
public class BookmarkInfo extends GalleryInfo {
public static final Creator<BookmarkInfo> CREATOR = new Creator<>() {
@Override
public BookmarkInfo createFromParcel(Parcel source) {
return new BookmarkInfo(source);
}
@Override
public BookmarkInfo[] newArray(int size) {
return new BookmarkInfo[size];
}
};
@ColumnInfo(name = "PAGE")
public int page;
@ColumnInfo(name = "TIME")
public long time;
protected BookmarkInfo(Parcel in) {
super(in);
this.page = in.readInt();
this.time = in.readLong();
}
public BookmarkInfo(GalleryInfo galleryInfo) {
this.gid = galleryInfo.gid;
this.token = galleryInfo.token;
this.title = galleryInfo.title;
this.titleJpn = galleryInfo.titleJpn;
this.thumb = galleryInfo.thumb;
this.category = galleryInfo.category;
this.posted = galleryInfo.posted;
this.uploader = galleryInfo.uploader;
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
}
public BookmarkInfo(int page, long time) {
this.page = page;
this.time = time;
}
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitleJpn() {
return titleJpn;
}
public void setTitleJpn(String titleJpn) {
this.titleJpn = titleJpn;
}
public String getThumb() {
return thumb;
}
public void setThumb(String thumb) {
this.thumb = thumb;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getPosted() {
return posted;
}
public void setPosted(String posted) {
this.posted = posted;
}
public String getUploader() {
return uploader;
}
public void setUploader(String uploader) {
this.uploader = uploader;
}
public float getRating() {
return rating;
}
public void setRating(float rating) {
this.rating = rating;
}
public String getSimpleLanguage() {
return simpleLanguage;
}
public void setSimpleLanguage(String simpleLanguage) {
this.simpleLanguage = simpleLanguage;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.page);
dest.writeLong(this.time);
}
}
@@ -0,0 +1,16 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "BOOKMARKS")
class BookmarkInfo : GalleryInfo() {
@JvmField
@ColumnInfo(name = "PAGE")
var page = 0
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
}
@@ -1,20 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface BookmarksDao extends BasicDao<BookmarkInfo> {
@Insert
long insert(BookmarkInfo bookmark);
@Delete
void delete(BookmarkInfo bookmark);
@Query("SELECT * FROM BOOKMARKS ORDER BY TIME DESC")
List<BookmarkInfo> list();
}
@@ -0,0 +1,18 @@
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,30 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "DOWNLOAD_DIRNAME")
public class DownloadDirname {
@PrimaryKey
@ColumnInfo(name = "GID")
private long gid;
@ColumnInfo(name = "DIRNAME")
private String dirname;
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getDirname() {
return dirname;
}
public void setDirname(String dirname) {
this.dirname = dirname;
}
}
@@ -0,0 +1,15 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "DOWNLOAD_DIRNAME")
data class DownloadDirname(
@PrimaryKey
@ColumnInfo(name = "GID")
var gid: Long = 0,
@ColumnInfo(name = "DIRNAME")
var dirname: String? = null
)
@@ -1,29 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface DownloadDirnameDao extends BasicDao<DownloadDirname> {
@Query("SELECT * FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
DownloadDirname load(long gid);
@Update
void update(DownloadDirname downloadDirname);
@Insert
long insert(DownloadDirname downloadDirname);
@Query("DELETE FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
void deleteByKey(long gid);
@Query("DELETE FROM DOWNLOAD_DIRNAME")
void deleteAll();
@Query("SELECT * FROM DOWNLOAD_DIRNAME")
List<DownloadDirname> list();
}
@@ -0,0 +1,27 @@
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,252 +0,0 @@
package com.hippo.ehviewer.dao;
import android.os.Parcel;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import com.hippo.ehviewer.client.data.GalleryInfo;
@Entity(tableName = "DOWNLOADS")
public class DownloadInfo extends GalleryInfo {
public static final Creator<DownloadInfo> CREATOR = new Creator<>() {
@Override
public DownloadInfo createFromParcel(Parcel source) {
return new DownloadInfo(source);
}
@Override
public DownloadInfo[] newArray(int size) {
return new DownloadInfo[size];
}
};
public static final int STATE_INVALID = -1;
public static final int STATE_NONE = 0;
public static final int STATE_WAIT = 1;
public static final int STATE_DOWNLOAD = 2;
public static final int STATE_FINISH = 3;
public static final int STATE_FAILED = 4;
@ColumnInfo(name = "STATE")
public int state;
@ColumnInfo(name = "LEGACY")
public int legacy;
@ColumnInfo(name = "TIME")
public long time;
@ColumnInfo(name = "LABEL")
public String label;
@Ignore
public long speed;
@Ignore
public long remaining;
@Ignore
public int finished;
@Ignore
public int downloaded;
@Ignore
public int total;
DownloadInfo() {
}
protected DownloadInfo(Parcel in) {
super(in);
this.state = in.readInt();
this.legacy = in.readInt();
this.time = in.readLong();
this.label = in.readString();
}
public DownloadInfo(GalleryInfo galleryInfo) {
this.gid = galleryInfo.gid;
this.token = galleryInfo.token;
this.title = galleryInfo.title;
this.titleJpn = galleryInfo.titleJpn;
this.thumb = galleryInfo.thumb;
this.category = galleryInfo.category;
this.posted = galleryInfo.posted;
this.uploader = galleryInfo.uploader;
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
}
public DownloadInfo(int state, int legacy, long time, String label, long speed, long remaining,
int finished, int downloaded, int total) {
this.state = state;
this.legacy = legacy;
this.time = time;
this.label = label;
this.speed = speed;
this.remaining = remaining;
this.finished = finished;
this.downloaded = downloaded;
this.total = total;
}
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitleJpn() {
return titleJpn;
}
public void setTitleJpn(String titleJpn) {
this.titleJpn = titleJpn;
}
public String getThumb() {
return thumb;
}
public void setThumb(String thumb) {
this.thumb = thumb;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getPosted() {
return posted;
}
public void setPosted(String posted) {
this.posted = posted;
}
public String getUploader() {
return uploader;
}
public void setUploader(String uploader) {
this.uploader = uploader;
}
public float getRating() {
return rating;
}
public void setRating(float rating) {
this.rating = rating;
}
public String getSimpleLanguage() {
return simpleLanguage;
}
public void setSimpleLanguage(String simpleLanguage) {
this.simpleLanguage = simpleLanguage;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public int getLegacy() {
return legacy;
}
public void setLegacy(int legacy) {
this.legacy = legacy;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.state);
dest.writeInt(this.legacy);
dest.writeLong(this.time);
dest.writeString(this.label);
}
public long getSpeed() {
return this.speed;
}
public void setSpeed(long speed) {
this.speed = speed;
}
public long getRemaining() {
return this.remaining;
}
public void setRemaining(long remaining) {
this.remaining = remaining;
}
public int getFinished() {
return this.finished;
}
public void setFinished(int finished) {
this.finished = finished;
}
public int getDownloaded() {
return this.downloaded;
}
public void setDownloaded(int downloaded) {
this.downloaded = downloaded;
}
public int getTotal() {
return this.total;
}
public void setTotal(int total) {
this.total = total;
}
}
@@ -0,0 +1,68 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "DOWNLOADS")
class DownloadInfo() : GalleryInfo() {
@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
thumb = galleryInfo.thumb
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,40 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "DOWNLOAD_LABELS")
public class DownloadLabel {
@PrimaryKey
@ColumnInfo(name = "_id")
private Long id;
@ColumnInfo(name = "LABEL")
private String label;
@ColumnInfo(name = "TIME")
private long time;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
@@ -0,0 +1,18 @@
package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "DOWNLOAD_LABELS")
data class DownloadLabel(
@PrimaryKey
@ColumnInfo(name = "_id")
var id: Long? = null,
@ColumnInfo(name = "LABEL")
var label: String? = null,
@ColumnInfo(name = "TIME")
var time: Long = 0
)
@@ -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;
import androidx.room.Update;
import java.util.List;
@Dao
public interface DownloadLabelDao extends BasicDao<DownloadLabel> {
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC")
List<DownloadLabel> list();
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
List<DownloadLabel> list(int offset, int limit);
@Update
void update(List<DownloadLabel> downloadLabels);
@Update
void update(DownloadLabel downloadLabel);
@Insert
long insert(DownloadLabel downloadLabel);
@Delete
void delete(DownloadLabel downloadLabel);
}
@@ -0,0 +1,28 @@
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,27 +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;
import java.util.List;
@Dao
public interface DownloadsDao extends BasicDao<DownloadInfo> {
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC")
List<DownloadInfo> list();
@Query("SELECT * FROM DOWNLOADS WHERE GID = :gid")
DownloadInfo load(long gid);
@Update
void update(DownloadInfo downloadInfo);
@Insert
long insert(DownloadInfo downloadInfo);
@Delete
void delete(DownloadInfo downloadInfo);
}
@@ -0,0 +1,25 @@
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 WHERE GID = :gid")
fun load(gid: Long): DownloadInfo?
@Update
fun update(downloadInfo: DownloadInfo)
@Insert
override fun insert(downloadInfo: DownloadInfo): Long
@Delete
fun delete(downloadInfo: DownloadInfo)
}
@@ -1,23 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {BookmarkInfo.class, DownloadInfo.class, DownloadLabel.class, DownloadDirname.class, Filter.class, HistoryInfo.class, LocalFavoriteInfo.class, QuickSearch.class}, version = 4, exportSchema = false)
public abstract class EhDatabase extends RoomDatabase {
public abstract BookmarksDao bookmarksBao();
public abstract DownloadDirnameDao downloadDirnameDao();
public abstract DownloadLabelDao downloadLabelDao();
public abstract DownloadsDao downloadsDao();
public abstract FilterDao filterDao();
public abstract HistoryDao historyDao();
public abstract LocalFavoritesDao localFavoritesDao();
public abstract QuickSearchDao quickSearchDao();
}
@@ -0,0 +1,20 @@
package com.hippo.ehviewer.dao
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(
entities = [BookmarkInfo::class, DownloadInfo::class, DownloadLabel::class, DownloadDirname::class, Filter::class, HistoryInfo::class, LocalFavoriteInfo::class, QuickSearch::class],
version = 4,
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,66 +0,0 @@
package com.hippo.ehviewer.dao;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.hippo.util.HashCodeUtils;
import com.hippo.yorozuya.ObjectUtils;
@Entity(tableName = "FILTER")
public class Filter {
@ColumnInfo(name = "MODE")
public int mode;
@ColumnInfo(name = "TEXT")
public String text;
@ColumnInfo(name = "ENABLE")
public Boolean enable;
@PrimaryKey
@ColumnInfo(name = "_id")
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public int getMode() {
return mode;
}
public void setMode(int mode) {
this.mode = mode;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
@Override
public int hashCode() {
return HashCodeUtils.hashCode(mode, text);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Filter filter)) {
return false;
}
return filter.mode == mode && ObjectUtils.equal(filter.text, text);
}
}
@@ -0,0 +1,24 @@
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
)
Loaded 100 of 442 files, more files were not shown because too many files have changed in this diff. Show more