Compare commits

..
Author SHA1 Message Date
txyyh e1820617a9 Rel 1.7.31.0-alpha04
Releases / Build (push) Has been cancelled
2022-11-06 07:22:01 +00:00
tarsin 512e529b51 No gallery found no more a exception 2022-11-06 07:02:27 +00:00
tarsin 9b0766dd6e Fix favourite list load first 2022-11-06 07:02:27 +00:00
txyyh f56bf7da8b Rel 1.7.31.0-alpha03
Releases / Build (push) Has been cancelled
2022-11-05 09:52:26 +00:00
小白-白 ee895a92cd Remove preview count limit #332 (#333)
fix: preview count limit
2022-11-04 17:23:25 +00:00
老虎豆 2fbc343b55 Fix when clicking cover in download that don't enter gallery detail(#251) (#305)
Fix when clicking cover in download that don't enter gallery detail
2022-11-04 17:23:25 +00:00
小白-白 dccd02f15e Fix favorite page load (#368)
修复收藏页面按发布时间排序时无法翻页问题
2022-11-04 04:12:44 +00:00
txyyh 6f3476895e Rel 1.7.31.0-alpha02
Releases / Build (push) Has been cancelled
2022-11-03 15:56:49 +00:00
tarsin 98af874e83 Don't parse found in common path 2022-11-03 15:12:55 +00:00
tarsin 02bd5badb9 Fix tag search page load 2022-11-03 15:02:54 +00:00
tarsin 605f86483b Fix favourite scene data restore 2022-11-03 15:00:45 +00:00
tarsin f0d6ad9848 Fix uploader page request 2022-11-03 14:51:35 +00:00
tarsin 7677498e0c Relax founds limit
We will calculate accurately later
2022-11-03 14:51:14 +00:00
tarsin d115af60b3 Fix restore state 2022-11-03 14:50:55 +00:00
tarsin b07ab230e9 Code clean and allow refresh pages 2022-11-03 14:50:32 +00:00
tarsin 760ca8d6fb Minimal changes to rework Favourite grabs
TODO: Reimpl goto, refresh pages
And take care of per page counts by user
2022-11-03 05:39:36 +00:00
tarsin 4f0acb465a Minimal changes to rework new exhentai page load
TODO: Reimpl Favourite grab, goto, refresh pages
2022-11-03 05:39:22 +00:00
renovate[bot] f1ddc6c3aa Update lifecycle_version to v2.6.0-alpha03 (1.7.x.x) (#346)
Update lifecycle_version to v2.6.0-alpha03

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 17:28:50 +00:00
renovate[bot] 3b955cc9a9 Update fragment_version to v1.5.4 (1.7.x.x) (#345)
Update fragment_version to v1.5.4

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 14:30:30 +00:00
renovate[bot] 652f503635 Update dependency com.google.android.material:material to v1.8.0-alpha02 (1.7.x.x) (#344)
Update dependency com.google.android.material:material to v1.8.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 11:05:23 +00:00
renovate[bot] 5f6d2e8dbd Update activity_version to v1.6.1 (1.7.x.x) (#343)
Update activity_version to v1.6.1

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 07:22:55 +00:00
renovate[bot] 4925312c4a Update plugin com.android.application to v7.3.1 (1.7.x.x) (#328)
Update plugin com.android.application to v7.3.1

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-13 20:41:56 +00:00
renovate[bot] c58bad59e2 Update plugin org.jetbrains.kotlin.android to v1.7.20 (1.7.x.x) (#315)
Update plugin org.jetbrains.kotlin.android to v1.7.20

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-29 14:52:04 +00:00
renovate[bot] 7aa318890d Update dependency androidx.annotation:annotation to v1.5.0 (1.7.x.x) (#306)
Update dependency androidx.annotation:annotation to v1.5.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 19:42:22 +08:00
renovate[bot] 066500f2f8 Update fragment_version to v1.5.3 (1.7.x.x) (#304)
Update fragment_version to v1.5.3

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 13:34:46 +08:00
renovate[bot] d37752a6db Update dependency androidx.recyclerview:recyclerview to v1.3.0-rc01 (1.7.x.x) (#303)
Update dependency androidx.recyclerview:recyclerview to v1.3.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 13:34:32 +08:00
renovate[bot] cd81a10d28 Update dependency androidx.customview:customview to v1.2.0-alpha02 (1.7.x.x) (#301)
Update dependency androidx.customview:customview to v1.2.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 10:44:48 +08:00
renovate[bot] 812018af06 Update dependency androidx.constraintlayout:constraintlayout to v2.2.0-alpha04 (1.7.x.x) (#300)
Update dependency androidx.constraintlayout:constraintlayout to v2.2.0-alpha04

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 10:44:36 +08:00
renovate[bot] ee1ddeec12 Update activity_version to v1.6.0 (1.7.x.x) (#298)
Update activity_version to v1.6.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 10:30:44 +08:00
renovate[bot] 8a3ce0e5ef Update dependency androidx.biometric:biometric-ktx to v1.2.0-alpha05 (1.7.x.x) (#299)
Update dependency androidx.biometric:biometric-ktx to v1.2.0-alpha05

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 10:30:34 +08:00
LaLucid 8b9a3a0274 Add Gradle cache clean (#287) 2022-09-18 10:05:57 +00:00
txyyh 08f8f6cc75 Use example version code in issue template 2022-09-17 08:53:21 +00:00
txyyh 1052ac5fc5 Apply format to all docs 2022-09-16 12:43:49 +00:00
tarsin b62b6f95a8 Update README.md 2022-09-16 12:43:38 +00:00
renovate[bot] ffd719a104 Update plugin com.android.application to v7.3.0 (1.7.x.x) (#280)
Update plugin com.android.application to v7.3.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-15 22:37:41 +00:00
txyyh 2f787ca94b Add thanks to issue template (#272)
* Add thanks

* Shorten description
2022-09-12 14:44:09 +00:00
renovate[bot] 990c8ed9c9 Update dependency androidx.annotation:annotation-experimental to v1.3.0 (1.7.x.x) (#270)
Update dependency androidx.annotation:annotation-experimental to v1.3.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-09 08:51:39 +08:00
renovate[bot] 3492593a06 Update lifecycle_version to v2.6.0-alpha02 (1.7.x.x) (#269)
Update lifecycle_version to v2.6.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-09 08:51:23 +08:00
renovate[bot] 2bea46b15b Update core_version to v1.9.0 (1.7.x.x) (#267)
Update core_version to v1.9.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-09 07:09:08 +08:00
renovate[bot] f0903fa8b2 Update dependency androidx.appcompat:appcompat to v1.6.0-rc01 (1.7.x.x) (#268)
Update dependency androidx.appcompat:appcompat to v1.6.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-09 07:08:39 +08:00
renovate[bot] 60caa099e2 Update activity_version to v1.6.0-rc02 (1.7.x.x) (#266)
Update activity_version to v1.6.0-rc02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 20:16:00 +00:00
txyyh 586fba4221 Add additional context textarea (#255)
* Use other word

* Add additional context to bug report
2022-09-07 16:20:06 +08:00
EdwardWu 80faa9a2db Ehviewer: Translate: Update zh-rTW Translate (09/06) (#254)
* Update string from d2fc2e
* Update some strings translate

Signed-off-by: Edward Wu <bluehome.wu@gmail.com>
2022-09-06 05:48:24 +00:00
LittleLoli 54fea2929f add backup remote favorite list to local. (#252) 2022-09-06 00:47:02 +00:00
EdwardWu 4599ed88ee Ehviewer: Translate: Update zh-rTW Translate (09/04) (#250)
* Update string from a5161bb

Signed-off-by: Edward Wu <bluehome.wu@gmail.com>
2022-09-04 16:00:26 +00:00
txyyh 60d75fd780 Update material-components (#249) 2022-09-03 11:14:53 +00:00
tarsin 896d71b867 Fix #246 2022-08-31 08:50:07 +08:00
renovate[bot] 6d440730fd Update dependency com.google.android.material:material to v1.7.0-rc01 (1.7.x.x) (#248)
Update dependency com.google.android.material:material to v1.7.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-31 08:43:27 +08:00
txyyh 77a6960e90 Use label on info card (#243) 2022-08-29 08:26:52 +00:00
txyyh 215eb267fe Use LabelLarge on CardMessage (#244) 2022-08-29 08:26:38 +00:00
txyyh 2c86e8b898 Use title small on comment (#242) 2022-08-29 08:26:23 +00:00
tarsin d7daa47c73 Make download card takes proper round corner 2022-08-26 07:00:27 +00:00
tarsin 3c488916bf Drop unused code 2022-08-26 07:00:27 +00:00
tarsin ba09fcdc3c Drop unused color res
* Color should be dynamic most time
2022-08-26 07:00:27 +00:00
tarsin 385101a729 Make swiperefreshlayout and ProgressIndicator follow dyncolor 2022-08-26 07:00:27 +00:00
renovate[bot] 002c3cf1d5 Update dependency androidx.webkit:webkit to v1.5.0 (1.7.x.x) (#239)
Update dependency androidx.webkit:webkit to v1.5.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 19:27:40 +00:00
renovate[bot] 84064549ac Update core_version to v1.9.0-rc01 (1.7.x.x) (#238)
Update core_version to v1.9.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 15:46:28 +00:00
renovate[bot] 8c52ad212e Update activity_version to v1.6.0-rc01 (1.7.x.x) (#237)
Update activity_version to v1.6.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 14:40:24 +08:00
renovate[bot] 23e6f78067 Update dependency org.jsoup:jsoup to v1.15.3 (1.7.x.x) (#226)
Update dependency org.jsoup:jsoup to v1.15.3

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-24 05:56:58 +00:00
tarsin d5fcb06e8f Allow universal apk build
Releases / Build (push) Has been cancelled
2022-08-24 02:52:04 +00:00
txyyh 562d1f9a0d Make release 2022-08-24 01:51:21 +00:00
tarsin 03f29288ad Fix a wrong place res 2022-08-23 07:35:18 +00:00
tarsin eb84320225 Support resize thumb, Implement #193 2022-08-23 07:34:57 +00:00
tarsin 10d73ab639 IsLightStatusBar always follow night mode, Fix #199 2022-08-23 07:34:28 +00:00
tarsin cac8f6361a Rename .java to .kt 2022-08-23 07:34:10 +00:00
tarsin 32d0758fdc Fix #196 2022-08-23 02:12:18 +00:00
tarsin c382c9c896 Add back doh 2022-08-23 02:12:18 +00:00
tarsin e772a52bf2 Fix thumb card margin 2022-08-22 10:00:38 +00:00
tarsin edda52159e Make default label removable 2022-08-22 09:58:27 +00:00
tarsin 00c2e683bd Make downloadList qs drawer drag to rearrange position and swipe to remove 2022-08-22 09:58:27 +00:00
tarsin a88766db56 Make gallerylist qs drawer drag to rearrange position and swipe to remove 2022-08-22 09:58:27 +00:00
tarsin f6c1092cba Reimplement history swipe-to-remove 2022-08-22 09:58:27 +00:00
tarsin a15b92333a Clean up readme 2022-08-22 09:58:27 +00:00
tarsin 21fc20a0f4 Drop android-advancedrecyclerview
* Actually this library is unmaintained for above a year, and drag feature we used is totally broken on Android 12 and above https://github.com/h6ah4i/android-advancedrecyclerview/issues/516 https://github.com/Ehviewer-Overhauled/Ehviewer/issues/54
2022-08-22 09:58:27 +00:00
txyyh bf72d772b5 Describe CI pages 2022-08-15 15:34:49 +00:00
txyyh 76d8575398 Rename apks on releases 2022-08-15 15:34:02 +00:00
renovate[bot] 029dc7ac61 Update dependency com.google.android.material:material to v1.7.0-beta01 2022-08-11 22:02:38 +00:00
renovate[bot] 0b6f8d1d6f Update fragment_version to v1.5.2 (1.7.x.x) (#189)
Update fragment_version to v1.5.2

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 13:33:21 +08:00
renovate[bot] 0b283bb333 Update dependency androidx.recyclerview:recyclerview to v1.3.0-beta02 (1.7.x.x) (#187)
Update dependency androidx.recyclerview:recyclerview to v1.3.0-beta02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 13:21:34 +08:00
renovate[bot] c74ee2b5cb Update dependency androidx.webkit:webkit to v1.5.0-rc01 (1.7.x.x) (#188)
Update dependency androidx.webkit:webkit to v1.5.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 13:21:19 +08:00
renovate[bot] 8263dce3ca Update core_version to v1.9.0-beta01 (1.7.x.x) (#185)
Update core_version to v1.9.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 12:53:03 +08:00
renovate[bot] a37e5e1ab3 Update dependency androidx.appcompat:appcompat to v1.6.0-beta01 (1.7.x.x) (#186)
Update dependency androidx.appcompat:appcompat to v1.6.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 12:52:47 +08:00
txyyh 0baad6c042 Disable blank issue 2022-08-11 01:33:03 +00:00
renovate[bot] a2da9ffea6 Update activity_version to v1.6.0-beta01 (1.7.x.x) (#184)
Update activity_version to v1.6.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 09:10:51 +08:00
txyyh 4f3668dae4 Fix submodule error 2022-08-10 12:57:58 +00:00
txyyh 2598b68b97 Add LICENSE header 2022-08-10 12:44:56 +00:00
txyyh 7d065fb5c5 Use ic_launcher_foreground on scene_login (#174) 2022-08-10 17:10:47 +08:00
txyyh c4cc089eca Use new icon (#173)
* Use new icon

* Add monochrome icon

* Add LICENSE header
2022-08-10 17:10:47 +08:00
txyyh 85c377713d Only build CI on branches (#171) 2022-08-08 01:05:14 +08:00
renovate[bot] 3e7a1d9d35 Update dependency gradle to v7.5.1 (1.7.x.x) (#168)
Update dependency gradle to v7.5.1

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-06 07:42:36 +08:00
txyyh b0e9d8ec66 Fix wrong thanks 2022-08-05 18:33:18 +08:00
txyyh c25163b3c4 Revert "Use O3 optimization"
This reverts commit d396cae207.
2022-08-05 18:31:19 +08:00
txyyh 12d5aa9e08 Fix wrong TOC 2022-08-05 16:33:34 +08:00
txyyh 9fe1743dd9 Fix wrong Download section 2022-08-05 16:25:55 +08:00
txyyh e178880eb4 Add mult-language README and markdown TOC (#165)
* Add mult-language README

* Add markdown TOC

* Add language selection to README

* Fix wrong branch name in README
2022-08-05 15:48:17 +08:00
txyyh 0abab346ab Delete useless string (#161) 2022-08-05 15:25:16 +08:00
EdwardWuandtarsin 63d355ad95 fixup! Ehviewer: Add Chinese(Traditional) Translate (#160)
* Ehviewer: Add Chinese(Traditional) Translate

Signed-off-by: Edward Wu <bluehome.wu@gmail.com>

* fixup! Ehviewer: Add Chinese(Traditional) Translate

Signed-off-by: Edward Wu <bluehome.wu@gmail.com>

Co-authored-by: tarsin <43900799+asuka-mio@users.noreply.github.com>
2022-08-05 15:25:03 +08:00
renovate[bot] 17ab6c7532 Update plugin com.android.application to v7.2.2 (1.7.x.x) (#163)
Update plugin com.android.application to v7.2.2

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-04 04:59:20 +08:00
txyyhandEdwardWu 8d82fc39f6 Ehviewer: Add Chinese(Traditional) Translate (#158)
Signed-off-by: Edward Wu <bluehome.wu@gmail.com>

Co-authored-by: EdwardWu <56945729+bluehomewu@users.noreply.github.com>
2022-08-03 21:30:14 +08:00
tarsin d396cae207 Use O3 optimization 2022-08-02 14:21:41 +08:00
tarsin ec96ca2d7c Update README.md 2022-08-02 14:13:29 +08:00
LaLucid fa2279e480 update CI (#155)
* update CI

* do not need setup-android
2022-08-02 14:13:20 +08:00
txyyh de5b77cc96 Rewrite download section in README (#151)
* Delete CI Download in README

* Add Chinese NOTE

* Add English NOTE

* Use icon for github download
2022-07-31 12:34:41 +08:00
txyyh 41a54b76bf Release 1.7.29.3
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-31 00:16:18 +08:00
txyyh a09940eb86 Turn app link dialog to acquire update (#147) 2022-07-30 23:49:57 +08:00
txyyh 159b8c13b0 Fix archive open error (#145)
This reverts commit d2aecb5eb5 and use https://github.com/Ehviewer-Overhauled/Ehviewer/commit/d2aecb5eb5e25fd5a50726485fd53de178d45aa9#r79616069
2022-07-28 14:54:49 +08:00
renovate[bot] 498d51ec22 Update fragment_version to v1.5.1 (#141)
Update fragment_version to v1.5.1

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 04:25:44 +08:00
renovate[bot] 24e50da7c9 Update collection_version to v1.3.0-alpha02 (#140)
Update collection_version to v1.3.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 04:25:05 +08:00
tarsin fcc595d9a3 Drop taptargetview
* Not MD3
2022-07-25 23:13:27 +08:00
tarsin d2aecb5eb5 Log tweaks 2022-07-25 23:09:50 +08:00
txyyh e2cfa30052 Release 1.7.29.2
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-25 14:54:18 +08:00
txyyh 513886eff4 pick gallery_detail_header.xml changes to television (#131)
* Rewrite info TableLayout to ConstraintLayout for television

* Rewrite layout and minor design improve for television
2022-07-25 07:50:58 +08:00
txyyh 0093db9986 Make buttongroup horizontal (#125)
* Use another impl to create space

* Horizontal button
2022-07-25 07:50:41 +08:00
txyyh 182e3a0d40 Rewrite README description (#124) 2022-07-24 17:40:19 +08:00
txyyh 6d140aaa9d Delete FOSSA (#123) 2022-07-24 14:24:31 +08:00
tarsin 17248d3a82 Revert "Use SMAPSHOT version of mdc"
This reverts commit 7a0fab29bc.
2022-07-24 13:07:28 +08:00
txyyh ec80e7a189 Back to corner (#121) 2022-07-24 05:36:07 +08:00
txyyh 493eeadf1b Add note for CI download (#122) 2022-07-24 03:35:28 +08:00
tarsin 7a0fab29bc Use SMAPSHOT version of mdc 2022-07-23 20:41:02 +08:00
hathlife 5f7e40f258 Gallery detail head tv layout (#96)
* Make height of App logo ImageView match parent

* Add TV layout for gallery detail scene.

* Add credits

* Use GPLv3 for all TV layout code
2022-07-23 20:27:42 +08:00
txyyh 0bf6b8d827 Release 1.7.29.1
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-23 17:14:25 +08:00
tarsin 4c6ccab051 Native lib info refactor 2022-07-23 16:25:53 +08:00
tarsin 00377f15b9 Set our own libs 2022-07-23 16:07:23 +08:00
tarsin 96b50eb5aa Add nettle for encrypt 2022-07-23 15:51:43 +08:00
tarsin acdf681d40 Add gnutls libnettle 2022-07-23 14:26:17 +08:00
tarsin b92cf9746b Add .gz .xz .tar .cbz .cbr to supported mimeTypes 2022-07-23 13:28:41 +08:00
txyyh d0dfbaaae0 Rewrite layout and minor design improve (#116)
* Use ConstraintLayout instead

* Specify info Card Text to BodySmall

* Specify thumb shape to none (no corner)
2022-07-23 07:44:33 +08:00
txyyh 0c656e617b Add CI download link (#117)
* Add CI Download link

* Use head in right way
2022-07-23 00:49:03 +08:00
tarsin 148b1a6344 Release 1.7.29.0
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-22 23:12:27 +08:00
tarsin 1fe0b079cd Support auto store archive's passwd 2022-07-22 22:53:44 +08:00
tarsin 87505aa989 Code clean 2022-07-22 22:18:41 +08:00
tarsin 3d7a71647e Initial support for encrypted archive read 2022-07-22 21:28:19 +08:00
txyyh c9d2f698b6 Add MDC-Android to Thanks (#113) 2022-07-22 16:51:52 +08:00
tarsin 233adbddae Drop another spinner 2022-07-22 14:14:29 +08:00
tarsin 6900d8a8c9 Rename .java to .kt 2022-07-22 14:14:29 +08:00
tarsin 640f75d8d9 Finally our data is no need to pass any jByteArray 2022-07-22 10:08:04 +08:00
tarsin 4381eb8c3d Fuck off the buffer 2022-07-22 09:52:35 +08:00
tarsin 3026b16d70 Don't emulate posix read, do mmap instead 2022-07-22 00:13:53 +08:00
txyyh ac3662a469 Release 1.7.28.4
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-22 00:07:31 +08:00
tarsin 912e6c5a5b Fix a miscompare 2022-07-21 22:25:50 +08:00
txyyh 6a73a51f63 Rewrite info TableLayout to ConstraintLayout (#111) 2022-07-21 22:13:07 +08:00
tarsin 768b6bdda7 Don't rewind fd if we can continue read 2022-07-21 22:11:50 +08:00
tarsin 9ea7cd5f7c JNIEnv is shared in the same thread 2022-07-21 21:56:32 +08:00
tarsin a91547ace7 Use in-place decompress 2022-07-21 20:47:55 +08:00
tarsin ed58085b8f Update AndroidManifest.xml
https://developer.android.google.cn/about/versions/13/behavior-changes-13?hl=zh-cn#migration
2022-07-20 21:10:07 +08:00
txyyh 944e267795 Separate different action (#107)
* Rename ci action

* Separate push and pull request action

* Change README CI shield

* Rename artifact to right name
2022-07-20 19:22:59 +08:00
txyyh 325d6df2d3 Add thanks (#106) 2022-07-20 07:57:02 +08:00
txyyh 3ba08bf003 Auto-gen releases discussion (#105) 2022-07-20 07:56:49 +08:00
tarsin 86d00cc0d1 Use mdc's slider 2022-07-19 22:17:40 +08:00
tarsin 4ee518451c Release 1.7.28.3 2022-07-19 19:42:10 +08:00
tarsin 7b167b1612 Check to our xz fork 2022-07-19 19:01:03 +08:00
txyyh 7f3965bb08 Regen repo link in app (#103) 2022-07-19 17:27:00 +08:00
txyyh 49ea156ea8 Regen repo link (#102) 2022-07-19 17:20:02 +08:00
tarsin 90c7f9fd98 Re-checkout upstream 2022-07-19 17:02:22 +08:00
tarsin 1604885596 Optimize size 2022-07-19 16:37:48 +08:00
NekoInverter f0fbf2f46e Open app info when cannot open open by default 2022-07-19 16:37:48 +08:00
txyyh 3ab563e25d Require SoC and device model in bug report template (#101)
* Require device model on bug report

* Require SoC model on bug report
2022-07-19 12:27:54 +08:00
tarsin 1bdb52a096 kotlinize 2022-07-19 12:16:58 +08:00
tarsin 029401288e Rename .java to .kt 2022-07-19 12:16:57 +08:00
tarsin b84daa45c5 kotlinize 2022-07-19 12:02:07 +08:00
tarsin ba3fe869d8 Rename .java to .kt 2022-07-19 12:02:07 +08:00
tarsin 74494848d3 kotlinize 2022-07-19 11:49:51 +08:00
tarsin fa0a72e159 Rename .java to .kt 2022-07-19 11:49:51 +08:00
tarsin 30c408a27f kotlinize 2022-07-19 11:41:49 +08:00
tarsin 4e9a8be076 Rename .java to .kt 2022-07-19 11:41:49 +08:00
tarsin e3a87d44f4 Gallery info card tweaks 2022-07-18 22:15:28 +08:00
tarsin 1387b7acf9 Revert "Update proguard-rules.pro"
This reverts commit 77e5c3a347.
2022-07-18 22:11:22 +08:00
tarsin 3df6fed3ac Only use kotlin 1.7.10 2022-07-18 21:17:36 +08:00
tarsin 30d11e92c6 Regen gradle wrappers 2022-07-18 21:05:13 +08:00
tarsin 77e5c3a347 Update proguard-rules.pro 2022-07-18 21:03:08 +08:00
tarsin 2ddb26b1aa app: Remove unused res 2022-07-18 20:52:52 +08:00
tarsin 191db14398 app: Do code cleanup 2022-07-18 20:47:04 +08:00
tarsin 9dcc24f4d9 Make renovate bot happy 2022-07-18 20:44:00 +08:00
tarsin 05d29f4cfd Update dependencies 2022-07-18 20:19:09 +08:00
tarsin f257c936ba Use fwb's glSurfaceView 2022-07-18 16:57:43 +08:00
tarsin c2d8cf8b47 Use fwb's html parser
* Drop tagsoup as well

* Our minsdk is 26, no need to concern about api compatibility below 24 then
2022-07-18 16:34:10 +08:00
txyyh 7cfbfb5db5 Update screenshots (#97) 2022-07-17 16:53:16 +08:00
tarsin 9d00e47f74 Refactor gallery comment to adapt non-divider recyclerview 2022-07-17 15:55:58 +08:00
tarsin 452b373f62 Don't use dividers
* That's so fucking ugly
2022-07-17 15:33:57 +08:00
tarsin 8fa04644c4 Convert filter add dialog's spinner to dropdownmenu 2022-07-17 15:06:44 +08:00
tarsin 67024486e7 Rename .java to .kt 2022-07-17 15:06:44 +08:00
txyyh e1be24053b Version Bump (#94)
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-17 00:25:33 +08:00
txyyh 5ddf2b0d8f CI for pull request (#95) 2022-07-17 00:25:20 +08:00
tarsin 0802b0b4c6 Drop NekoInverter's email 2022-07-16 07:20:12 +08:00
tarsin 2a709e6190 Make all fragmentPref use fade anim 2022-07-15 13:34:31 +08:00
tarsin 156e4f990d Workaround for preference anim 2022-07-15 13:12:30 +08:00
tarsin 07c1737597 Drop takisoft's preferenceFix 2022-07-15 11:24:53 +08:00
tarsin a3f61ab4ef Remove some workground 2022-07-15 07:43:14 +08:00
tarsin 7aa3eff23f Fix fab size 2022-07-15 07:39:58 +08:00
renovate[bot] a79375843b Update dependency com.google.android.material:material to v1.7.0-alpha03 (#93)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-15 06:53:50 +08:00
renovate[bot] 1b50ce31f5 Update dependency gradle to v7.5 (#91)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-15 06:53:38 +08:00
tarsin 167fe89908 Version Bump
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-14 11:20:29 +08:00
tarsin 892b3ece41 Fix name shadow 2022-07-14 11:19:10 +08:00
tarsin 6dfa056e41 Use polly optimization
* Polly is always exist in ndk
2022-07-14 11:08:39 +08:00
tarsin 1a8afd86be Use llvm's Link Time Optimization 2022-07-14 09:51:47 +08:00
tarsin e0ef18659b We have no cpp things 2022-07-14 09:26:37 +08:00
tarsin 52be469b2d Optimize size 2022-07-14 09:24:47 +08:00
tarsin 6895289ec8 Upgrade to ndk r25 2022-07-14 08:37:57 +08:00
tarsin 3e86cbcf60 Convert a spinner to m3AutoCompleteTextInputLayout 2022-07-13 23:45:12 +08:00
tarsin 536d294f7b Rename .java to .kt 2022-07-13 23:45:12 +08:00
tarsin 8e7888df35 Use rikkax's correct m3Switch temporarily 2022-07-13 21:04:58 +08:00
tarsin 79703ce0dd Make simplemenuPreference md3 2022-07-13 20:56:25 +08:00
tarsin 25aa08d8e4 Use AppcompatSpinner 2022-07-13 20:06:25 +08:00
hathlife 35a77cd03d Add TV layout for initial wizard scenes. (#90)
* fix typo

* Add TV layout for initial setting scenes.

* Add credits
2022-07-13 07:36:55 +08:00
renovate[bot] 1198927d85 Update dependency org.jboss.forge.roaster:roaster-api to v2.26.0.Final (#87)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 22:49:38 +08:00
renovate[bot] f1c5e7660e Update dependency org.jboss.forge.roaster:roaster-jdt to v2.26.0.Final (#88)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 22:47:47 +08:00
txyyh 0f99403702 Use webp instead 2022-07-11 00:19:38 +08:00
tarsin 10170a274b Only need unlock when process is paused 2022-07-10 22:08:11 +08:00
tarsin 47630e6620 Implement #81 2022-07-10 20:05:13 +08:00
tarsin bb5799b414 Fix #86 2022-07-10 19:23:36 +08:00
renovate[bot] 2110e1b102 Update dependency dev.rikka.rikkax.core:core-ktx to v1.4.1 (#83)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-10 13:10:51 +08:00
hathlife 3dfbd102b4 Add Entry for Android TV launchers (#82) 2022-07-10 10:14:04 +08:00
renovate[bot] 0ba6500625 Update plugin org.jetbrains.kotlin.android to v1.7.10 (#80)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-08 00:06:29 +08:00
253 changed files with 5982 additions and 9139 deletions

No files matched your search

+43 -10
View File
@@ -1,8 +1,17 @@
name: Bug 反馈 / Bug report
description: 提交一个问题报告 / Create a bug report to help us improve
description: 提交一个问题报告 / Create a bug report
labels:
- "bug"
body:
- type: markdown
attributes:
value: |
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
提交问题报告前,还请首先完成文末的自查步骤
Thanks for your contribution on Ehviewer-Overhauled Project!
Please finish verify steps which list in the end first before create bug report
- type: textarea
id: reproduce
attributes:
@@ -44,12 +53,25 @@ body:
validations:
required: true
- type: textarea
id: more
attributes:
label: 备注 / Addition details
description: |
在此处写下其他您想说的内容
Describe addition details here
placeholder: |
其他有用的信息与附件
Additional details and attachments
validations:
required: false
- type: input
id: version
attributes:
label: EhViewer 版本号 / EhViewer version code
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
placeholder: 四位版本号 / 4 digits version code
placeholder: 1.6.9.13
validations:
required: true
@@ -57,8 +79,8 @@ body:
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
placeholder: https://github.com/asuka-mio/Ehviewer/actions/runs/XXXXXXXXXX
description: 直接将对应 GitHub Action CI 下载页面网址填入即可 / Simply drop GitHub Action CI download page url here
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
validations:
required: true
@@ -74,9 +96,20 @@ body:
- type: input
id: device
attributes:
label: 设备信息 / Device info
description: 特殊设备在此填入设备信息 / Put message here if you device is special
placeholder: WSA, arm-v7a
label: 设备型号 / Device model
description: 在此填入设备型号 / Put device model here
placeholder: OnePlus 7 Pro, Xiaomi 12 Ultra
validations:
required: true
- type: input
id: SoC
attributes:
label: SoC 型号 / Soc model
description: 在此填入 SoC 型号 / Put Soc model here
placeholder: 骁龙 8+ Gen 1, Snapdragon 8+ Gen 1
validations:
required: true
- type: textarea
id: log
@@ -98,14 +131,14 @@ body:
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
required: false
- label: 您已仔细查看并知情 [Q&A](https://github.com/asuka-mio/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/asuka-mio/Ehviewer/issues/61) carefully
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
required: true
- label: 您已搜索过 [Issue Tracker](https://github.com/asuka-mio/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/asuka-mio/Ehviewer/issues), No duplicate or related open issue has been found
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
required: true
- label: 您确保这个 Issue 只提及一个问题。如果您有多个问题报告,烦请发起多个 Issue / Ensure there is only one bug report in this issue. Please make mutiply issue for mutiply bugs
required: true
- label: 您确保已使用最新 CI 版本测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest CI version
required: true
required: true
+2 -1
View File
@@ -1,4 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 所有其他问题 / All other questions
url: https://github.com/asuka-mio/Ehviewer/discussions
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
about: 转到 Discussions / Turn to discussions
+17 -8
View File
@@ -1,8 +1,17 @@
name: 功能请求 / Feature request
description: 提出一个功能建议 / Suggest an idea for this project
description: 提出一个功能建议 / Suggest an idea
labels:
- "enhancement"
body:
- type: markdown
attributes:
value: |
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
提交功能建议前,还请首先完成文末的自查步骤
Thanks for your contribution on Ehviewer-Overhauled Project!
Please finish verify steps which list in the end first before suggest an idea
- type: textarea
id: request
attributes:
@@ -33,7 +42,7 @@ body:
- type: textarea
id: more
attributes:
label: 其他细节 / Addition details
label: 备注 / Addition details
description: |
在此处写下其他您想说的内容
Describe addition details here
@@ -48,7 +57,7 @@ body:
attributes:
label: EhViewer 版本号 / EhViewer version code
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
placeholder: 四位版本号 / 4 digits version code
placeholder: 1.6.9.13
validations:
required: true
@@ -56,8 +65,8 @@ body:
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
placeholder: https://github.com/asuka-mio/Ehviewer/actions/runs/XXXXXXXXXX
description: 直接将对应 GitHub Action CI 下载页面网址填入即可 / Simply drop GitHub Action CI download page url here
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
validations:
required: true
@@ -72,14 +81,14 @@ body:
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
required: false
- label: 您已仔细查看并知情 [Q&A](https://github.com/asuka-mio/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/asuka-mio/Ehviewer/issues/61) carefully
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
required: true
- label: 您已搜索过 [Issue Tracker](https://github.com/asuka-mio/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/asuka-mio/Ehviewer/issues), No duplicate or related open issue has been found
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
required: true
- label: 您确保这个 Issue 只提及一个功能。如果您有多个功能请求,烦请发起多个 Issue / Ensure there is only one feature request in this issue. Please make mutiply issue for mutiply feature request
required: true
- label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
required: true
required: true
+58
View File
@@ -0,0 +1,58 @@
name: CI
on:
push:
branches:
- '*'
tags-ignore:
- "*"
jobs:
linux:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
with:
gradle-home-cache-cleanup: true
arguments: assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Update x86_64
uses: actions/upload-artifact@v3
with:
name: x86_64-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86_64-release.apk
- name: Update ARM32
uses: actions/upload-artifact@v3
with:
name: armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
- name: Update x86
uses: actions/upload-artifact@v3
with:
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
@@ -1,6 +1,6 @@
name: EhViewer
name: Pull Request Check
on: push
on: [pull_request]
jobs:
linux:
@@ -9,6 +9,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -16,23 +18,19 @@ jobs:
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;24.0.8215888" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
with:
arguments: assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
with:
name: arm64-${{ github.sha }}
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Update x86_64
+20 -12
View File
@@ -12,6 +12,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -19,25 +21,31 @@ jobs:
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;24.0.8215888" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
with:
arguments: assembleRelease
- name: Rename Apks
run: |
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal-A11-.apk
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a-A11-.apk
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64-A11-.apk
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86-A11-.apk
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a-A11-.apk
- name: Releases
uses: softprops/action-gh-release@v0.1.14
uses: softprops/action-gh-release@v1
with:
body: Bump Version
discussion_category_name: Announcements
files: |
app/build/outputs/apk/release/app-arm64-v8a-release.apk
app/build/outputs/apk/release/app-x86_64-release.apk
app/build/outputs/apk/release/app-x86-release.apk
app/build/outputs/apk/release/app-armeabi-v7a-release.apk
app/build/outputs/apk/release/EhViewer-universal-A11-.apk
app/build/outputs/apk/release/EhViewer-arm64-v8a-A11-.apk
app/build/outputs/apk/release/EhViewer-x86_64-A11-.apk
app/build/outputs/apk/release/EhViewer-x86-A11-.apk
app/build/outputs/apk/release/EhViewer-armeabi-v7a-A11-.apk
+8 -5
View File
@@ -1,15 +1,18 @@
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
url = https://github.com/asuka-mio/libjpeg-turbo
url = https://github.com/Ehviewer-Overhauled/libjpeg-turbo
[submodule "native/src/main/jni/image/giflib/giflib"]
path = native/src/main/jni/image/giflib/giflib
url = https://gitlab.com/NekoInverter/giflib.git
url = https://github.com/Ehviewer-Overhauled/giflib
[submodule "native/src/main/jni/image/libpng/libpng"]
path = native/src/main/jni/image/libpng/libpng
url = https://gitlab.com/NekoInverter/libpng.git
url = https://github.com/Ehviewer-Overhauled/libpng
[submodule "native/src/main/jni/libarchive"]
path = native/src/main/jni/libarchive
url = https://github.com/asuka-mio/libarchive
url = https://github.com/Ehviewer-Overhauled/libarchive
[submodule "native/src/main/jni/liblzma"]
path = native/src/main/jni/liblzma
url = https://git.tukaani.org/xz.git
url = https://github.com/Ehviewer-Overhauled/xz
[submodule "native/src/main/jni/nettle/nettle"]
path = native/src/main/jni/nettle/nettle
url = https://github.com/Ehviewer-Overhauled/nettle
+61 -33
View File
@@ -1,57 +1,89 @@
<p align="right">
<strong>English<strong>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md">
简体中文
</a>
<span> | <span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md">
正體中文
</a>
</p>
<h1 align="center">
<img src="art/launcher_icon-web.png" width="200">
<img src="art/launcher_icon-web.svg" width="200">
<br>EhViewer<br>
</h1>
<p align="center">
<a href="https://github.com/asuka-mio/Ehviewer/actions">
<img src="https://img.shields.io/github/workflow/status/asuka-mio/Ehviewer/EhViewer" alt="Github Actions">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/asuka-mio/Ehviewer" alt="LICENSE">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/asuka-mio/ehviewer">
<img src="https://www.codefactor.io/repository/github/asuka-mio/ehviewer/badge" alt="CodeFactor">
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/asuka-mio/ehviewer?include_prereleases" alt="Release">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/asuka-mio/ehviewer" alt="Issues">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
</a>
</p>
## Description
这是一个 E-Hentai Android 平台的浏览器
已针对性适配 Material Design 3 及其动态取色系统
动态配色仅供 Android 12 及以上平台使用!
<div align="center">
<h3>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#description">
Description
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#download">
Download
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#screenshot">
Screenshot
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#thanks">
Thanks
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#license">
License
</a>
</h3>
</div>
An E-Hentai Application for Android.
# Description
with Material Design 3 and Dynamic Color Support.
EhViewer fork dedicated to lightweight and high-performance
Dynamic Color only support Android S and above!
with Material Design 3 and Dynamic Color Support
## Download
# Download
[Github 下载](https://github.com/asuka-mio/Ehviewer/releases)
<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>
## Screenshot
App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 and above, also the develop mainline. Version 1.7 support Android 11 and below, drop down some spicetify functions for Android 12 to make app useable on lower Android System
# Screenshot
![screenshot-01](art/screenshot-01.png)
![screenshot-02](art/screenshot-02.png)
## Thanks
本项目受到了诸多开源项目的帮助
# Thanks
Here is the libraries
- [AOSP](http://source.android.com/)
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
- [AOSP & AndroidX](http://source.android.com/)
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
- [GnuTLS](https://gnutls.org/)
- [material-components-android](https://github.com/material-components/material-components-android)
- [material-design-icons](https://github.com/google/material-design-icons)
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
- [apng](http://apng.sourceforge.net/)
- [giflib](http://giflib.sourceforge.net)
@@ -61,13 +93,11 @@ Here is the libraries
- [libpng](http://www.libpng.org/pub/png/libpng.html)
- [okhttp](https://github.com/square/okhttp)
- [roaster](https://github.com/forge/roaster)
- [TapTargetView](https://github.com/KeepSafe/TapTargetView)
- [TagSoup](http://home.ccil.org/~cowan/tagsoup/)
- [libarchive](http://www.libarchive.org/)
- [XZ Utils](https://tukaani.org/xz/)
## License
# License
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
@@ -78,5 +108,3 @@ Here is the libraries
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/>.
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fasuka-mio%2FEhviewer.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fasuka-mio%2FEhviewer?ref=badge_large)
+70 -17
View File
@@ -18,6 +18,7 @@ android {
enable = true
reset()
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
universalApk = true
}
}
@@ -34,8 +35,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 26
targetSdkVersion 33
versionCode 114527
versionName "1.7.28.0"
versionCode 114539
versionName "1.7.31.0-alpha04"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -92,27 +93,79 @@ tasks.withType(JavaCompile) {
}
dependencies {
implementation 'androidx.activity:activity:1.6.0-alpha05'
implementation 'androidx.appcompat:appcompat:1.6.0-alpha05'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
implementation 'androidx.browser:browser:1.4.0'
implementation 'androidx.fragment:fragment:1.5.0'
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"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
implementation "androidx.arch.core:core-runtime:$arch_core_version"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
implementation "androidx.browser:browser:1.4.0"
def collection_version = "1.3.0-alpha02"
implementation "androidx.collection:collection:$collection_version"
implementation "androidx.collection:collection-ktx:$collection_version"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
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.documentfile:documentfile:1.1.0-alpha01"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.5.4"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.6.0-alpha03"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
implementation "androidx.loader:loader:1.1.0"
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation 'androidx.webkit:webkit:1.4.0'
implementation "androidx.transition:transition-ktx:1.4.1"
implementation "androidx.vectordrawable:vectordrawable:1.2.0-beta01"
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.webkit:webkit:1.5.0'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.7.0-alpha02'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.0'
implementation 'com.google.android.material:material:1.8.0-alpha02'
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1'
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
implementation 'org.greenrobot:greendao:3.3.0'
implementation 'org.jsoup:jsoup:1.15.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jsoup:jsoup:1.15.3'
implementation projects.native
}
configurations.all {
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
exclude group: 'dev.rikka.rikkax.material', module: 'material'
}
+32 -32
View File
@@ -16,13 +16,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<!-- Required to maintain app compatibility. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
@@ -43,6 +51,7 @@
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
android:banner="@mipmap/ic_leanback_banner"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask"
@@ -53,6 +62,11 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
@@ -106,35 +120,21 @@
<data android:mimeType="application/x-7z-compressed" />
<data android:mimeType="application/x-rar-compressed" />
<data android:mimeType="application/x-zip-compressed" />
</intent-filter>
<data android:mimeType="application/x-tar" />
<data android:mimeType="application/x-xz" />
<data android:mimeType="application/gzip" />
<data android:mimeType="application/x-cbz" />
<data android:mimeType="application/x-cbr" />
<data android:mimeType="application/x-cbt" />
<data android:mimeType="application/x-cb7" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:pathPattern=".*\\.7z" />
<data android:pathPattern=".*\\.rar" />
<data android:pathPattern=".*\\.zip" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.7z" />
<data android:pathPattern=".*\\.rar" />
<data android:pathPattern=".*\\.zip" />
<!-- untested -->
<data android:mimeType="application/vnd.comicbook+zip" />
<data android:mimeType="application/vnd.comicbook-rar" />
<data android:mimeType="application/x-compressed-tar" />
<data android:mimeType="application/x-bzip-compressed-tar" />
<data android:mimeType="application/x-lzma-compressed-tar" />
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
</activity>
@@ -1,47 +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.app;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.R;
public class CheckBoxDialogBuilder extends MaterialAlertDialogBuilder {
private final CheckBox mCheckBox;
@SuppressLint("InflateParams")
public CheckBoxDialogBuilder(Context context, String message, String checkText, boolean checked) {
super(context);
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_checkbox_builder, null);
setView(view);
setMessage(message);
mCheckBox = view.findViewById(R.id.checkbox);
mCheckBox.setText(checkText);
mCheckBox.setChecked(checked);
view.setOnClickListener(v -> mCheckBox.toggle());
}
public boolean isChecked() {
return mCheckBox.isChecked();
}
}
@@ -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.app
import android.content.Context
import android.view.LayoutInflater
import android.widget.CheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.ehviewer.R
class CheckBoxDialogBuilder constructor(
context: Context,
message: String?,
checkText: String?,
checked: Boolean
) : MaterialAlertDialogBuilder(
context
) {
private val mCheckBox: CheckBox
val isChecked: Boolean
get() = mCheckBox.isChecked
init {
val view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_checkbox_builder, null)
setView(view)
setMessage(message)
mCheckBox = view.findViewById(R.id.checkbox)
mCheckBox.text = checkText
mCheckBox.isChecked = checked
view.setOnClickListener { mCheckBox.toggle() }
}
}
@@ -1,86 +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.app;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.R;
public class EditTextDialogBuilder extends MaterialAlertDialogBuilder implements EditText.OnEditorActionListener {
private final TextInputLayout mTextInputLayout;
private final EditText mEditText;
private AlertDialog mDialog;
@SuppressLint("InflateParams")
public EditTextDialogBuilder(Context context, String text, String hint) {
super(context);
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_edittext_builder, null);
setView(view);
mTextInputLayout = (TextInputLayout) view;
mEditText = view.findViewById(R.id.edit_text);
mEditText.setText(text);
mEditText.setSelection(mEditText.getText().length());
mEditText.setOnEditorActionListener(this);
mTextInputLayout.setHint(hint);
}
public EditText getEditText() {
return mEditText;
}
public String getText() {
return mEditText.getText().toString();
}
public void setError(CharSequence error) {
mTextInputLayout.setError(error);
}
@NonNull
@Override
public AlertDialog create() {
mDialog = super.create();
return mDialog;
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (mDialog != null) {
Button button = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (button != null) {
button.performClick();
}
return true;
} else {
return false;
}
}
}
@@ -0,0 +1,72 @@
/*
* 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.app
import android.content.Context
import android.content.DialogInterface
import android.view.KeyEvent
import android.view.LayoutInflater
import android.widget.EditText
import android.widget.TextView
import android.widget.TextView.OnEditorActionListener
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
import com.hippo.ehviewer.R
class EditTextDialogBuilder constructor(
context: Context,
text: String?,
hint: String?
) : MaterialAlertDialogBuilder(
context
), OnEditorActionListener {
private val mTextInputLayout: TextInputLayout
val editText: EditText
private var mDialog: AlertDialog? = null
val text: String
get() = editText.text.toString()
fun setError(error: CharSequence?) {
mTextInputLayout.error = error
}
override fun create(): AlertDialog {
mDialog = super.create()
return mDialog as AlertDialog
}
override fun onEditorAction(v: TextView?, p1: Int, event: KeyEvent?): Boolean {
return if (mDialog != null) {
val button = mDialog!!.getButton(DialogInterface.BUTTON_POSITIVE)
button?.performClick()
true
} else {
false
}
}
init {
val view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_edittext_builder, null)
setView(view)
mTextInputLayout = view as TextInputLayout
editText = view.findViewById(R.id.edit_text)
editText.setText(text)
editText.setSelection(editText.text.length)
editText.setOnEditorActionListener(this)
mTextInputLayout.hint = hint
}
}
@@ -1,73 +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.app;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ViewUtils;
public class ListCheckBoxDialogBuilder extends MaterialAlertDialogBuilder {
private final CheckBox mCheckBox;
private AlertDialog mDialog;
@SuppressLint("InflateParams")
public ListCheckBoxDialogBuilder(Context context, CharSequence[] items,
final OnItemClickListener listener, String checkText, boolean checked) {
super(context);
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_list_checkbox_builder, null);
setView(view);
ListView listView = (ListView) ViewUtils.$$(view, R.id.list_view);
mCheckBox = (CheckBox) ViewUtils.$$(view, R.id.checkbox);
listView.setAdapter(new ArrayAdapter<>(getContext(), R.layout.item_select_dialog, items));
listView.setOnItemClickListener((parent, view1, position, id) -> {
if (listener != null) {
listener.onItemClick(ListCheckBoxDialogBuilder.this, mDialog, position);
}
mDialog.dismiss();
});
mCheckBox.setText(checkText);
mCheckBox.setChecked(checked);
}
public boolean isChecked() {
return mCheckBox.isChecked();
}
@NonNull
@Override
public AlertDialog create() {
mDialog = super.create();
return mDialog;
}
public interface OnItemClickListener {
void onItemClick(ListCheckBoxDialogBuilder builder, AlertDialog dialog, int position);
}
}
@@ -0,0 +1,65 @@
/*
* 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.app
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.CheckBox
import android.widget.ListView
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.ehviewer.R
import com.hippo.yorozuya.ViewUtils
class ListCheckBoxDialogBuilder constructor(
context: Context, items: Array<CharSequence>,
listener: OnItemClickListener?, checkText: String?, checked: Boolean
) : MaterialAlertDialogBuilder(
context
) {
private val mCheckBox: CheckBox
private var mDialog: AlertDialog? = null
val isChecked: Boolean
get() = mCheckBox.isChecked
override fun create(): AlertDialog {
mDialog = super.create()
return mDialog as AlertDialog
}
interface OnItemClickListener {
fun onItemClick(builder: ListCheckBoxDialogBuilder?, dialog: AlertDialog?, position: Int)
}
init {
val view =
LayoutInflater.from(getContext()).inflate(R.layout.dialog_list_checkbox_builder, null)
setView(view)
val listView = ViewUtils.`$$`(view, R.id.list_view) as ListView
mCheckBox = ViewUtils.`$$`(view, R.id.checkbox) as CheckBox
listView.adapter = ArrayAdapter(getContext(), R.layout.item_select_dialog, items)
listView.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
listener?.onItemClick(this@ListCheckBoxDialogBuilder, mDialog, position)
mDialog?.dismiss()
}
mCheckBox.text = checkText
mCheckBox.isChecked = checked
}
}
@@ -166,7 +166,16 @@ public final class DiskLruCache implements Closeable {
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0; private final Callable<Void> cleanupCallable = new Callable<>() {
private long nextSequenceNumber = 0;
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
@@ -182,16 +191,6 @@ public final class DiskLruCache implements Closeable {
}
};
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
@@ -980,4 +979,6 @@ public final class DiskLruCache implements Closeable {
}
}
@@ -51,7 +51,6 @@ public final class ConacoTask<V> {
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
private final String mKey;
private final String mUrl;
private final DataContainer mDataContainer;
private final boolean mUseMemoryCache;
private final boolean mUseDiskCache;
private final boolean mUseNetwork;
@@ -73,7 +72,6 @@ public final class ConacoTask<V> {
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
mKey = builder.mKey;
mUrl = builder.mUrl;
mDataContainer = builder.mDataContainer;
mUseMemoryCache = builder.mUseMemoryCache;
mUseDiskCache = builder.mUseDiskCache;
mUseNetwork = builder.mUseNetwork;
@@ -85,42 +83,6 @@ public final class ConacoTask<V> {
mConaco = builder.mConaco;
}
private static void putFromDiskCacheToDataContainer(String key, ValueCache cache, DataContainer container) {
SimpleDiskCache diskCache = cache.getDiskCache();
if (diskCache != null) {
InputStreamPipe pipe = diskCache.getInputStreamPipe(key);
if (pipe != null) {
try {
pipe.obtain();
container.save(pipe.open(), -1L, null, null);
} catch (IOException e) {
Log.d(TAG, "Can't save value from disk cache to data container");
e.printStackTrace();
container.remove();
} finally {
pipe.close();
pipe.release();
}
}
}
}
private static void putFromDataContainerToDiskCache(String key, ValueCache cache, DataContainer container) {
InputStreamPipe pipe = container.get();
if (pipe != null) {
try {
pipe.obtain();
cache.putRawToDisk(key, pipe.open());
} catch (IOException e) {
Log.w(TAG, "Can't save value from data container to disk cache", e);
cache.removeFromDisk(key);
} finally {
pipe.close();
pipe.release();
}
}
}
int getId() {
return mId;
}
@@ -219,7 +181,6 @@ public final class ConacoTask<V> {
private Unikery<T> mUnikery;
private String mKey;
private String mUrl;
private DataContainer mDataContainer;
private boolean mUseMemoryCache = true;
private boolean mUseDiskCache = true;
private boolean mUseNetwork = true;
@@ -262,11 +223,6 @@ public final class ConacoTask<V> {
return this;
}
public Builder<T> setDataContainer(DataContainer dataContainer) {
mDataContainer = dataContainer;
return this;
}
boolean isUseMemoryCache() {
return mUseMemoryCache;
}
@@ -332,7 +288,7 @@ public final class ConacoTask<V> {
if (mUnikery == null) {
throw new IllegalStateException("Must set unikery");
}
if (mKey == null && mUrl == null && mDataContainer == null) {
if (mKey == null && mUrl == null) {
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
}
}
@@ -352,26 +308,13 @@ public final class ConacoTask<V> {
} else {
V value = null;
// First check data container
if (mDataContainer != null && mDataContainer.isEnabled()) {
InputStreamPipe isp = mDataContainer.get();
if (isp != null) {
value = mHelper.decode(isp);
}
}
// Then check disk cache
if (mKey != null) {
if (value == null && mUseDiskCache) {
value = mCache.getFromDisk(mKey);
// Put back to data container
if (value != null && mDataContainer != null && mDataContainer.isEnabled()) {
putFromDiskCacheToDataContainer(mKey, mCache, mDataContainer);
}
}
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
// Put it to memory
mCache.putToMemory(mKey, value);
}
}
@@ -451,18 +394,6 @@ public final class ConacoTask<V> {
}
}
private boolean putToDataContainer(InputStream is, ResponseBody body) {
// Get media type
String mediaType;
MediaType mt = body.contentType();
if (mt != null) {
mediaType = mt.type() + '/' + mt.subtype();
} else {
mediaType = null;
}
return mDataContainer.save(is, body.contentLength(), mediaType, this);
}
@Override
protected V doInBackground(Void... params) {
if (isNotNecessary(this)) {
@@ -489,7 +420,7 @@ public final class ConacoTask<V> {
return null;
}
if ((mDataContainer == null || !mDataContainer.isEnabled()) && mKey != null) {
if (mKey != null) {
if (putToDiskCache(is, body.contentLength())) {
// Get object from disk cache
value = mCache.getFromDisk(mKey);
@@ -506,37 +437,6 @@ public final class ConacoTask<V> {
mCache.removeFromDisk(mKey);
return null;
}
} else if (mDataContainer != null && mDataContainer.isEnabled()) {
// Check url Moved
HttpUrl requestHttpUrl = request.url();
HttpUrl responseHttpUrl = response.request().url();
if (!responseHttpUrl.equals(requestHttpUrl)) {
mDataContainer.onUrlMoved(mUrl, responseHttpUrl.url().toString());
}
// Put to data container
if (!putToDataContainer(is, body)) {
return null;
}
// Get value from data container
InputStreamPipe isp = mDataContainer.get();
if (isp == null) {
return null;
}
value = mHelper.decode(isp);
if (value == null) {
mDataContainer.remove();
} else if (mKey != null) {
// Put to disk cache
putFromDataContainerToDiskCache(mKey, mCache, mDataContainer);
if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
// Put it to memory
mCache.putToMemory(mKey, value);
}
}
return value;
} else {
return null;
}
@@ -1,52 +0,0 @@
/*
* Copyright 2015-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.conaco;
import androidx.annotation.Nullable;
import com.hippo.streampipe.InputStreamPipe;
import java.io.InputStream;
public interface DataContainer {
/**
* Returns the enabled status for this DataContainer.
*/
boolean isEnabled();
/**
* Get 304 or something like that
*/
void onUrlMoved(String requestUrl, String responseUrl);
/**
* Save the {@code InputStream}
*/
boolean save(InputStream is, long length, @Nullable String mediaType, @Nullable ProgressNotifier notify);
/**
* Get {@code InputStreamPipe} for saved before
*/
@Nullable
InputStreamPipe get();
/**
* Remove saved stuff
*/
void remove();
}
@@ -1,131 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.database;
/*
* Created by Hippo on 2017/9/3.
*/
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.SparseArray;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MSQLiteBuilder {
public static final String COLUMN_ID = "_id";
private static final Map<Class, String> JAVA_TYPE_TO_SQLITE_TYPE = new HashMap<>();
static {
JAVA_TYPE_TO_SQLITE_TYPE.put(boolean.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(byte.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(short.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(int.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(long.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(float.class, "REAL NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(double.class, "REAL NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(String.class, "TEXT");
}
private final SparseArray<List<String>> statementsMap = new SparseArray<>();
private int version = 0;
private List<String> statements;
private static String javaTypeToSQLiteType(Class clazz) {
String type = JAVA_TYPE_TO_SQLITE_TYPE.get(clazz);
if (type == null) {
throw new IllegalStateException("Unknown type: " + clazz);
}
return type;
}
/**
* Bump database version.
*/
public MSQLiteBuilder version(int version) {
if (version <= this.version) {
throw new IllegalStateException("New version must be bigger than current version. "
+ "current version: " + this.version + ", new version: " + version + ".");
}
this.version = version;
this.statements = new ArrayList<>();
this.statementsMap.put(version, this.statements);
return this;
}
/**
* Creates a table with int {@link #COLUMN_ID} primary key.
*/
public MSQLiteBuilder createTable(String table) {
return createTable(table, COLUMN_ID, int.class);
}
/**
* Creates a table.
*/
public MSQLiteBuilder createTable(String table, String column, Class clazz) {
return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);");
}
/**
* Drops a table.
*/
public MSQLiteBuilder dropTable(String table) {
return statement("DROP TABLE " + table + ";");
}
/**
* Inserts a column to the table.
*/
public MSQLiteBuilder insertColumn(String table, String column, Class clazz) {
return statement("ALTER TABLE " + table + " ADD COLUMN " + column + " " + javaTypeToSQLiteType(clazz) + ";");
}
/**
* Add a statement.
*/
public MSQLiteBuilder statement(String statement) {
if (version == 0 || statements == null) {
throw new IllegalStateException("Call version() first!");
}
statements.add(statement);
return this;
}
/**
* Build a SQLiteOpenHelper from it.
*/
public SQLiteOpenHelper build(Context context, String name, int version) {
return new MSQLiteOpenHelper(context, name, version, this);
}
List<String> getStatements(int oldVersion, int newVersion) {
List<String> result = new ArrayList<>();
for (int i = oldVersion + 1; i <= newVersion; ++i) {
List<String> list = statementsMap.get(i);
if (list != null) {
result.addAll(list);
}
}
return result;
}
}
@@ -0,0 +1,124 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.database
import android.content.Context
import android.database.sqlite.SQLiteOpenHelper
import android.util.SparseArray
class MSQLiteBuilder {
companion object {
const val COLUMN_ID = "_id"
private val JAVA_TYPE_TO_SQLITE_TYPE: MutableMap<Class<*>?, String> = HashMap()
private fun javaTypeToSQLiteType(clazz: Class<*>?): String {
return JAVA_TYPE_TO_SQLITE_TYPE[clazz]
?: throw IllegalStateException("Unknown type: $clazz")
}
init {
JAVA_TYPE_TO_SQLITE_TYPE[Boolean::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Byte::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Short::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Int::class.javaPrimitiveType] = "INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Long::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Float::class.javaPrimitiveType] =
"REAL NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Double::class.javaPrimitiveType] = "REAL NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[String::class.java] = "TEXT"
}
}
private val statementsMap = SparseArray<List<String>?>()
private var version = 0
private var statements: MutableList<String>? = null
/**
* Bump database version.
*/
fun version(version: Int): MSQLiteBuilder {
check(version > this.version) {
("New version must be bigger than current version. "
+ "current version: " + this.version + ", new version: " + version + ".")
}
this.version = version
statements = ArrayList()
statementsMap.put(version, statements)
return this
}
/**
* Creates a table.
*/
/**
* Creates a table with int [.COLUMN_ID] primary key.
*/
@JvmOverloads
fun createTable(
table: String,
column: String = COLUMN_ID,
clazz: Class<*>? = Int::class.javaPrimitiveType
): MSQLiteBuilder {
return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);")
}
/**
* Drops a table.
*/
fun dropTable(table: String): MSQLiteBuilder {
return statement("DROP TABLE $table;")
}
/**
* Inserts a column to the table.
*/
fun insertColumn(table: String, column: String, clazz: Class<*>?): MSQLiteBuilder {
return statement(
"ALTER TABLE $table ADD COLUMN $column " + javaTypeToSQLiteType(
clazz
) + ";"
)
}
/**
* Add a statement.
*/
fun statement(statement: String): MSQLiteBuilder {
check(!(version == 0 || statements == null)) { "Call version() first!" }
statements!!.add(statement)
return this
}
/**
* Build a SQLiteOpenHelper from it.
*/
fun build(context: Context?, name: String?, version: Int): SQLiteOpenHelper {
return MSQLiteOpenHelper(context, name, version, this)
}
fun getStatements(oldVersion: Int, newVersion: Int): List<String> {
val result: MutableList<String> = ArrayList()
for (i in oldVersion + 1..newVersion) {
val list = statementsMap[i]
if (list != null) {
result.addAll(list)
}
}
return result
}
}
@@ -1,57 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.database;
/*
* Created by Hippo on 2017/9/4.
*/
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.hippo.util.SqlUtils;
class MSQLiteOpenHelper extends SQLiteOpenHelper {
private final int version;
private final MSQLiteBuilder builder;
public MSQLiteOpenHelper(Context context, String name, int version, MSQLiteBuilder builder) {
super(context, name, null, version);
this.version = version;
this.builder = builder;
}
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, version);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (String command : builder.getStatements(oldVersion, newVersion)) {
db.execSQL(command);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
SqlUtils.dropAllTable(db);
onCreate(db);
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.database
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import com.hippo.util.SqlUtils
internal class MSQLiteOpenHelper(
context: Context?,
name: String?,
private val version: Int,
private val builder: MSQLiteBuilder
) : SQLiteOpenHelper(context, name, null, version) {
override fun onCreate(db: SQLiteDatabase) {
onUpgrade(db, 0, version)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
for (command in builder.getStatements(oldVersion, newVersion)) {
db.execSQL(command)
}
}
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
SqlUtils.dropAllTable(db)
onCreate(db)
}
}
@@ -1,271 +0,0 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.easyrecyclerview;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
// Add overlap
/**
* Only work for {@link androidx.recyclerview.widget.LinearLayoutManager}.<br>
* Show divider between item, just like
* {@link android.widget.ListView#setDivider(android.graphics.drawable.Drawable)}
*/
public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL = LinearLayoutManager.VERTICAL;
private final Rect mRect;
private final Paint mPaint;
private boolean mShowFirstDivider = false;
private boolean mShowLastDivider = false;
private int mOrientation;
private int mThickness;
private int mPaddingStart = 0;
private int mPaddingEnd = 0;
private boolean mOverlap = false;
private ShowDividerHelper mShowDividerHelper;
public LinearDividerItemDecoration(int orientation, int color, int thickness) {
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
setOrientation(orientation);
setColor(color);
setThickness(thickness);
}
public void setShowDividerHelper(ShowDividerHelper showDividerHelper) {
mShowDividerHelper = showDividerHelper;
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL && orientation != VERTICAL) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
public void setColor(int color) {
mPaint.setColor(color);
}
public void setThickness(int thickness) {
mThickness = thickness;
}
public void setShowFirstDivider(boolean showFirstDivider) {
mShowFirstDivider = showFirstDivider;
}
public void setShowLastDivider(boolean showLastDivider) {
mShowLastDivider = showLastDivider;
}
public void setPadding(int padding) {
setPaddingStart(padding);
setPaddingEnd(padding);
}
public void setPaddingStart(int paddingStart) {
mPaddingStart = paddingStart;
}
public void setPaddingEnd(int paddingEnd) {
mPaddingEnd = paddingEnd;
}
public void setOverlap(boolean overlap) {
mOverlap = overlap;
}
@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
if (parent.getAdapter() == null) {
outRect.set(0, 0, 0, 0);
return;
}
if (mOverlap) {
outRect.set(0, 0, 0, 0);
return;
}
final int position = parent.getChildLayoutPosition(view);
final int itemCount = parent.getAdapter().getItemCount();
if (mShowDividerHelper != null) {
if (mOrientation == VERTICAL) {
if (position == 0 && mShowDividerHelper.showDivider(0)) {
outRect.top = mThickness;
}
if (mShowDividerHelper.showDivider(position + 1)) {
outRect.bottom = mThickness;
}
} else {
if (position == 0 && mShowDividerHelper.showDivider(0)) {
outRect.left = mThickness;
}
if (mShowDividerHelper.showDivider(position + 1)) {
outRect.right = mThickness;
}
}
} else {
if (mOrientation == VERTICAL) {
if (position == 0 && mShowFirstDivider) {
outRect.top = mThickness;
}
outRect.bottom = mThickness;
if (position == itemCount - 1 && !mShowLastDivider) {
outRect.bottom = 0;
}
} else {
if (position == 0 && mShowFirstDivider) {
outRect.left = mThickness;
}
outRect.right = mThickness;
if (position == itemCount - 1 && !mShowLastDivider) {
outRect.right = 0;
}
}
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent,
RecyclerView.State state) {
RecyclerView.Adapter adapter = parent.getAdapter();
if (adapter == null) {
return;
}
int itemCount = adapter.getItemCount();
boolean overlap = mOverlap;
if (mOrientation == VERTICAL) {
final boolean isRtl = ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL;
int mPaddingLeft;
int mPaddingRight;
if (isRtl) {
mPaddingLeft = mPaddingEnd;
mPaddingRight = mPaddingStart;
} else {
mPaddingLeft = mPaddingStart;
mPaddingRight = mPaddingEnd;
}
final int left = parent.getPaddingLeft() + mPaddingLeft;
final int right = parent.getWidth() - parent.getPaddingRight() - mPaddingRight;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
final int position = parent.getChildLayoutPosition(child);
boolean show;
if (mShowDividerHelper != null) {
show = mShowDividerHelper.showDivider(position + 1);
} else {
show = position != itemCount - 1 || mShowLastDivider;
}
if (show) {
int top = child.getBottom() + lp.bottomMargin;
if (overlap) {
top -= mThickness;
}
final int bottom = top + mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
}
if (position == 0) {
if (mShowDividerHelper != null) {
show = mShowDividerHelper.showDivider(0);
} else {
show = mShowFirstDivider;
}
if (show) {
int bottom = child.getTop() + lp.topMargin;
if (overlap) {
bottom += mThickness;
}
final int top = bottom - mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
}
}
}
} else {
final int top = parent.getPaddingTop() + mPaddingStart;
final int bottom = parent.getHeight() - parent.getPaddingBottom() - mPaddingEnd;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
final int position = parent.getChildLayoutPosition(child);
boolean show;
if (mShowDividerHelper != null) {
show = mShowDividerHelper.showDivider(position + 1);
} else {
show = position != itemCount - 1 || mShowLastDivider;
}
if (show) {
int left = child.getRight() + lp.rightMargin;
if (overlap) {
left -= mThickness;
}
final int right = left + mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
}
if (position == 0) {
if (mShowDividerHelper != null) {
show = mShowDividerHelper.showDivider(0);
} else {
show = mShowFirstDivider;
}
if (show) {
int right = child.getLeft() + lp.leftMargin;
if (overlap) {
right += mThickness;
}
final int left = right - mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
}
}
}
}
}
public interface ShowDividerHelper {
boolean showDivider(int index);
}
}
@@ -27,6 +27,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Debug;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.View;
@@ -38,6 +39,9 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.collection.LruCache;
import androidx.core.os.LocaleListCompat;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.Native;
@@ -60,7 +64,6 @@ import com.hippo.image.Image;
import com.hippo.image.ImageBitmap;
import com.hippo.network.StatusCodeException;
import com.hippo.scene.SceneApplication;
import com.hippo.text.Html;
import com.hippo.unifile.UniFile;
import com.hippo.util.BitmapUtils;
import com.hippo.util.ClipboardUtil;
@@ -104,6 +107,8 @@ public class EhApplication extends SceneApplication {
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
private static final long DEBUG_PRINT_INTERVAL = 3000L;
public static boolean locked = false;
private static EhApplication instance;
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
@@ -300,6 +305,7 @@ public class EhApplication extends SceneApplication {
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
ProcessLifecycleOwner.get().getLifecycle().addObserver(new EhlifecycleObserver());
instance = this;
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
@@ -325,7 +331,6 @@ public class EhApplication extends SceneApplication {
StatusCodeException.initialize(this);
Settings.initialize(this);
ReadableTime.initialize(this);
Html.initialize(this);
AppConfig.initialize(this);
SpiderDen.initialize(this);
EhDB.initialize(this);
@@ -581,4 +586,11 @@ public class EhApplication extends SceneApplication {
return null;
}
}
static class EhlifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onPause(@NonNull LifecycleOwner owner) {
locked = true;
}
}
}
@@ -41,6 +41,7 @@ import com.hippo.yorozuya.NumberUtils;
import java.io.File;
import java.util.Locale;
import java.util.Set;
public class Settings {
@@ -99,6 +100,11 @@ public class Settings {
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;
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
private static int LIST_THUMB_SIZE = 40;
public static boolean LIST_THUMB_SIZE_INITED = false;
private static final String KEY_AVATAR = "avatar";
private static final String DEFAULT_AVATAR = null;
private static final String KEY_SHOW_WARNING = "show_warning";
@@ -271,6 +277,11 @@ public class Settings {
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
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 Context sContext;
private static SharedPreferences sSettingsPre;
private static EhConfig sEhConfig;
@@ -384,6 +395,21 @@ public class Settings {
sSettingsPre.edit().putString(key, value).apply();
}
public static Set<String> getStringSet(String key) {
return sSettingsPre.getStringSet(key, null);
}
public static void putStringToStringSet(String key, String value) {
Set<String> set = getStringSet(key);
if (set == null)
set = Set.of(value);
else if (set.contains(value))
return;
else
set.add(value);
sSettingsPre.edit().putStringSet(key, set).apply();
}
public static int getIntFromStr(String key, int defValue) {
try {
return NumberUtils.parseIntSafely(sSettingsPre.getString(key, Integer.toString(defValue)), defValue);
@@ -1139,4 +1165,22 @@ public class Settings {
public static void putClipboardTextHashCode(int value) {
putInt(KEY_CLIPBOARD_TEXT_HASH_CODE, value);
}
public static Set<String> getArchivePasswds() {
return getStringSet(KEY_ARCHIVE_PASSWDS);
}
public static void putPasswdToArchivePasswds(String value) {
putStringToStringSet(KEY_ARCHIVE_PASSWDS, value);
}
public static int getListThumbSize() {
if (LIST_THUMB_SIZE_INITED) {
return LIST_THUMB_SIZE;
}
int size = 3 * getInt(KEY_LIST_THUMB_SIZE, DEFAULT_LIST_THUMB_SIZE);
LIST_THUMB_SIZE = size;
LIST_THUMB_SIZE_INITED = true;
return size;
}
}
@@ -44,7 +44,7 @@ public class FavListUrlBuilder implements Parcelable {
}
};
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
private int mIndex;
private String mNext;
private String mKeyword;
private int mFavCat = FAV_CAT_ALL;
@@ -52,7 +52,7 @@ public class FavListUrlBuilder implements Parcelable {
}
protected FavListUrlBuilder(Parcel in) {
this.mIndex = in.readInt();
this.mNext = in.readString();
this.mKeyword = in.readString();
this.mFavCat = in.readInt();
}
@@ -61,8 +61,8 @@ public class FavListUrlBuilder implements Parcelable {
return favCat >= 0 && favCat <= 9;
}
public void setIndex(int index) {
mIndex = index;
public void setNext(String next) {
mNext = next;
}
public String getKeyword() {
@@ -105,8 +105,8 @@ public class FavListUrlBuilder implements Parcelable {
Log.e(TAG, "Can't URLEncoder.encode " + mKeyword);
}
}
if (mIndex > 0) {
ub.addQuery("page", Integer.toString(mIndex));
if (mNext != null && !mNext.isEmpty()) {
ub.addQuery("next", mNext);
}
return ub.build();
}
@@ -118,7 +118,7 @@ public class FavListUrlBuilder implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mIndex);
dest.writeString(this.mNext);
dest.writeString(this.mKeyword);
dest.writeInt(this.mFavCat);
}
@@ -64,7 +64,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
@Mode
private int mMode = MODE_NORMAL;
private int mPageIndex = 0;
private int mNextGid = 0;
private int mPrevGid = 0;
private int mCategory = EhUtils.NONE;
private String mKeyword = null;
@@ -86,7 +87,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
@SuppressWarnings("WrongConstant")
protected ListUrlBuilder(Parcel in) {
this.mMode = in.readInt();
this.mPageIndex = in.readInt();
this.mNextGid = in.readInt();
this.mPrevGid = in.readInt();
this.mCategory = in.readInt();
this.mKeyword = in.readString();
this.mAdvanceSearch = in.readInt();
@@ -105,7 +107,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
*/
public void reset() {
mMode = MODE_NORMAL;
mPageIndex = 0;
mNextGid = 0;
mPrevGid = 0;
mCategory = EhUtils.NONE;
mKeyword = null;
mAdvanceSearch = -1;
@@ -137,12 +140,12 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
mMode = mode;
}
public int getPageIndex() {
return mPageIndex;
public void setNextGid(int nextGid) {
mNextGid = nextGid;
}
public void setPageIndex(int pageIndex) {
mPageIndex = pageIndex;
public void setPrevGid(int prevGid) {
mPrevGid = prevGid;
}
public int getCategory() {
@@ -233,7 +236,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
*/
public void set(ListUrlBuilder lub) {
mMode = lub.mMode;
mPageIndex = lub.mPageIndex;
mNextGid = lub.mNextGid;
mPrevGid = lub.mPrevGid;
mCategory = lub.mCategory;
mKeyword = lub.mKeyword;
mAdvanceSearch = lub.mAdvanceSearch;
@@ -524,9 +528,11 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
if (mSHash != null) {
ub.addQuery("f_shash", mSHash);
}
// Page index
if (mPageIndex != 0) {
ub.addQuery("page", mPageIndex);
if (mNextGid != 0) {
ub.addQuery("next", mNextGid);
}
if (mPrevGid != 0) {
ub.addQuery("prev", mPrevGid);
}
// Advance search
if (mAdvanceSearch != -1) {
@@ -570,8 +576,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
} catch (UnsupportedEncodingException e) {
// Empty
}
if (mPageIndex != 0) {
sb.append('/').append(mPageIndex);
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid);
}
return sb.toString();
}
@@ -583,8 +589,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
} catch (UnsupportedEncodingException e) {
// Empty
}
if (mPageIndex != 0) {
sb.append('/').append(mPageIndex);
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid);
}
return sb.toString();
}
@@ -600,8 +606,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
} catch (UnsupportedEncodingException e) {
// Empty
}
if (mPageIndex != 0) {
sb.append("&p=").append(mPageIndex);
if (mNextGid != 0) {
sb.append("&p=").append(mNextGid);
}
return sb.toString();
}
@@ -615,7 +621,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mMode);
dest.writeInt(this.mPageIndex);
dest.writeInt(this.mNextGid);
dest.writeInt(this.mPrevGid);
dest.writeInt(this.mCategory);
dest.writeString(this.mKeyword);
dest.writeInt(this.mAdvanceSearch);
@@ -31,8 +31,10 @@ import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.List;
import java.util.regex.Pattern;
public class FavoritesParser {
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>")) {
@@ -59,14 +61,25 @@ public class FavoritesParser {
e.printStackTrace();
throw new ParseException("Parse favorites error", body);
}
Result re = new Result();
try {
var d = Jsoup.parse(body);
var next = d.getElementById("unext");
assert next != null;
var href = next.attr("href");
var matcher = PATTERN_NEXT_PAGE.matcher(href);
if (matcher.find()) {
re.nextPage = matcher.group(1);
}
} catch (Throwable e) {
e.printStackTrace();
}
GalleryListParser.Result result = GalleryListParser.parse(body);
Result re = new Result();
re.catArray = catArray;
re.countArray = countArray;
re.pages = result.pages;
re.nextPage = result.nextPage;
re.galleryInfoList = result.galleryInfoList;
return re;
@@ -75,8 +88,7 @@ public class FavoritesParser {
public static class Result {
public String[] catArray; // Size 10
public int[] countArray; // Size 10
public int pages;
public int nextPage;
public String nextPage;
public List<GalleryInfo> galleryInfoList;
}
}
@@ -51,7 +51,8 @@ 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_NEXT_PAGE = Pattern.compile("page=(\\d+)");
private static final Pattern PATTERN_GETINT = Pattern.compile("[^0-9]");
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
new String[]{"0", "0", "0"},
@@ -297,39 +298,21 @@ public class GalleryListParser {
return gi;
}
private static int getInt(String str) {
var m = PATTERN_GETINT.matcher(str);
return Integer.parseInt(m.replaceAll("").trim());
}
public static Result parse(@NonNull String body) throws Exception {
Result result = new Result();
Document d = Jsoup.parse(body);
try {
Element ptt = d.getElementsByClass("ptt").first();
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) {
ExceptionUtils.throwIfFatal(e);
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
if (body.contains("No hits found</p>")) {
result.pages = 0;
//noinspection unchecked
result.galleryInfoList = Collections.EMPTY_LIST;
return result;
} else if (d.getElementsByClass("ptt").isEmpty()) {
result.pages = 1;
} else {
result.pages = Integer.MAX_VALUE;
}
result.founds = Integer.MAX_VALUE;
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
if (body.contains("No hits found</p>")) {
result.founds = 0;
result.galleryInfoList = Collections.EMPTY_LIST;
return result;
}
try {
@@ -350,7 +333,7 @@ public class GalleryListParser {
}
if (list.isEmpty()) {
if (es.size() < 2 || !NO_UNFILTERED_TEXT.equals(es.get(1).text())) {
throw new ParseException("No gallery", body);
Log.d(TAG, "No gallery found");
}
}
result.galleryInfoList = list;
@@ -364,8 +347,7 @@ public class GalleryListParser {
}
public static class Result {
public int pages;
public int nextPage;
public int founds;
public boolean noWatchedTags;
public List<GalleryInfo> galleryInfoList;
}
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.gallery;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Process;
import androidx.annotation.NonNull;
@@ -26,22 +27,26 @@ import androidx.annotation.Nullable;
import com.hippo.UriArchiveAccessor;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.thread.PVLock;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
public class ArchiveGalleryProvider extends GalleryProvider2 {
private static final AtomicInteger sIdGenerator = new AtomicInteger();
public static Handler showPasswd;
public static String passwd;
public static PVLock pv = new PVLock(0);
private final UriArchiveAccessor archiveAccessor;
private final Stack<Integer> requests = new Stack<>();
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
@@ -56,7 +61,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
UriArchiveAccessor archiveAccessor1;
try {
archiveAccessor1 = new UriArchiveAccessor(context, uri);
} catch (FileNotFoundException e) {
} catch (Exception e) {
archiveAccessor1 = null;
e.printStackTrace();
}
@@ -90,6 +95,11 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
decodeThread.interrupt();
decodeThread = null;
}
try {
archiveAccessor.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
@@ -159,9 +169,27 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
}
private class ArchiveTask implements Runnable {
private void waitPasswd() throws InterruptedException {
showPasswd.sendEmptyMessage(0);
pv.p();
}
private void notifyError() {
showPasswd.sendEmptyMessage(1);
}
private void notifyDismiss() {
showPasswd.sendEmptyMessage(2);
}
@Override
public void run() {
size = archiveAccessor.open();
try {
size = archiveAccessor.open();
} catch (Exception e) {
e.printStackTrace();
size = 0;
}
if (size <= 0) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
@@ -171,6 +199,34 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
// Update size and notify changed
notifyDataChanged();
if (archiveAccessor.needPassword()) {
boolean need_request = true;
Set<String> set = Settings.getArchivePasswds();
if (set != null) {
for (String passwd : set) {
if (archiveAccessor.providePassword(passwd)) {
need_request = false;
break;
}
}
}
while (!Thread.currentThread().isInterrupted() && need_request) {
try {
waitPasswd();
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
if (!archiveAccessor.providePassword(passwd))
notifyError();
else {
Settings.putPasswdToArchivePasswds(passwd);
notifyDismiss();
break;
}
}
}
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (requests) {
@@ -194,7 +250,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
Pipe pipe = new Pipe(4096 * 16);
Pipe pipe = new Pipe();
synchronized (streams) {
if (streams.get(index) != null) {
@@ -204,7 +260,8 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
streams.notify();
}
archiveAccessor.extractTargetIndexToOutputStream(index, pipe.getOutputStream());
archiveAccessor.extracttoOutputStream(index, pipe.getOutputFd());
pipe.closeOutputFd();
}
}
}
@@ -16,141 +16,37 @@
package com.hippo.ehviewer.gallery;
import androidx.annotation.NonNull;
import android.os.ParcelFileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
class Pipe {
private final int capacity;
private final byte[] buffer;
private int head = 0;
private int tail = 0;
private boolean full = false;
private boolean inClosed = false;
private boolean outClosed = false;
private final InputStream inputStream = new InputStream() {
@Override
public int read() throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[1];
if (read(bytes, 0, 1) != -1) {
return bytes[0];
} else {
return -1;
}
}
private ParcelFileDescriptor[] fd;
{
try {
fd = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
e.printStackTrace();
}
@Override
public int read(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
for (; ; ) {
if (inClosed) {
throw new IOException("The InputStream is closed");
}
if (len == 0) {
return 0;
}
if (head == tail && !full) {
if (outClosed) {
// No bytes available and the OutputStream is closed. So it's the end.
return -1;
} else {
// Wait for OutputStream write bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
}
} else {
int read = Math.min(len, (head < tail ? tail : capacity) - head);
System.arraycopy(buffer, head, b, off, read);
head += read;
if (head == capacity) {
head = 0;
}
full = false;
Pipe.this.notifyAll();
return read;
}
}
}
}
@Override
public void close() {
synchronized (Pipe.this) {
inClosed = true;
Pipe.this.notifyAll();
}
}
};
private final OutputStream outputStream = new OutputStream() {
@Override
public void write(int b) throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[]{(byte) b};
write(bytes, 0, 1);
}
}
@Override
public void write(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
while (len != 0) {
if (head == tail && full) {
// The buffer is full, wait for InputStream read bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
} else {
int write = Math.min(len, (head <= tail ? capacity : head) - tail);
System.arraycopy(b, off, buffer, tail, write);
off += write;
len -= write;
tail += write;
if (tail == capacity) {
tail = 0;
}
if (head == tail) {
full = true;
}
Pipe.this.notifyAll();
}
}
}
}
@Override
public void close() {
synchronized (Pipe.this) {
outClosed = true;
Pipe.this.notifyAll();
}
}
};
Pipe(int capacity) {
this.capacity = capacity;
this.buffer = new byte[capacity];
}
private final InputStream inputStream = new FileInputStream(fd[0].getFileDescriptor());
InputStream getInputStream() {
return inputStream;
}
OutputStream getOutputStream() {
return outputStream;
int getOutputFd() {
return fd[1].getFd();
}
void closeOutputFd() {
try {
fd[1].close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.preference;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.Html;
import android.util.AttributeSet;
import androidx.appcompat.app.AlertDialog;
@@ -29,7 +30,6 @@ import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.ui.SettingsActivity;
import com.hippo.ehviewer.ui.scene.BaseScene;
import com.hippo.preference.MessagePreference;
import com.hippo.text.Html;
import com.hippo.util.ClipboardUtil;
import java.util.LinkedList;
@@ -92,7 +92,7 @@ public class IdentityCookiePreference extends MessagePreference {
message = EhCookieStore.KEY_IPD_MEMBER_ID + ": " + ipbMemberId + "<br>"
+ EhCookieStore.KEY_IPD_PASS_HASH + ": " + ipbPassHash + "<br>"
+ EhCookieStore.KEY_IGNEOUS + ": " + igneous;
setDialogMessage(Html.fromHtml(getContext().getString(R.string.settings_eh_identity_cookies_signed, message)));
setDialogMessage(Html.fromHtml(getContext().getString(R.string.settings_eh_identity_cookies_signed, message), Html.FROM_HTML_MODE_LEGACY));
message = message.replace("<br>", "\n");
} else {
setDialogMessage(getContext().getString(R.string.settings_eh_identity_cookies_tourist));
@@ -1,191 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.preference;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.Spinner;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhProxySelector;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.network.InetValidator;
import com.hippo.preference.DialogPreference;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.ViewUtils;
public class ProxyPreference extends DialogPreference implements View.OnClickListener {
private Spinner mType;
private TextInputLayout mIpInputLayout;
private EditText mIp;
private TextInputLayout mPortInputLayout;
private EditText mPort;
public ProxyPreference(Context context) {
super(context);
init();
}
public ProxyPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ProxyPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setDialogLayoutResource(R.layout.preference_dialog_proxy);
updateSummary(Settings.getProxyType(), Settings.getProxyIp(), Settings.getProxyPort());
}
private String getProxyTypeText(Context context, int type) {
String[] array = context.getResources().getStringArray(R.array.proxy_types);
return array[MathUtils.clamp(type, 0, array.length - 1)];
}
private void updateSummary(int type, String ip, int port) {
if ((type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS)
&& (TextUtils.isEmpty(ip) || !InetValidator.isValidInetPort(port))) {
type = EhProxySelector.TYPE_SYSTEM;
}
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
Context context = getContext();
setSummary(context.getString(R.string.settings_advanced_proxy_summary_1,
getProxyTypeText(context, type),
ip,
port));
} else {
Context context = getContext();
setSummary(context.getString(R.string.settings_advanced_proxy_summary_2,
getProxyTypeText(context, type)));
}
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
builder.setPositiveButton(android.R.string.ok, null);
}
@Override
@SuppressLint("SetTextI18n")
protected void onDialogCreated(AlertDialog dialog) {
super.onDialogCreated(dialog);
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(this);
mType = (Spinner) ViewUtils.$$(dialog, R.id.type);
mIpInputLayout = (TextInputLayout) ViewUtils.$$(dialog, R.id.ip_input_layout);
mIp = (EditText) ViewUtils.$$(dialog, R.id.ip);
mPortInputLayout = (TextInputLayout) ViewUtils.$$(dialog, R.id.port_input_layout);
mPort = (EditText) ViewUtils.$$(dialog, R.id.port);
int type = Settings.getProxyType();
String[] array = getContext().getResources().getStringArray(R.array.proxy_types);
mType.setSelection(MathUtils.clamp(type, 0, array.length));
mIp.setText(Settings.getProxyIp());
String portString;
int port = Settings.getProxyPort();
if (!InetValidator.isValidInetPort(port)) {
portString = null;
} else {
portString = Integer.toString(port);
}
mPort.setText(portString);
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
mType = null;
mIpInputLayout = null;
mIp = null;
mPortInputLayout = null;
mPort = null;
}
@Override
public void onClick(View v) {
Dialog dialog = getDialog();
Context context = getContext();
if (null == dialog || null == context || null == mType ||
null == mIpInputLayout || null == mIp ||
null == mPortInputLayout || null == mPort) {
return;
}
int type = mType.getSelectedItemPosition();
String ip = mIp.getText().toString().trim();
if (ip.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mIpInputLayout.setError(context.getString(R.string.text_is_empty));
return;
}
}
mIpInputLayout.setError(null);
int port;
String portString = mPort.getText().toString().trim();
if (portString.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mPortInputLayout.setError(context.getString(R.string.text_is_empty));
return;
} else {
port = -1;
}
} else {
try {
port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
port = -1;
}
if (!InetValidator.isValidInetPort(port)) {
mPortInputLayout.setError(context.getString(R.string.proxy_invalid_port));
return;
}
}
mPortInputLayout.setError(null);
Settings.putProxyType(type);
Settings.putProxyIp(ip);
Settings.putProxyPort(port);
updateSummary(type, ip, port);
EhApplication.getEhProxySelector(getContext()).updateProxy();
dialog.dismiss();
}
}
@@ -0,0 +1,167 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.preference
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
import android.widget.AutoCompleteTextView
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import com.google.android.material.textfield.TextInputLayout
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhProxySelector
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.network.InetValidator
import com.hippo.preference.DialogPreference
import com.hippo.yorozuya.MathUtils
import com.hippo.yorozuya.ViewUtils
class ProxyPreference @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : DialogPreference(context, attrs), View.OnClickListener {
private var mType: TextInputLayout? = null
private var mIpInputLayout: TextInputLayout? = null
private var mIp: EditText? = null
private var mPortInputLayout: TextInputLayout? = null
private var mPort: EditText? = null
private val mArray: Array<String>
init {
mArray = context.resources.getStringArray(R.array.proxy_types)
dialogLayoutResource = R.layout.preference_dialog_proxy
updateSummary(Settings.getProxyType(), Settings.getProxyIp(), Settings.getProxyPort())
}
private fun getProxyTypeText(type: Int): String {
return mArray[MathUtils.clamp(type, 0, mArray.size - 1)]
}
private fun updateSummary(type: Int, ip: String?, port: Int) {
var type1 = type
if ((type1 == EhProxySelector.TYPE_HTTP || type1 == EhProxySelector.TYPE_SOCKS)
&& (TextUtils.isEmpty(ip) || !InetValidator.isValidInetPort(port))
) {
type1 = EhProxySelector.TYPE_SYSTEM
}
summary = if (type1 == EhProxySelector.TYPE_HTTP || type1 == EhProxySelector.TYPE_SOCKS) {
val context = context
context.getString(
R.string.settings_advanced_proxy_summary_1,
getProxyTypeText(type1),
ip,
port
)
} else {
val context = context
context.getString(
R.string.settings_advanced_proxy_summary_2,
getProxyTypeText(type1)
)
}
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setPositiveButton(android.R.string.ok, null)
}
@SuppressLint("SetTextI18n")
override fun onDialogCreated(dialog: AlertDialog) {
super.onDialogCreated(dialog)
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(this)
mType = ViewUtils.`$$`(dialog, R.id.type) as TextInputLayout
val array = context.resources.getStringArray(R.array.proxy_types)
mIpInputLayout = ViewUtils.`$$`(dialog, R.id.ip_input_layout) as TextInputLayout
mIp = ViewUtils.`$$`(dialog, R.id.ip) as EditText
mPortInputLayout = ViewUtils.`$$`(dialog, R.id.port_input_layout) as TextInputLayout
mPort = ViewUtils.`$$`(dialog, R.id.port) as EditText
val type = Settings.getProxyType()
(mType!!.editText as AutoCompleteTextView).setText(
array[MathUtils.clamp(
type,
0,
array.size
)], false
)
mIp!!.setText(Settings.getProxyIp())
val portString: String?
val port = Settings.getProxyPort()
portString = if (!InetValidator.isValidInetPort(port)) {
null
} else {
port.toString()
}
mPort!!.setText(portString)
}
override fun onDialogClosed(positiveResult: Boolean) {
super.onDialogClosed(positiveResult)
mType = null
mIpInputLayout = null
mIp = null
mPortInputLayout = null
mPort = null
}
override fun onClick(v: View) {
val dialog = dialog
val context: Context = context
if (null == dialog || null == mType || null == mIpInputLayout || null == mIp || null == mPortInputLayout || null == mPort) {
return
}
val type = mArray.indexOf(mType!!.editText!!.text.toString())
val ip = mIp!!.text.toString().trim { it <= ' ' }
if (ip.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mIpInputLayout!!.error = context.getString(R.string.text_is_empty)
return
}
}
mIpInputLayout!!.error = null
val port: Int
val portString = mPort!!.text.toString().trim { it <= ' ' }
if (portString.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mPortInputLayout!!.error = context.getString(R.string.text_is_empty)
return
} else {
port = -1
}
} else {
port = try {
portString.toInt()
} catch (e: NumberFormatException) {
-1
}
if (!InetValidator.isValidInetPort(port)) {
mPortInputLayout!!.error = context.getString(R.string.proxy_invalid_port)
return
}
}
mPortInputLayout!!.error = null
Settings.putProxyType(type)
Settings.putProxyIp(ip)
Settings.putProxyPort(port)
updateSummary(type, ip, port)
EhApplication.getEhProxySelector(getContext()).updateProxy()
dialog.dismiss()
}
}
@@ -41,6 +41,28 @@ class VersionPreference @JvmOverloads constructor(
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setTitle(R.string.show_library_version)
builder.setMessage(Native.getlibarchiveVersion() + "\nlzma(xz-utils) " + Native.getliblzmaVersion() + "\nzlib " + Native.getzlibVersion() + "\nlibjpeg-turbo " + Native.getlibjpeg_turboVersion())
val info = NativeLibInfo().addLib(Native.getlibarchiveVersion())
.addLib("lzma(xz-utils)", Native.getliblzmaVersion())
.addLib("zlib", Native.getzlibVersion())
.addLib("libjpeg-turbo", Native.getlibjpeg_turboVersion())
.addLib(Native.getnettleVersion())
builder.setMessage(info.getMessage())
}
inner class NativeLibInfo {
private var message: String = ""
fun addLib(libname:String, libversion:String): NativeLibInfo {
message = "$message$libname $libversion\n"
return this
}
fun getMessage(): String {
return message;
}
fun addLib(msg: String): NativeLibInfo {
message = "$message$msg\n"
return this
}
}
}
@@ -45,6 +45,10 @@ import java.util.List;
public final class CommonOperations {
public static int getPagesForFounds(int founds, int per) {
return (int)Math.ceil((double) founds/per);
}
private static void doAddToFavorites(Activity activity, GalleryInfo galleryInfo,
int slot, EhClient.Callback<Void> listener) {
if (slot == -1) {
@@ -37,6 +37,8 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.KeyEvent;
@@ -67,6 +69,7 @@ import androidx.core.view.WindowInsetsControllerCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
import com.hippo.ehviewer.R;
@@ -103,6 +106,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import rikka.core.res.ConfigurationKt;
import rikka.core.res.ResourcesKt;
@@ -296,16 +300,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
break;
}
super.attachBaseContext(newBase);
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
}
@Override
@SuppressWarnings({"WrongConstant"})
@@ -323,10 +318,19 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
onRestore(savedInstanceState);
}
builder = new EditTextDialogBuilder(this, null, getString(R.string.archive_passwd));
builder.setTitle(getString(R.string.archive_need_passwd));
builder.setPositiveButton(getString(android.R.string.ok), null);
dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
if (mGalleryProvider == null) {
finish();
return;
}
ArchiveGalleryProvider.showPasswd = new ShowPasswdDialogHandler(this);
mGalleryProvider.start();
// Get start page
@@ -390,17 +394,22 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
if (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0.0f) {
float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 300;
if (scroll < 0.0f) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageLeft();
} else {
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT){
mGalleryView.pageRight();
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
}
} else {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageRight();
} else {
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
mGalleryView.pageLeft();
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
}
}
}
@@ -473,7 +482,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
FrameLayout mainLayout = (FrameLayout) ViewUtils.$$(this, R.id.main);
mainLayout.addView(new GalleryGuideView(this));
}
}
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
@Override
protected void onDestroy() {
@@ -1293,7 +1311,69 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
private EditTextDialogBuilder builder;
private boolean dialogShown = false;
private AlertDialog dialog;
private void showPasswdDialog() {
if (!dialogShown) {
dialogShown = true;
dialog.show();
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
GalleryActivity.this.onProvidePasswd();
});
}
dialog.setOnCancelListener(v -> finish());
}
}
private void onProvidePasswd() {
String passwd = builder.getText();
if (passwd.isEmpty())
builder.setError(getString(R.string.passwd_cannot_be_empty));
else {
ArchiveGalleryProvider.passwd = passwd;
ArchiveGalleryProvider.pv.v();
}
}
private void onPasswdWrong() {
builder.setError(getString(R.string.passwd_wrong));
}
private void onPasswdCorrect() {
dialog.dismiss();
}
private static class ShowPasswdDialogHandler extends Handler {
//弱引用持有HandlerActivity , GC 回收时会被回收掉
private final WeakReference<GalleryActivity> weakReference;
public ShowPasswdDialogHandler(GalleryActivity activity) {
this.weakReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
GalleryActivity activity = weakReference.get();
super.handleMessage(msg);
if (null != activity) {
switch (msg.what) {
case 0:
activity.showPasswdDialog();
break;
case 1:
activity.onPasswdWrong();
break;
case 2:
activity.onPasswdCorrect();
break;
}
}
}
}
}
@@ -47,6 +47,7 @@ import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhTagDatabase;
@@ -306,7 +307,6 @@ public final class MainActivity extends StageActivity
mStageLayout = (EhStageLayout) ViewUtils.$$(this, R.id.fragment_container);
mDrawerLayout = (DrawerLayout) ViewUtils.$$(this, R.id.draw_view);
mDrawerLayout.setStatusBarBackgroundColor(0);
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
@@ -360,9 +360,15 @@ public final class MainActivity extends StageActivity
.setTitle(R.string.app_link_not_verified_title)
.setMessage(R.string.app_link_not_verified_message)
.setPositiveButton(R.string.open_settings, (dialogInterface, i) -> {
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
try {
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
} catch (Throwable t) {
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
})
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.dont_show_again, (dialogInterface, i) -> Settings.putAppLinkVerifyTip(true))
@@ -420,6 +426,9 @@ public final class MainActivity extends StageActivity
@Override
protected void onResume() {
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this) && EhApplication.locked) {
startScene(new Announcer(SecurityScene.class), true);
}
super.onResume();
setNavCheckedItem(mNavCheckedItem);
checkClipboardUrl();
@@ -1,66 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.fragment.SettingsFragment;
import com.hippo.ehviewer.ui.scene.BaseScene;
public final class SettingsActivity extends EhActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preference);
setSupportActionBar(findViewById(R.id.toolbar));
ActionBar bar = getSupportActionBar();
if (bar != null) {
bar.setDisplayHomeAsUpEnabled(true);
}
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment, new SettingsFragment())
.commitAllowingStateLoss();
}
}
public void showTip(@StringRes int id, int length) {
showTip(getString(id), length);
}
public void showTip(CharSequence message, int length) {
Snackbar.make(findViewById(R.id.snackbar), message,
length == BaseScene.LENGTH_LONG ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT).show();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}
@@ -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.ui
import android.content.res.Configuration
import android.os.Bundle
import android.view.MenuItem
import androidx.annotation.StringRes
import androidx.core.view.WindowCompat
import androidx.fragment.app.FragmentTransaction
import com.google.android.material.snackbar.Snackbar
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.fragment.SettingsFragment
import com.hippo.ehviewer.ui.scene.BaseScene
class SettingsActivity : EhActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_preference)
setSupportActionBar(findViewById(R.id.toolbar))
val bar = supportActionBar
bar?.setDisplayHomeAsUpEnabled(true)
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
.replace(R.id.fragment, SettingsFragment())
.commitAllowingStateLoss()
}
}
fun showTip(@StringRes id: Int, length: Int) {
showTip(getString(id), length)
}
fun showTip(message: CharSequence?, length: Int) {
Snackbar.make(
findViewById(R.id.snackbar), message!!,
if (length == BaseScene.LENGTH_LONG) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT
).show()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
}
@@ -17,12 +17,12 @@
package com.hippo.ehviewer.ui.fragment;
import android.os.Bundle;
import android.text.Html;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.hippo.ehviewer.R;
import com.hippo.text.Html;
public class AboutFragment extends BasePreferenceFragment {
@@ -30,10 +30,10 @@ public class AboutFragment extends BasePreferenceFragment {
private static final String KEY_CHECK_FOR_UPDATES = "check_for_updates";
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.about_settings);
Preference author = findPreference(KEY_AUTHOR);
author.setSummary(Html.fromHtml(getString(R.string.settings_about_author_summary).replace('$', '@')));
author.setSummary(Html.fromHtml(getString(R.string.settings_about_author_summary).replace('$', '@'), Html.FROM_HTML_MODE_LEGACY));
}
@Override
@@ -17,12 +17,13 @@
package com.hippo.ehviewer.ui.fragment;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@@ -39,6 +40,11 @@ import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
import com.hippo.ehviewer.client.parser.FavoritesParser;
import com.hippo.ehviewer.ui.scene.BaseScene;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
@@ -53,7 +59,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -65,6 +71,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
private static final String KEY_IMPORT_DATA = "import_data";
private static final String KEY_EXPORT_DATA = "export_data";
private static final String KEY_OPEN_BY_DEFAULT = "open_by_default";
private static final String KEY_BACKUP_FAVORITE = "backup_favorite";
ActivityResultLauncher<String> exportLauncher = registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
@@ -223,7 +230,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
});
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.advanced_settings);
Preference dumpLogcat = findPreference(KEY_DUMP_LOGCAT);
@@ -232,6 +239,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
Preference importData = findPreference(KEY_IMPORT_DATA);
Preference exportData = findPreference(KEY_EXPORT_DATA);
Preference openByDefault = findPreference(KEY_OPEN_BY_DEFAULT);
Preference backupFavorite = findPreference(KEY_BACKUP_FAVORITE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
openByDefault.setVisible(false);
@@ -243,6 +251,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
clearMemoryCache.setOnPreferenceClickListener(this);
importData.setOnPreferenceClickListener(this);
exportData.setOnPreferenceClickListener(this);
backupFavorite.setOnPreferenceClickListener(this);
appLanguage.setOnPreferenceChangeListener(this);
}
@@ -279,14 +288,72 @@ public class AdvancedFragment extends BasePreferenceFragment {
}
return true;
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
Intent intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + requireContext().getPackageName()));
startActivity(intent);
try {
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + requireContext().getPackageName()));
startActivity(intent);
} catch (Throwable t) {
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:" + requireContext().getPackageName()));
startActivity(intent);
}
return true;
} else if (KEY_BACKUP_FAVORITE.equals(key)) {
try {
showTip(R.string.backup_favorite_start, BaseScene.LENGTH_SHORT);
backupFavorite();
} catch (Exception e) {
ExceptionUtils.throwIfFatal(e);
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
}
return true;
}
return false;
}
private void backupFavorite() {
AtomicInteger page = new AtomicInteger(0);
Context context = requireContext();
Activity activity = requireActivity();
EhClient mClient = EhApplication.getEhClient(context);
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
// favListUrlBuilder.setNext(page.get());
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
request.setCallback(new EhClient.Callback() {
@Override
public void onSuccess(Object result) {
try {
FavoritesParser.Result result1 = (FavoritesParser.Result) result;
EhDB.putLocalFavorites(result1.galleryInfoList);
Log.d("LocalFavorites", "now backup page " + page.get());
if (!result1.galleryInfoList.isEmpty()) {
// favListUrlBuilder.setNext(page.incrementAndGet());
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
mClient.execute(request);
} else {
showTip(R.string.backup_favorite_success, BaseScene.LENGTH_SHORT);
}
} catch (Exception e) {
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
}
}
@Override
public void onFailure(Exception e) {
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
}
@Override
public void onCancel() {
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
}
});
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
mClient.execute(request);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
@@ -22,10 +22,13 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.SettingsActivity;
import com.takisoft.preferencex.PreferenceFragmentCompat;
public class BasePreferenceFragment extends PreferenceFragmentCompat
@@ -52,10 +55,26 @@ public class BasePreferenceFragment extends PreferenceFragmentCompat
}
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
String key = preference.getKey();
try {
Fragment classObj = (Fragment) Class.forName(key).newInstance();
requireActivity().getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.fragment, classObj)
.addToBackStack(null)
.commitAllowingStateLoss();
} catch (ClassNotFoundException | IllegalAccessException | java.lang.InstantiationException ignored) {
}
return true;
}
private void setTitle(@StringRes int string) {
requireActivity().setTitle(string);
}
@@ -59,7 +59,7 @@ public class DownloadFragment extends BasePreferenceFragment {
});
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.download_settings);
Preference mediaScan = findPreference(Settings.KEY_MEDIA_SCAN);
@@ -31,13 +31,14 @@ import com.hippo.ehviewer.client.EhTagDatabase;
public class EhFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.eh_settings);
Preference theme = findPreference(Settings.KEY_THEME);
Preference blackDarkTheme = findPreference(Settings.KEY_BLACK_DARK_THEME);
Preference gallerySite = findPreference(Settings.KEY_GALLERY_SITE);
Preference listMode = findPreference(Settings.KEY_LIST_MODE);
Preference listThumbSize = findPreference(Settings.KEY_LIST_THUMB_SIZE);
Preference detailSize = findPreference(Settings.KEY_DETAIL_SIZE);
Preference thumbSize = findPreference(Settings.KEY_THUMB_SIZE);
Preference showTagTranslations = findPreference(Settings.KEY_SHOW_TAG_TRANSLATIONS);
@@ -46,6 +47,7 @@ public class EhFragment extends BasePreferenceFragment {
theme.setOnPreferenceChangeListener(this);
gallerySite.setOnPreferenceChangeListener(this);
listMode.setOnPreferenceChangeListener(this);
listThumbSize.setOnPreferenceChangeListener(this);
detailSize.setOnPreferenceChangeListener(this);
thumbSize.setOnPreferenceChangeListener(this);
showTagTranslations.setOnPreferenceChangeListener(this);
@@ -70,6 +72,10 @@ public class EhFragment extends BasePreferenceFragment {
} else if (Settings.KEY_LIST_MODE.equals(key)) {
requireActivity().setResult(Activity.RESULT_OK);
return true;
} else if (Settings.KEY_LIST_THUMB_SIZE.equals(key)) {
Settings.LIST_THUMB_SIZE_INITED = false;
requireActivity().setResult(Activity.RESULT_OK);
return true;
} else if (Settings.KEY_DETAIL_SIZE.equals(key)) {
requireActivity().setResult(Activity.RESULT_OK);
} else if (Settings.KEY_THUMB_SIZE.equals(key)) {
@@ -1,483 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.ui.fragment;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.dao.Filter;
import com.hippo.view.ViewTransition;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ViewUtils;
import java.util.List;
import rikka.core.res.ResourcesKt;
public class FilterFragment extends BaseFragment {
@Nullable
private ViewTransition mViewTransition;
@Nullable
private FilterAdapter mAdapter;
@Nullable
private FilterList mFilterList;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_filter, container, false);
mFilterList = new FilterList();
RecyclerView recyclerView = (EasyRecyclerView) ViewUtils.$$(view, R.id.recycler_view);
TextView tip = (TextView) ViewUtils.$$(view, R.id.tip);
mViewTransition = new ViewTransition(recyclerView, tip);
FloatingActionButton fab = view.findViewById(R.id.fab);
Drawable drawable = ContextCompat.getDrawable(requireActivity(), R.drawable.big_filter);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
tip.setCompoundDrawables(null, drawable, null, null);
mAdapter = new FilterAdapter();
mAdapter.setHasStableIds(true);
recyclerView.setAdapter(mAdapter);
recyclerView.setClipToPadding(false);
recyclerView.setClipChildren(false);
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(requireActivity().getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(requireActivity(), 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
recyclerView.setHasFixedSize(true);
DefaultItemAnimator defaultItemAnimator = (DefaultItemAnimator) recyclerView.getItemAnimator();
if (defaultItemAnimator != null) {
defaultItemAnimator.setSupportsChangeAnimations(false);
}
fab.setOnClickListener(v -> showAddFilterDialog());
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
updateView(false);
}
private void updateView(boolean animation) {
if (null == mViewTransition) {
return;
}
if (null == mFilterList || 0 == mFilterList.size()) {
mViewTransition.showView(1, animation);
} else {
mViewTransition.showView(0, animation);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
mViewTransition = null;
mAdapter = null;
mFilterList = null;
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.activity_filter, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_tip) {
showTipDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showTipDialog() {
new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.filter)
.setMessage(R.string.filter_tip)
.setPositiveButton(android.R.string.ok, null)
.show();
}
private void showAddFilterDialog() {
AlertDialog dialog = new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.add_filter)
.setView(R.layout.dialog_add_filter)
.setPositiveButton(R.string.add, null)
.setNegativeButton(android.R.string.cancel, null)
.show();
AddFilterDialogHelper helper = new AddFilterDialogHelper();
helper.setDialog(dialog);
}
private void showDeleteFilterDialog(final Filter filter) {
String message = getString(R.string.delete_filter, filter.text);
new MaterialAlertDialogBuilder(requireActivity())
.setMessage(message)
.setPositiveButton(R.string.delete, (dialog, which) -> {
if (DialogInterface.BUTTON_POSITIVE != which || null == mFilterList) {
return;
}
mFilterList.delete(filter);
if (null != mAdapter) {
mAdapter.notifyDataSetChanged();
}
updateView(true);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
@Override
public int getFragmentTitle() {
return R.string.filter;
}
private static class FilterHolder extends RecyclerView.ViewHolder {
private final MaterialCheckBox checkbox;
private final TextView text;
private final ImageView delete;
public FilterHolder(View itemView) {
super(itemView);
checkbox = itemView.findViewById(R.id.checkbox);
text = itemView.findViewById(R.id.text);
delete = itemView.findViewById(R.id.delete);
}
}
private class AddFilterDialogHelper implements View.OnClickListener {
@Nullable
private AlertDialog mDialog;
@Nullable
private Spinner mSpinner;
@Nullable
private TextInputLayout mInputLayout;
@Nullable
private EditText mEditText;
public void setDialog(AlertDialog dialog) {
mDialog = dialog;
mSpinner = (Spinner) ViewUtils.$$(dialog, R.id.spinner);
mInputLayout = (TextInputLayout) ViewUtils.$$(dialog, R.id.text_input_layout);
mEditText = mInputLayout.getEditText();
View button = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (null != button) {
button.setOnClickListener(this);
}
}
@Override
public void onClick(View v) {
if (null == mFilterList || null == mDialog || null == mSpinner ||
null == mInputLayout || null == mEditText) {
return;
}
String text = mEditText.getText().toString().trim();
if (TextUtils.isEmpty(text)) {
mInputLayout.setError(getString(R.string.text_is_empty));
return;
} else {
mInputLayout.setError(null);
}
int mode = mSpinner.getSelectedItemPosition();
Filter filter = new Filter();
filter.mode = mode;
filter.text = text;
if (!mFilterList.add(filter)) {
mInputLayout.setError(getString(R.string.label_text_exist));
return;
} else {
mInputLayout.setError(null);
}
if (null != mAdapter) {
mAdapter.notifyDataSetChanged();
}
updateView(true);
mDialog.dismiss();
mDialog = null;
mSpinner = null;
mInputLayout = null;
mEditText = null;
}
}
private class FilterAdapter extends RecyclerView.Adapter<FilterHolder> {
private static final int TYPE_ITEM = 0;
private static final int TYPE_HEADER = 1;
@Override
public int getItemViewType(int position) {
if (null == mFilterList) {
return TYPE_ITEM;
}
if (mFilterList.get(position).mode == FilterList.MODE_HEADER) {
return TYPE_HEADER;
} else {
return TYPE_ITEM;
}
}
@NonNull
@Override
public FilterHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
int layoutId;
switch (viewType) {
default:
case TYPE_ITEM:
layoutId = R.layout.item_filter;
break;
case TYPE_HEADER:
layoutId = R.layout.item_filter_header;
break;
}
return new FilterHolder(getLayoutInflater().inflate(layoutId, parent, false));
}
@Override
public void onBindViewHolder(@NonNull FilterHolder holder, int position) {
if (null == mFilterList) {
return;
}
Filter filter = mFilterList.get(position);
if (FilterList.MODE_HEADER == filter.mode) {
holder.text.setText(filter.text);
} else {
holder.checkbox.setText(filter.text);
holder.checkbox.setChecked(filter.enable);
holder.itemView.setOnClickListener(v -> {
mFilterList.trigger(filter);
//for updating delete line on filter text
if (mAdapter != null) {
mAdapter.notifyItemChanged(position);
}
});
holder.delete.setOnClickListener(v -> showDeleteFilterDialog(filter));
}
}
@Override
public int getItemCount() {
return null != mFilterList ? mFilterList.size() : 0;
}
@Override
public long getItemId(int position) {
if (mFilterList == null) {
return 0;
} else {
Filter filter = mFilterList.get(position);
if (filter.getId() != null) {
return (filter.text.hashCode() >> filter.mode) + filter.getId();
}
return filter.text.hashCode() >> filter.mode;
}
}
}
private class FilterList {
public static final int MODE_HEADER = -1;
private final EhFilter mEhFilter;
private final List<Filter> mTitleFilterList;
private final List<Filter> mUploaderFilterList;
private final List<Filter> mTagFilterList;
private final List<Filter> mTagNamespaceFilterList;
private Filter mTitleHeader;
private Filter mUploaderHeader;
private Filter mTagHeader;
private Filter mTagNamespaceHeader;
public FilterList() {
mEhFilter = EhFilter.getInstance();
mTitleFilterList = mEhFilter.getTitleFilterList();
mUploaderFilterList = mEhFilter.getUploaderFilterList();
mTagFilterList = mEhFilter.getTagFilterList();
mTagNamespaceFilterList = mEhFilter.getTagNamespaceFilterList();
}
public int size() {
int count = 0;
int size = mTitleFilterList.size();
count += 0 == size ? 0 : size + 1;
size = mUploaderFilterList.size();
count += 0 == size ? 0 : size + 1;
size = mTagFilterList.size();
count += 0 == size ? 0 : size + 1;
size = mTagNamespaceFilterList.size();
count += 0 == size ? 0 : size + 1;
return count;
}
private Filter getTitleHeader() {
if (null == mTitleHeader) {
mTitleHeader = new Filter();
mTitleHeader.mode = MODE_HEADER;
mTitleHeader.text = getString(R.string.filter_title);
}
return mTitleHeader;
}
private Filter getUploaderHeader() {
if (null == mUploaderHeader) {
mUploaderHeader = new Filter();
mUploaderHeader.mode = MODE_HEADER;
mUploaderHeader.text = getString(R.string.filter_uploader);
}
return mUploaderHeader;
}
private Filter getTagHeader() {
if (null == mTagHeader) {
mTagHeader = new Filter();
mTagHeader.mode = MODE_HEADER;
mTagHeader.text = getString(R.string.filter_tag);
}
return mTagHeader;
}
private Filter getTagNamespaceHeader() {
if (null == mTagNamespaceHeader) {
mTagNamespaceHeader = new Filter();
mTagNamespaceHeader.mode = MODE_HEADER;
mTagNamespaceHeader.text = getString(R.string.filter_tag_namespace);
}
return mTagNamespaceHeader;
}
public Filter get(int index) {
int size = mTitleFilterList.size();
if (0 != size) {
if (index == 0) {
return getTitleHeader();
} else if (index <= size) {
return mTitleFilterList.get(index - 1);
} else {
index -= size + 1;
}
}
size = mUploaderFilterList.size();
if (0 != size) {
if (index == 0) {
return getUploaderHeader();
} else if (index <= size) {
return mUploaderFilterList.get(index - 1);
} else {
index -= size + 1;
}
}
size = mTagFilterList.size();
if (0 != size) {
if (index == 0) {
return getTagHeader();
} else if (index <= size) {
return mTagFilterList.get(index - 1);
} else {
index -= size + 1;
}
}
size = mTagNamespaceFilterList.size();
if (0 != size) {
if (index == 0) {
return getTagNamespaceHeader();
} else if (index <= size) {
return mTagNamespaceFilterList.get(index - 1);
}
}
throw new IndexOutOfBoundsException();
}
public boolean add(Filter filter) {
return mEhFilter.addFilter(filter);
}
public void delete(Filter filter) {
mEhFilter.deleteFilter(filter);
}
public void trigger(Filter filter) {
mEhFilter.triggerFilter(filter);
}
}
}
@@ -0,0 +1,382 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.ui.fragment
import android.content.DialogInterface
import android.os.Bundle
import android.text.TextUtils
import android.view.*
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.MenuProvider
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout
import com.hippo.easyrecyclerview.EasyRecyclerView
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhFilter
import com.hippo.ehviewer.dao.Filter
import com.hippo.view.ViewTransition
import com.hippo.yorozuya.ViewUtils
class FilterFragment : BaseFragment() {
private var mViewTransition: ViewTransition? = null
private var mAdapter: FilterAdapter = FilterAdapter()
private var mFilterList: FilterList = FilterList()
private val mMenuProvider: MenuProvider = FilterMenuProvider()
inner class FilterMenuProvider : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.activity_filter, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
val itemId = menuItem.itemId
if (itemId == R.id.action_tip) {
showTipDialog()
return true
}
return false
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.activity_filter, container, false)
val recyclerView: RecyclerView =
ViewUtils.`$$`(view, R.id.recycler_view) as EasyRecyclerView
val tip = ViewUtils.`$$`(view, R.id.tip) as TextView
mViewTransition = ViewTransition(recyclerView, tip)
val fab = view.findViewById<FloatingActionButton>(R.id.fab)
val drawable = ContextCompat.getDrawable(requireActivity(), R.drawable.big_filter)
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
tip.setCompoundDrawables(null, drawable, null, null)
mAdapter.setHasStableIds(true)
recyclerView.adapter = mAdapter
recyclerView.clipToPadding = false
recyclerView.clipChildren = false
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.setHasFixedSize(true)
val defaultItemAnimator = recyclerView.itemAnimator as DefaultItemAnimator?
defaultItemAnimator?.supportsChangeAnimations = false
fab.setOnClickListener { showAddFilterDialog() }
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
updateView(false)
requireActivity().addMenuProvider(mMenuProvider)
}
private fun updateView(animation: Boolean) {
if (null == mViewTransition) {
return
}
if (0 == mFilterList.size()) {
mViewTransition!!.showView(1, animation)
} else {
mViewTransition!!.showView(0, animation)
}
}
override fun onDestroyView() {
super.onDestroyView()
mViewTransition = null
requireActivity().removeMenuProvider(mMenuProvider)
}
private fun showTipDialog() {
MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.filter)
.setMessage(R.string.filter_tip)
.setPositiveButton(android.R.string.ok, null)
.show()
}
private fun showAddFilterDialog() {
val dialog = MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.add_filter)
.setView(R.layout.dialog_add_filter)
.setPositiveButton(R.string.add, null)
.setNegativeButton(android.R.string.cancel, null)
.show()
AddFilterDialogHelper(dialog)
}
private fun showDeleteFilterDialog(filter: Filter) {
val message = getString(R.string.delete_filter, filter.text)
MaterialAlertDialogBuilder(requireActivity())
.setMessage(message)
.setPositiveButton(R.string.delete) { _: DialogInterface?, which: Int ->
if (DialogInterface.BUTTON_POSITIVE != which) {
return@setPositiveButton
}
mFilterList.delete(filter)
mAdapter.notifyDataSetChanged()
updateView(true)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun getFragmentTitle(): Int {
return R.string.filter
}
private class FilterHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val checkbox: MaterialCheckBox?
val text: TextView?
val delete: ImageView?
init {
checkbox = itemView.findViewById(R.id.checkbox)
text = itemView.findViewById(R.id.text)
delete = itemView.findViewById(R.id.delete)
}
}
private inner class AddFilterDialogHelper(dialog: AlertDialog) : View.OnClickListener {
private var mDialog: AlertDialog
private var mSpinner: TextInputLayout
private var mInputLayout: TextInputLayout
private var mEditText: EditText
private val mArray: Array<String> =
context!!.resources.getStringArray(R.array.filter_entries)
init {
mDialog = dialog
mSpinner = ViewUtils.`$$`(dialog, R.id.spinner) as TextInputLayout
mInputLayout = ViewUtils.`$$`(dialog, R.id.text_input_layout) as TextInputLayout
mEditText = mInputLayout.editText!!
val button: View? = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
button?.setOnClickListener(this)
}
override fun onClick(v: View) {
val text1 = mSpinner.editText!!.text.toString()
if (TextUtils.isEmpty(text1)) {
mSpinner.error = getString(R.string.text_is_empty)
return
} else {
mSpinner.error = null
}
val text = mEditText.text.toString().trim { it <= ' ' }
if (TextUtils.isEmpty(text)) {
mInputLayout.error = getString(R.string.text_is_empty)
return
} else {
mInputLayout.error = null
}
val mode = mArray.indexOf(text1)
val filter = Filter()
filter.mode = mode
filter.text = text
if (!mFilterList.add(filter)) {
mInputLayout.error = getString(R.string.label_text_exist)
return
} else {
mInputLayout.error = null
}
mAdapter.notifyDataSetChanged()
updateView(true)
mDialog.dismiss()
}
}
private inner class FilterAdapter : RecyclerView.Adapter<FilterHolder>() {
override fun getItemViewType(position: Int): Int {
return if (mFilterList[position].mode == MODE_HEADER) {
TYPE_HEADER
} else {
TYPE_ITEM
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterHolder {
val layoutId: Int = when (viewType) {
TYPE_ITEM -> R.layout.item_filter
TYPE_HEADER -> R.layout.item_filter_header
else -> R.layout.item_filter
}
return FilterHolder(layoutInflater.inflate(layoutId, parent, false))
}
override fun onBindViewHolder(holder: FilterHolder, position: Int) {
val filter = mFilterList[position]
if (MODE_HEADER == filter.mode) {
holder.text?.text = filter.text
} else {
holder.checkbox?.text = filter.text
holder.checkbox?.isChecked = filter.enable
holder.itemView.setOnClickListener {
mFilterList.trigger(filter)
//for updating delete line on filter text
mAdapter.notifyItemChanged(position)
}
holder.delete?.setOnClickListener { showDeleteFilterDialog(filter) }
}
}
override fun getItemCount(): Int {
return mFilterList.size()
}
override fun getItemId(position: Int): Long {
return run {
val filter = mFilterList[position]
if (filter.id != null) {
(filter.text.hashCode() shr filter.mode) + filter.id
} else (filter.text.hashCode() shr filter.mode).toLong()
}
}
}
private inner class FilterList {
private val mEhFilter: EhFilter = EhFilter.getInstance()
private val mTitleFilterList: List<Filter> = mEhFilter.titleFilterList
private val mUploaderFilterList: List<Filter> = mEhFilter.uploaderFilterList
private val mTagFilterList: List<Filter> = mEhFilter.tagFilterList
private val mTagNamespaceFilterList: List<Filter> = mEhFilter.tagNamespaceFilterList
private var mTitleHeader: Filter? = null
private var mUploaderHeader: Filter? = null
private var mTagHeader: Filter? = null
private var mTagNamespaceHeader: Filter? = null
fun size(): Int {
var count = 0
var size = mTitleFilterList.size
count += if (0 == size) 0 else size + 1
size = mUploaderFilterList.size
count += if (0 == size) 0 else size + 1
size = mTagFilterList.size
count += if (0 == size) 0 else size + 1
size = mTagNamespaceFilterList.size
count += if (0 == size) 0 else size + 1
return count
}
private val titleHeader: Filter
get() {
if (null == mTitleHeader) {
mTitleHeader = Filter()
mTitleHeader!!.mode = MODE_HEADER
mTitleHeader!!.text = getString(R.string.filter_title)
}
return mTitleHeader!!
}
private val uploaderHeader: Filter
get() {
if (null == mUploaderHeader) {
mUploaderHeader = Filter()
mUploaderHeader!!.mode = MODE_HEADER
mUploaderHeader!!.text = getString(R.string.filter_uploader)
}
return mUploaderHeader!!
}
private val tagHeader: Filter
get() {
if (null == mTagHeader) {
mTagHeader = Filter()
mTagHeader!!.mode = MODE_HEADER
mTagHeader!!.text = getString(R.string.filter_tag)
}
return mTagHeader!!
}
private val tagNamespaceHeader: Filter
get() {
if (null == mTagNamespaceHeader) {
mTagNamespaceHeader = Filter()
mTagNamespaceHeader!!.mode = MODE_HEADER
mTagNamespaceHeader!!.text = getString(R.string.filter_tag_namespace)
}
return mTagNamespaceHeader!!
}
operator fun get(index: Int): Filter {
var index1 = index
var size = mTitleFilterList.size
if (0 != size) {
index1 -= if (index1 == 0) {
return titleHeader
} else if (index1 <= size) {
return mTitleFilterList[index1 - 1]
} else {
size + 1
}
}
size = mUploaderFilterList.size
if (0 != size) {
index1 -= if (index1 == 0) {
return uploaderHeader
} else if (index1 <= size) {
return mUploaderFilterList[index1 - 1]
} else {
size + 1
}
}
size = mTagFilterList.size
if (0 != size) {
index1 -= if (index1 == 0) {
return tagHeader
} else if (index1 <= size) {
return mTagFilterList[index1 - 1]
} else {
size + 1
}
}
size = mTagNamespaceFilterList.size
if (0 != size) {
if (index1 == 0) {
return tagNamespaceHeader
} else if (index1 <= size) {
return mTagNamespaceFilterList[index1 - 1]
}
}
throw IndexOutOfBoundsException()
}
fun add(filter: Filter?): Boolean {
return mEhFilter.addFilter(filter)
}
fun delete(filter: Filter?) {
mEhFilter.deleteFilter(filter)
}
fun trigger(filter: Filter?) {
mEhFilter.triggerFilter(filter)
}
}
companion object {
private const val MODE_HEADER = -1
private const val TYPE_ITEM = 0
private const val TYPE_HEADER = 1
}
}
@@ -37,19 +37,15 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.Hosts;
import com.hippo.ehviewer.R;
import com.hippo.view.ViewTransition;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ViewUtils;
import java.util.List;
import java.util.Locale;
import rikka.core.res.ResourcesKt;
public class HostsFragment extends BaseFragment
implements View.OnClickListener {
@@ -85,12 +81,6 @@ public class HostsFragment extends BaseFragment
adapter = new HostsAdapter();
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(requireActivity().getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(requireActivity(), 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
recyclerView.setHasFixedSize(true);
fab.setOnClickListener(this);
@@ -28,7 +28,7 @@ import com.hippo.ehviewer.ui.scene.SecurityScene.Companion.isAuthenticationSuppo
* Created by Mo10 on 2018/2/10.
*/
class PrivacyFragment : BasePreferenceFragment() {
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.privacy_settings)
}
@@ -25,7 +25,7 @@ import com.hippo.ehviewer.R;
public class ReadFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.read_settings);
}
@@ -26,7 +26,7 @@ import com.hippo.ehviewer.R;
public class SettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_headers);
}
@@ -56,7 +56,6 @@ public abstract class BaseScene extends SceneFragment {
private View drawerView;
@Nullable
private SparseArray<Parcelable> drawerViewState;
private boolean needWhiteStatusBar = needWhiteStatusBar();
public void addAboveSnackView(View view) {
FragmentActivity activity = getActivity();
@@ -118,10 +117,6 @@ public abstract class BaseScene extends SceneFragment {
return true;
}
public boolean needWhiteStatusBar() {
return true;
}
public int getNavCheckedItem() {
return 0;
}
@@ -176,14 +171,6 @@ public abstract class BaseScene extends SceneFragment {
drawerView = null;
}
public void setLightStatusBar(boolean set) {
var insetsController = getInsetsController();
if (insetsController != null) {
insetsController.setAppearanceLightStatusBars(set && (requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
}
needWhiteStatusBar = set;
}
public void onDestroyDrawerView() {
}
@@ -213,7 +200,11 @@ public abstract class BaseScene extends SceneFragment {
// Hide soft ime
hideSoftInput();
setLightStatusBar(needWhiteStatusBar);
insetsController = getInsetsController();
if (insetsController != null) {
insetsController.setAppearanceLightStatusBars((requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
}
}
@Nullable
@@ -22,21 +22,17 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
@@ -45,34 +41,23 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
import com.hippo.app.CheckBoxDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.conaco.DataContainer;
import com.hippo.conaco.ProgressNotifier;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.HandlerDrawable;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
@@ -86,7 +71,6 @@ import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.download.DownloadService;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.io.UniFileInputStreamPipe;
import com.hippo.scene.Announcer;
@@ -100,7 +84,6 @@ import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ObjectUtils;
import com.hippo.yorozuya.ViewUtils;
import com.hippo.yorozuya.collect.LongList;
@@ -391,90 +374,9 @@ public class DownloadsScene extends ToolbarScene
updateView();
guide();
return view;
}
private void guide() {
if (Settings.getGuideDownloadThumb() && null != mRecyclerView) {
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Settings.getGuideDownloadThumb()) {
guideDownloadThumb();
}
if (null != mRecyclerView) {
ViewUtils.removeOnGlobalLayoutListener(mRecyclerView.getViewTreeObserver(), this);
}
}
});
} else {
guideDownloadLabels();
}
}
private void guideDownloadThumb() {
MainActivity activity = getMainActivity();
if (null == activity || !Settings.getGuideDownloadThumb() || null == mLayoutManager || null == mRecyclerView) {
guideDownloadLabels();
return;
}
int position = mLayoutManager.findFirstCompletelyVisibleItemPositions(null)[0];
if (position < 0) {
guideDownloadLabels();
return;
}
RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(position);
if (null == holder) {
guideDownloadLabels();
return;
}
TapTargetView.showFor(requireActivity(),
TapTarget.forView(((DownloadHolder) holder).thumb,
getString(R.string.guide_download_thumb_title),
getString(R.string.guide_download_thumb_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetDismissed(view, userInitiated);
Settings.putGuideDownloadThumb(false);
guideDownloadLabels();
}
});
}
private void guideDownloadLabels() {
MainActivity activity = getMainActivity();
if (null == activity || !Settings.getGuideDownloadLabels()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_download_labels_title),
getString(R.string.guide_download_labels_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.puttGuideDownloadLabels(false);
openDrawer(Gravity.RIGHT);
}
});
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -640,26 +542,13 @@ public class DownloadsScene extends ToolbarScene
initLabels();
RecyclerViewDragDropManager dragDropManager = new RecyclerViewDragDropManager();
dragDropManager.setInitiateOnLongPress(true);
dragDropManager.setInitiateOnTouch(false);
dragDropManager.setDraggingItemAlpha(0.8f);
mLabelAdapter = new DownloadLabelAdapter(inflater);
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
mLabelAdapter.setHasStableIds(true);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(mLabelAdapter));
dragDropManager.attachRecyclerView(recyclerView);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new DownloadLabelItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setAdapter(mLabelAdapter);
return view;
}
@@ -982,7 +871,7 @@ public class DownloadsScene extends ToolbarScene
holder.speed.setText(FileUtils.humanReadableByteCount(speed, false) + "/S");
}
private static class DownloadLabelHolder extends AbstractDraggableItemViewHolder {
private static class DownloadLabelHolder extends RecyclerView.ViewHolder {
private final TextView label;
private final ImageView option;
@@ -994,74 +883,7 @@ public class DownloadsScene extends ToolbarScene
}
}
private static class ThumbDataContainer implements DataContainer {
private final DownloadInfo mInfo;
@Nullable
private UniFile mFile;
public ThumbDataContainer(@NonNull DownloadInfo info) {
mInfo = info;
}
private void ensureFile() {
if (mFile == null) {
UniFile dir = SpiderDen.getGalleryDownloadDir(mInfo);
if (dir != null && dir.isDirectory()) {
mFile = dir.createFile(".thumb");
}
}
}
@Override
public boolean isEnabled() {
ensureFile();
return mFile != null;
}
@Override
public void onUrlMoved(String requestUrl, String responseUrl) {
}
@Override
public boolean save(InputStream is, long length, String mediaType, ProgressNotifier notify) {
ensureFile();
if (mFile == null) {
return false;
}
OutputStream os = null;
try {
os = mFile.openOutputStream();
IOUtils.copy(is, os);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
IOUtils.closeQuietly(os);
}
}
@Override
public InputStreamPipe get() {
ensureFile();
if (mFile != null) {
return new UniFileInputStreamPipe(mFile);
} else {
return null;
}
}
@Override
public void remove() {
if (mFile != null) {
mFile.delete();
}
}
}
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> implements DraggableItemAdapter<DownloadLabelHolder> {
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> {
private final LayoutInflater mInflater;
@@ -1115,36 +937,15 @@ public class DownloadsScene extends ToolbarScene
});
if (position > 0) {
holder.option.setVisibility(View.VISIBLE);
holder.itemView.setOnLongClickListener(v -> {
holder.option.setOnClickListener(v -> {
if (context != null) {
PopupMenu popupMenu = new PopupMenu(context, holder.option);
popupMenu.inflate(R.menu.download_label_option);
popupMenu.show();
popupMenu.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.menu_label_rename) {
EditTextDialogBuilder builder = new EditTextDialogBuilder(
context, label, getString(R.string.download_labels));
builder.setTitle(R.string.rename_label_title);
builder.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.show();
new RenameLabelDialogHelper(builder, dialog, label);
} else if (itemId == R.id.menu_label_remove) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_label_title))
.setMessage(getString(R.string.delete_label_message, label))
.setPositiveButton(R.string.delete, (dialog, which) -> {
mDownloadManager.deleteLabel(label);
mLabels.remove(position);
notifyDataSetChanged();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
return false;
});
EditTextDialogBuilder builder = new EditTextDialogBuilder(
context, label, getString(R.string.download_labels));
builder.setTitle(R.string.rename_label_title);
builder.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.show();
new RenameLabelDialogHelper(builder, dialog, label);
}
return true;
});
} else {
holder.option.setVisibility(View.GONE);
@@ -1161,43 +962,6 @@ public class DownloadsScene extends ToolbarScene
public int getItemCount() {
return mLabels != null ? mLabels.size() : 0;
}
@Override
public boolean onCheckCanStartDrag(@NonNull DownloadLabelHolder holder, int position, int x, int y) {
return position != 0 && x > holder.option.getX() && y > holder.option.getY();
}
@Override
public ItemDraggableRange onGetItemDraggableRange(@NonNull DownloadLabelHolder holder, int position) {
return new ItemDraggableRange(1, getItemCount() - 1);
}
@Override
public void onMoveItem(int fromPosition, int toPosition) {
Context context = getContext();
if (null == context || fromPosition == toPosition || toPosition == 0) {
return;
}
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
final String item = mLabels.remove(fromPosition);
mLabels.add(toPosition, item);
}
@Override
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
return dropPosition != 0;
}
@Override
public void onItemDragStarted(int position) {
notifyDataSetChanged();
}
@Override
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
notifyDataSetChanged();
}
}
private class DeleteRangeDialogHelper implements DialogInterface.OnClickListener {
@@ -1397,8 +1161,7 @@ public class DownloadsScene extends ToolbarScene
return;
}
DownloadInfo info = mList.get(position);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb,
new ThumbDataContainer(info), true);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb, true);
holder.title.setText(EhUtils.getSuitableTitle(info));
holder.uploader.setText(info.uploader);
holder.rating.setRating(info.rating);
@@ -1535,4 +1298,43 @@ public class DownloadsScene extends ToolbarScene
}
}
}
private class DownloadLabelItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
}
@SuppressLint("NotifyDataSetChanged")
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getBindingAdapterPosition();
int toPosition = target.getBindingAdapterPosition();
Context context = getContext();
if (null == context || fromPosition == toPosition || toPosition == 0 || fromPosition == 0) {
return false;
}
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
final String item = mLabels.remove(fromPosition);
mLabels.add(toPosition, item);
mLabelAdapter.notifyDataSetChanged();
return true;
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getBindingAdapterPosition();
if (mDownloadManager == null)
return;
if (position != 0) {
String label = mLabels.get(position);
mDownloadManager.deleteLabel(label);
}
mLabels.remove(position);
mLabelAdapter.notifyDataSetChanged();
}
}
}
@@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.InputType;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
@@ -45,8 +46,6 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.annotation.Implemented;
@@ -55,7 +54,6 @@ import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.R;
@@ -312,40 +310,9 @@ public class FavoritesScene extends BaseScene implements
mHelper.firstRefresh();
}
guideCollections();
return view;
}
private void guideCollections() {
Activity activity = getMainActivity();
if (null == activity || !Settings.getGuideCollections()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_collections_title),
getString(R.string.guide_collections_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.putGuideCollections(false);
openDrawer(Gravity.RIGHT);
}
});
}
// keyword of mUrlBuilder, fav cat of mUrlBuilder, mFavCatArray.
// They changed, call it
private void updateSearchBar() {
@@ -446,12 +413,6 @@ public class FavoritesScene extends BaseScene implements
EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
mDrawerAdapter = new FavDrawerAdapter(inflater);
recyclerView.setAdapter(mDrawerAdapter);
@@ -894,7 +855,14 @@ public class FavoritesScene extends BaseScene implements
}
updateSearchBar();
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
int pages = 0;
assert mUrlBuilder != null;
if (FavListUrlBuilder.isValidFavCat(mUrlBuilder.getFavCat()))
pages = CommonOperations.getPagesForFounds(mFavCountArray[mUrlBuilder.getFavCat()], 50);
else if (mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_ALL)
pages = CommonOperations.getPagesForFounds(mFavCountSum, 50);
mHelper.nextPg = result.nextPage;
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
if (mDrawerAdapter != null) {
mDrawerAdapter.notifyDataSetChanged();
@@ -1213,6 +1181,8 @@ public class FavoritesScene extends BaseScene implements
}
private class FavoritesHelper extends GalleryInfoContentHelper {
public int pgCounter = 0;
public String nextPg = null;
@Override
protected void getPageData(final int taskId, int type, int page) {
@@ -1220,6 +1190,7 @@ public class FavoritesScene extends BaseScene implements
if (null == activity || null == mUrlBuilder || null == mClient) {
return;
}
pgCounter = page;
if (mEnableModify) {
mEnableModify = false;
@@ -1259,7 +1230,7 @@ public class FavoritesScene extends BaseScene implements
url = mUrlBuilder.build();
}
mUrlBuilder.setIndex(page);
mUrlBuilder.setNext(nextPg);
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_MODIFY_FAVORITES);
request.setCallback(new GetFavoritesListener(getContext(),
@@ -1272,7 +1243,7 @@ public class FavoritesScene extends BaseScene implements
final String keyword = mUrlBuilder.getKeyword();
SimpleHandler.getInstance().post(() -> onGetFavoritesLocal(keyword, taskId));
} else {
mUrlBuilder.setIndex(page);
mUrlBuilder.setNext(nextPg);
String url = mUrlBuilder.build();
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
@@ -1335,5 +1306,25 @@ public class FavoritesScene extends BaseScene implements
}
}
}
@Override
protected void onClearData() {
super.onClearData();
nextPg = null;
}
@Override
protected Parcelable saveInstanceState(Parcelable superState) {
Bundle bundle = (Bundle) super.saveInstanceState(superState);
bundle.putString(KEY_NEXT_PAGE, nextPg);
return bundle;
}
@Override
protected Parcelable restoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
nextPg = bundle.getString(KEY_NEXT_PAGE);
return super.restoreInstanceState(state);
}
}
}
@@ -24,9 +24,11 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Looper;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
@@ -55,7 +57,6 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.UrlOpener;
@@ -70,7 +71,6 @@ import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.VoteCommentParser;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.text.Html;
import com.hippo.text.URLImageGetter;
import com.hippo.util.ClipboardUtil;
import com.hippo.util.ExceptionUtils;
@@ -82,8 +82,6 @@ import com.hippo.widget.LinkifyTextView;
import com.hippo.widget.ObservedTextView;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ResourcesUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.StringUtils;
import com.hippo.yorozuya.ViewUtils;
@@ -186,14 +184,7 @@ public final class GalleryCommentsScene extends ToolbarScene
mEditPanel,
mFabLayout
));
mRefreshLayout.setColorSchemeResources(
R.color.loading_indicator_red,
R.color.loading_indicator_purple,
R.color.loading_indicator_blue,
R.color.loading_indicator_cyan,
R.color.loading_indicator_green,
R.color.loading_indicator_yellow);
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(getTheme(), androidx.appcompat.R.attr.colorPrimary));
mRefreshLayout.setOnRefreshListener(this);
Context context = requireContext();
@@ -209,11 +200,6 @@ public final class GalleryCommentsScene extends ToolbarScene
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(context,
RecyclerView.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL, ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(true);
mRecyclerView.addItemDecoration(decoration);
mRecyclerView.setHasFixedSize(true);
// Cancel change animator
RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
@@ -327,11 +313,6 @@ public final class GalleryCommentsScene extends ToolbarScene
}
});
rv.setLayoutManager(new LinearLayoutManager(context));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL, ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setPadding(ResourcesUtils.getAttrDimensionPixelOffset(context, androidx.appcompat.R.attr.dialogPreferredPadding));
rv.addItemDecoration(decoration);
rv.setClipToPadding(false);
builder.setView(rv).show();
}
@@ -869,9 +850,11 @@ public final class GalleryCommentsScene extends ToolbarScene
}
private CharSequence generateComment(Context context, ObservedTextView textView, GalleryComment comment) {
SpannableStringBuilder ssb = Html.fromHtml(comment.comment, new URLImageGetter(textView,
Spanned sp = Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY, new URLImageGetter(textView,
EhApplication.getConaco(context)), null);
SpannableStringBuilder ssb = new SpannableStringBuilder(sp);
if (0 != comment.id && 0 != comment.score) {
int score = comment.score;
String scoreString = score > 0 ? "+" + score : Integer.toString(score);
@@ -34,6 +34,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.text.Html;
import android.text.TextUtils;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -100,7 +101,6 @@ import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
import com.hippo.ehviewer.widget.GalleryRatingBar;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.text.Html;
import com.hippo.text.URLImageGetter;
import com.hippo.util.AppHelper;
import com.hippo.util.ClipboardUtil;
@@ -559,12 +559,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
ViewGroup main = (ViewGroup) ViewUtils.$$(view, R.id.main);
ScrollView mainView = (ScrollView) ViewUtils.$$(main, R.id.scroll_view);
mainView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
if (mActionGroup != null && mHeader != null) {
//noinspection IntegerDivisionInFloatingPointContext
setLightStatusBar(mActionGroup.getY() - mHeader.findViewById(R.id.header_content).getPaddingTop() / 2 < scrollY);
}
});
View progressView = ViewUtils.$$(main, R.id.progress_view);
mTip = (TextView) ViewUtils.$$(main, R.id.tip);
mViewTransition = new ViewTransition(mainView, progressView, mTip);
@@ -668,11 +662,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
return view;
}
@Override
public boolean needWhiteStatusBar() {
return false;
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -841,19 +830,16 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
switch (state) {
case STATE_NORMAL:
setLightStatusBar(false);
// Show mMainView
mViewTransition.showView(0, animation);
// Show mBelowHeader
mViewTransition2.showView(0, animation);
break;
case STATE_REFRESH:
setLightStatusBar(true);
// Show mProgressView
mViewTransition.showView(1, animation);
break;
case STATE_REFRESH_HEADER:
setLightStatusBar(false);
// Show mMainView
mViewTransition.showView(0, animation);
// Show mProgress
@@ -862,7 +848,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
default:
case STATE_INIT:
case STATE_FAILED:
setLightStatusBar(true);
// Show mFailedView
mViewTransition.showView(2, animation);
break;
@@ -1063,7 +1048,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
time.setText(ReadableTime.getTimeAgo(comment.time));
ObservedTextView c = v.findViewById(R.id.comment);
c.setMaxLines(5);
c.setText(Html.fromHtml(comment.comment,
c.setText(Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY,
new URLImageGetter(c, EhApplication.getConaco(context)), null));
v.setBackgroundColor(Color.TRANSPARENT);
}
@@ -1082,7 +1067,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
if (gd.previewPages <= 0 || previewSet == null || previewSet.size() == 0) {
mPreviewText.setText(R.string.no_previews);
return;
} else if (gd.previewPages == 1 && previewSet.size() <= 60) {
} else if (gd.previewPages == 1) {
mPreviewText.setText(R.string.no_more_previews);
} else {
mPreviewText.setText(R.string.more_previews);
@@ -1091,8 +1076,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
int columnWidth = resources.getDimensionPixelOffset(Settings.getThumbSizeResId());
mGridLayout.setColumnSize(columnWidth);
mGridLayout.setStrategy(SimpleGridAutoSpanLayout.STRATEGY_SUITABLE_SIZE);
int size = Math.min(60, previewSet.size());
for (int i = 0; i < size; i++) {
for (int i = 0, size = previewSet.size(); i < size; i++) {
View view = inflater.inflate(R.layout.item_gallery_preview, mGridLayout, false);
mGridLayout.addView(view);
@@ -29,7 +29,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.UrlOpener;
@@ -38,13 +37,10 @@ import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.util.ClipboardUtil;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ViewUtils;
import java.util.ArrayList;
import rikka.core.res.ResourcesKt;
public final class GalleryInfoScene extends ToolbarScene {
public static final String KEY_GALLERY_DETAIL = "gallery_detail";
@@ -164,13 +160,6 @@ public final class GalleryInfoScene extends ToolbarScene {
InfoAdapter adapter = new InfoAdapter();
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
int keylineMargin = context.getResources().getDimensionPixelOffset(R.dimen.keyline_margin);
decoration.setPadding(keylineMargin);
mRecyclerView.addItemDecoration(decoration);
mRecyclerView.setClipToPadding(false);
mRecyclerView.setHasFixedSize(true);
return view;
@@ -18,13 +18,10 @@ package com.hippo.ehviewer.ui.scene;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -33,10 +30,8 @@ import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
@@ -50,31 +45,22 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.FavouriteStatusRouter;
@@ -112,7 +98,6 @@ import com.hippo.widget.FabLayout;
import com.hippo.widget.SearchBarMover;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.StringUtils;
@@ -635,40 +620,9 @@ public final class GalleryListScene extends BaseScene
mHelper.firstRefresh();
}
guideQuickSearch();
return view;
}
private void guideQuickSearch() {
Activity activity = getMainActivity();
if (null == activity || !Settings.getGuideQuickSearch()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_quick_search_title),
getString(R.string.guide_quick_search_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.putGuideQuickSearch(false);
openDrawer(Gravity.RIGHT);
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -787,25 +741,13 @@ public final class GalleryListScene extends BaseScene
Context context = getContext();
AssertUtils.assertNotNull(context);
RecyclerViewDragDropManager dragDropManager = new RecyclerViewDragDropManager();
dragDropManager.setInitiateOnLongPress(true);
dragDropManager.setInitiateOnTouch(false);
dragDropManager.setDraggingItemAlpha(0.8f);
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
final QsDrawerAdapter qsDrawerAdapter = new QsDrawerAdapter(inflater);
qsDrawerAdapter.setHasStableIds(true);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(qsDrawerAdapter));
dragDropManager.attachRecyclerView(recyclerView);
recyclerView.setAdapter(qsDrawerAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new GalleryListQSItemTouchHelperCallback(qsDrawerAdapter));
itemTouchHelper.attachToRecyclerView(recyclerView);
mQuickSearchList = EhDB.getAllQuickSearch();
tip.setText(R.string.quick_search_tip);
if (mIsTopList) {
@@ -1429,7 +1371,7 @@ public final class GalleryListScene extends BaseScene
? R.string.gallery_list_empty_hit_subscription
: R.string.gallery_list_empty_hit);
mHelper.setEmptyString(emptyString);
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
mHelper.onGetPageData(taskId, CommonOperations.getPagesForFounds(result.founds, 25), mHelper.pgCounter + 1, result.galleryInfoList);
}
}
@@ -1532,7 +1474,7 @@ public final class GalleryListScene extends BaseScene
}
}
private static class QsDrawerHolder extends AbstractDraggableItemViewHolder {
private static class QsDrawerHolder extends RecyclerView.ViewHolder {
private final TextView key;
private final ImageView option;
@@ -1577,7 +1519,7 @@ public final class GalleryListScene extends BaseScene
}
}
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> implements DraggableItemAdapter<QsDrawerHolder> {
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> {
private final LayoutInflater mInflater;
@@ -1602,40 +1544,12 @@ public final class GalleryListScene extends BaseScene
}
mUrlBuilder.set(mQuickSearchList.get(position));
mUrlBuilder.setPageIndex(0);
mUrlBuilder.setNextGid(0);
onUpdateUrlBuilder();
mHelper.refresh();
setState(STATE_NORMAL);
closeDrawer(Gravity.RIGHT);
});
holder.itemView.setOnLongClickListener(v -> {
PopupMenu popupMenu = new PopupMenu(requireContext(), holder.option);
popupMenu.inflate(R.menu.quicksearch_option);
popupMenu.show();
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
final QuickSearch quickSearch = mQuickSearchList.get(position);
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.menu_qs_remove) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_quick_search_title))
.setMessage(getString(R.string.delete_quick_search_message, quickSearch.name))
.setPositiveButton(R.string.delete, (dialog, which) -> {
EhDB.deleteQuickSearch(quickSearch);
mQuickSearchList.remove(position);
notifyDataSetChanged();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
}
return false;
}
});
return true;
});
} else {
int[] keywords = {11, 12, 13, 15};
int[] toplists = {R.string.toplist_alltime, R.string.toplist_pastyear, R.string.toplist_pastmonth, R.string.toplist_yesterday};
@@ -1647,7 +1561,7 @@ public final class GalleryListScene extends BaseScene
}
mUrlBuilder.setKeyword(String.valueOf(keywords[position]));
mUrlBuilder.setPageIndex(0);
mUrlBuilder.setNextGid(0);
onUpdateUrlBuilder();
mHelper.refresh();
setState(STATE_NORMAL);
@@ -1671,44 +1585,6 @@ public final class GalleryListScene extends BaseScene
public int getItemCount() {
return !mIsTopList ? mQuickSearchList != null ? mQuickSearchList.size() : 0 : 4;
}
@Override
public boolean onCheckCanStartDrag(@NonNull QsDrawerHolder holder, int position, int x, int y) {
return !mIsTopList && x > holder.option.getX() && y > holder.option.getY();
}
@Override
public ItemDraggableRange onGetItemDraggableRange(@NonNull QsDrawerHolder holder, int position) {
return null;
}
@Override
public void onMoveItem(int fromPosition, int toPosition) {
if (fromPosition == toPosition) {
return;
}
if (null == mQuickSearchList) {
return;
}
EhDB.moveQuickSearch(fromPosition, toPosition);
final QuickSearch item = mQuickSearchList.remove(fromPosition);
mQuickSearchList.add(toPosition, item);
}
@Override
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
return true;
}
@Override
public void onItemDragStarted(int position) {
notifyDataSetChanged();
}
@Override
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
notifyDataSetChanged();
}
}
private abstract class UrlSuggestion extends SearchBar.Suggestion {
@@ -1815,15 +1691,20 @@ public final class GalleryListScene extends BaseScene
}
private class GalleryListHelper extends GalleryInfoContentHelper {
public int pgCounter = 0;
@Override
protected void getPageData(int taskId, int type, int page) {
pgCounter = page;
MainActivity activity = getMainActivity();
if (null == activity || null == mClient || null == mUrlBuilder) {
return;
}
mUrlBuilder.setPageIndex(page);
if (page != 0)
mUrlBuilder.setNextGid(minGid);
else
mUrlBuilder.setNextGid(0);
if (ListUrlBuilder.MODE_IMAGE_SEARCH == mUrlBuilder.getMode()) {
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_IMAGE_SEARCH);
@@ -1893,4 +1774,47 @@ public final class GalleryListScene extends BaseScene
}
}
}
private class GalleryListQSItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final QsDrawerAdapter mAdapter;
public GalleryListQSItemTouchHelperCallback(QsDrawerAdapter adapter) {
mAdapter = adapter;
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
}
@SuppressLint("NotifyDataSetChanged")
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getBindingAdapterPosition();
int toPosition = target.getBindingAdapterPosition();
if (fromPosition == toPosition) {
return false;
}
if (null == mQuickSearchList) {
return false;
}
EhDB.moveQuickSearch(fromPosition, toPosition);
final QuickSearch item = mQuickSearchList.remove(fromPosition);
mQuickSearchList.add(toPosition, item);
mAdapter.notifyDataSetChanged();
return true;
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (mQuickSearchList == null)
return;
int position = viewHolder.getBindingAdapterPosition();
final QuickSearch quickSearch = mQuickSearchList.get(position);
EhDB.deleteQuickSearch(quickSearch);
mQuickSearchList.remove(position);
mAdapter.notifyDataSetChanged();
}
}
}
@@ -36,6 +36,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.slider.Slider;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
@@ -53,7 +54,6 @@ import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.widget.ContentLayout;
import com.hippo.widget.LoadImageView;
import com.hippo.widget.Slider;
import com.hippo.widget.recyclerview.AutoGridLayoutManager;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
@@ -209,7 +209,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return true;
}
int pages = mHelper.getPages();
if (pages > 0 && mHelper.canGoTo()) {
if (pages > 1 && mHelper.canGoTo()) {
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setTitle(R.string.go_to)
.setView(R.layout.dialog_go_to)
@@ -418,8 +418,8 @@ public class GalleryPreviewsScene extends ToolbarScene {
((TextView) ViewUtils.$$(dialog, R.id.start)).setText(String.format(Locale.US, "%d", 1));
((TextView) ViewUtils.$$(dialog, R.id.end)).setText(String.format(Locale.US, "%d", mPages));
mSlider = (Slider) ViewUtils.$$(dialog, R.id.slider);
mSlider.setRange(1, mPages);
mSlider.setProgress(mCurrentPage + 1);
mSlider.setValueTo(mPages);
mSlider.setValue(mCurrentPage + 1);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
dialog.setOnDismissListener(this);
@@ -431,7 +431,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return;
}
int page = mSlider.getProgress() - 1;
int page = (int) (mSlider.getValue() - 1);
if (page >= 0 && page < mPages && mHelper != null) {
mHelper.goTo(page);
if (mDialog != null) {
@@ -36,20 +36,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem;
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.HandlerDrawable;
@@ -206,17 +197,9 @@ public class HistoryScene extends ToolbarScene {
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
mTip.setCompoundDrawables(null, drawable, null, null);
RecyclerViewTouchActionGuardManager guardManager = new RecyclerViewTouchActionGuardManager();
guardManager.setInterceptVerticalScrollingWhileAnimationRunning(true);
guardManager.setEnabled(true);
RecyclerViewSwipeManager swipeManager = new RecyclerViewSwipeManager();
mAdapter = new HistoryAdapter();
mAdapter.setHasStableIds(true);
mAdapter = swipeManager.createWrappedAdapter(mAdapter);
mRecyclerView.setAdapter(mAdapter);
final GeneralItemAnimator animator = new SwipeDismissItemAnimator();
animator.setSupportsChangeAnimations(false);
mRecyclerView.setItemAnimator(animator);
AutoStaggeredGridLayoutManager layoutManager = new AutoStaggeredGridLayoutManager(
0, StaggeredGridLayoutManager.VERTICAL);
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()));
@@ -229,9 +212,8 @@ public class HistoryScene extends ToolbarScene {
int paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v);
MarginItemDecoration decoration = new MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV);
mRecyclerView.addItemDecoration(decoration);
guardManager.attachRecyclerView(mRecyclerView);
swipeManager.attachRecyclerView(mRecyclerView);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new HistoryItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(mRecyclerView);
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable handlerDrawable = new HandlerDrawable();
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorPrimary));
@@ -502,7 +484,7 @@ public class HistoryScene extends ToolbarScene {
}
}
private static class HistoryHolder extends AbstractSwipeableItemViewHolder {
private static class HistoryHolder extends RecyclerView.ViewHolder {
public final View card;
public final LoadImageView thumb;
@@ -529,12 +511,6 @@ public class HistoryScene extends ToolbarScene {
pages = itemView.findViewById(R.id.pages);
downloaded = itemView.findViewById(R.id.downloaded);
}
@NonNull
@Override
public View getSwipeableContainerView() {
return card;
}
}
private class MoveDialogHelper implements DialogInterface.OnClickListener {
@@ -570,8 +546,7 @@ public class HistoryScene extends ToolbarScene {
}
}
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder>
implements SwipeableItemAdapter<HistoryHolder> {
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder> {
private final LayoutInflater mInflater;
private final int mListThumbWidth;
@@ -649,55 +624,30 @@ public class HistoryScene extends ToolbarScene {
public int getItemCount() {
return null != mLazyList ? mLazyList.size() : 0;
}
@Override
public int onGetSwipeReactionType(@NonNull HistoryHolder holder, int position, int x, int y) {
return SwipeableItemConstants.REACTION_CAN_SWIPE_LEFT;
}
@Override
public void onSwipeItemStarted(@NonNull HistoryHolder holder, int position) {
}
@Override
public void onSetSwipeBackground(@NonNull HistoryHolder holder, int position, int type) {
}
@Override
public SwipeResultAction onSwipeItem(@NonNull HistoryHolder holder, int position, int result) {
switch (result) {
case SwipeableItemConstants.RESULT_SWIPED_LEFT:
return new SwipeResultActionClear(position);
case SwipeableItemConstants.RESULT_SWIPED_RIGHT:
case SwipeableItemConstants.RESULT_CANCELED:
case SwipeableItemConstants.RESULT_NONE:
case SwipeableItemConstants.RESULT_SWIPED_DOWN:
case SwipeableItemConstants.RESULT_SWIPED_UP:
default:
return new SwipeResultActionDefault();
}
}
}
private class SwipeResultActionClear extends SwipeResultActionRemoveItem {
private class HistoryItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final int mPosition;
protected SwipeResultActionClear(int position) {
mPosition = position;
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(0, ItemTouchHelper.LEFT);
}
@Override
protected void onPerformAction() {
super.onPerformAction();
if (null == mLazyList || null == mAdapter) {
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int mPosition = viewHolder.getBindingAdapterPosition();
if (null == mLazyList || mAdapter == null) {
return;
}
HistoryInfo info = mLazyList.get(mPosition);
EhDB.deleteHistoryInfo(info);
updateLazyList();
mAdapter.notifyDataSetChanged();
mAdapter.notifyItemRemoved(mPosition);
updateView(true);
}
}
@@ -27,6 +27,7 @@ import androidx.biometric.auth.AuthPromptCallback
import androidx.biometric.auth.startClass2BiometricOrCredentialAuthentication
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
class SecurityScene : SolidScene() {
@@ -39,7 +40,7 @@ class SecurityScene : SolidScene() {
if (isAuthenticationSupported(requireContext()))
startAuthentication(getString(R.string.settings_privacy_require_unlock))
else
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
onSuccess()
}
private fun startAuthentication(
@@ -61,8 +62,15 @@ class SecurityScene : SolidScene() {
activity: FragmentActivity?,
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(activity, result)
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
onSuccess()
}
override fun onAuthenticationError(
activity: FragmentActivity?,
errorCode: Int,
errString: CharSequence
) {
requireActivity().finish()
}
}
@@ -73,4 +81,11 @@ class SecurityScene : SolidScene() {
.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
}
}
private fun onSuccess() {
EhApplication.locked = false
if (stackIndex == 0)
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
finish()
}
}
@@ -87,12 +87,6 @@ public abstract class ToolbarScene extends BaseScene {
}
}
@Override
public boolean needWhiteStatusBar() {
return false;
}
public int getMenuResId() {
return 0;
}
@@ -1,229 +0,0 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.NumberUtils;
public class AdvanceSearchTable extends LinearLayout {
public static final int SNAME = 0x1;
public static final int STAGS = 0x2;
public static final int SDESC = 0x4;
public static final int STORR = 0x8;
public static final int STO = 0x10;
public static final int SDT1 = 0x20;
public static final int SDT2 = 0x40;
public static final int SH = 0x80;
public static final int SFL = 0x100;
public static final int SFU = 0x200;
public static final int SFT = 0x400;
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_ADVANCE_SEARCH = "advance_search";
private static final String STATE_KEY_MIN_RATING = "min_rating";
private static final String STATE_KEY_PAGE_FROM = "page_from";
private static final String STATE_KEY_PAGE_TO = "page_to";
private CheckBox mSname;
private CheckBox mStags;
private CheckBox mSdesc;
private CheckBox mStorr;
private CheckBox mSto;
private CheckBox mSdt1;
private CheckBox mSdt2;
private CheckBox mSh;
private CheckBox mSr;
private Spinner mMinRating;
private CheckBox mSp;
private EditText mSpf;
private EditText mSpt;
private CheckBox mSfl;
private CheckBox mSfu;
private CheckBox mSft;
public AdvanceSearchTable(Context context) {
super(context);
init(context);
}
public AdvanceSearchTable(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public void init(Context context) {
setOrientation(LinearLayout.VERTICAL);
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_advance_search_table, this);
ViewGroup row0 = (ViewGroup) getChildAt(0);
mSname = (CheckBox) row0.getChildAt(0);
mStags = (CheckBox) row0.getChildAt(1);
ViewGroup row1 = (ViewGroup) getChildAt(1);
mSdesc = (CheckBox) row1.getChildAt(0);
mStorr = (CheckBox) row1.getChildAt(1);
ViewGroup row2 = (ViewGroup) getChildAt(2);
mSto = (CheckBox) row2.getChildAt(0);
mSdt1 = (CheckBox) row2.getChildAt(1);
ViewGroup row3 = (ViewGroup) getChildAt(3);
mSdt2 = (CheckBox) row3.getChildAt(0);
mSh = (CheckBox) row3.getChildAt(1);
ViewGroup row4 = (ViewGroup) getChildAt(4);
mSr = (CheckBox) row4.getChildAt(0);
mMinRating = (Spinner) row4.getChildAt(1);
ViewGroup row5 = (ViewGroup) getChildAt(5);
mSp = (CheckBox) row5.getChildAt(0);
mSpf = (EditText) row5.getChildAt(1);
mSpt = (EditText) row5.getChildAt(3);
ViewGroup row7 = (ViewGroup) getChildAt(7);
mSfl = (CheckBox) row7.getChildAt(0);
mSfu = (CheckBox) row7.getChildAt(1);
mSft = (CheckBox) row7.getChildAt(2);
// Avoid java.lang.IllegalStateException: focus search returned a view that wasn't able to take focus!
mSpt.setOnEditorActionListener((v, actionId, event) -> {
View nextView = v.focusSearch(View.FOCUS_DOWN);
if (nextView != null) {
nextView.requestFocus(View.FOCUS_DOWN);
}
return true;
});
}
public int getAdvanceSearch() {
int advanceSearch = 0;
if (mSname.isChecked()) advanceSearch |= SNAME;
if (mStags.isChecked()) advanceSearch |= STAGS;
if (mSdesc.isChecked()) advanceSearch |= SDESC;
if (mStorr.isChecked()) advanceSearch |= STORR;
if (mSto.isChecked()) advanceSearch |= STO;
if (mSdt1.isChecked()) advanceSearch |= SDT1;
if (mSdt2.isChecked()) advanceSearch |= SDT2;
if (mSh.isChecked()) advanceSearch |= SH;
if (mSfl.isChecked()) advanceSearch |= SFL;
if (mSfu.isChecked()) advanceSearch |= SFU;
if (mSft.isChecked()) advanceSearch |= SFT;
return advanceSearch;
}
public void setAdvanceSearch(int advanceSearch) {
mSname.setChecked(NumberUtils.int2boolean(advanceSearch & SNAME));
mStags.setChecked(NumberUtils.int2boolean(advanceSearch & STAGS));
mSdesc.setChecked(NumberUtils.int2boolean(advanceSearch & SDESC));
mStorr.setChecked(NumberUtils.int2boolean(advanceSearch & STORR));
mSto.setChecked(NumberUtils.int2boolean(advanceSearch & STO));
mSdt1.setChecked(NumberUtils.int2boolean(advanceSearch & SDT1));
mSdt2.setChecked(NumberUtils.int2boolean(advanceSearch & SDT2));
mSh.setChecked(NumberUtils.int2boolean(advanceSearch & SH));
mSfl.setChecked(NumberUtils.int2boolean(advanceSearch & SFL));
mSfu.setChecked(NumberUtils.int2boolean(advanceSearch & SFU));
mSft.setChecked(NumberUtils.int2boolean(advanceSearch & SFT));
}
public int getMinRating() {
int position = mMinRating.getSelectedItemPosition();
if (mSr.isChecked() && position >= 0) {
return position + 2;
} else {
return -1;
}
}
public void setMinRating(int minRating) {
if (minRating >= 2 && minRating <= 5) {
mSr.setChecked(true);
mMinRating.setSelection(minRating - 2);
} else {
mSr.setChecked(false);
}
}
public int getPageFrom() {
if (mSp.isChecked()) {
return NumberUtils.parseIntSafely(mSpf.getText().toString(), -1);
}
return -1;
}
public void setPageFrom(int pageFrom) {
if (pageFrom > 0) {
mSpf.setText(Integer.toString(pageFrom));
mSp.setChecked(true);
} else {
mSp.setChecked(false);
mSpf.setText(null);
}
}
public int getPageTo() {
if (mSp.isChecked()) {
return NumberUtils.parseIntSafely(mSpt.getText().toString(), -1);
}
return -1;
}
public void setPageTo(int pageTo) {
if (pageTo > 0) {
mSpt.setText(Integer.toString(pageTo));
mSp.setChecked(true);
} else {
mSp.setChecked(false);
mSpt.setText(null);
}
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_ADVANCE_SEARCH, getAdvanceSearch());
state.putInt(STATE_KEY_MIN_RATING, getMinRating());
state.putInt(STATE_KEY_PAGE_FROM, getPageFrom());
state.putInt(STATE_KEY_PAGE_TO, getPageTo());
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
setAdvanceSearch(savedState.getInt(STATE_KEY_ADVANCE_SEARCH));
setMinRating(savedState.getInt(STATE_KEY_MIN_RATING));
setPageFrom(savedState.getInt(STATE_KEY_PAGE_FROM));
setPageTo(savedState.getInt(STATE_KEY_PAGE_TO));
}
}
}
@@ -0,0 +1,189 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.*
import com.google.android.material.textfield.TextInputLayout
import com.hippo.ehviewer.R
import com.hippo.yorozuya.NumberUtils
class AdvanceSearchTable @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
private var mSname: CheckBox
private var mStags: CheckBox
private var mSdesc: CheckBox
private var mStorr: CheckBox
private var mSto: CheckBox
private var mSdt1: CheckBox
private var mSdt2: CheckBox
private var mSh: CheckBox
private var mSr: CheckBox
private var mMinRating: TextInputLayout
private var mSp: CheckBox
private var mSpf: EditText
private var mSpt: EditText
private var mSfl: CheckBox
private var mSfu: CheckBox
private var mSft: CheckBox
private val mArray: Array<String> = context.resources.getStringArray(R.array.search_min_rating)
init {
orientation = VERTICAL
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.widget_advance_search_table, this)
val row0 = getChildAt(0) as ViewGroup
mSname = row0.getChildAt(0) as CheckBox
mStags = row0.getChildAt(1) as CheckBox
val row1 = getChildAt(1) as ViewGroup
mSdesc = row1.getChildAt(0) as CheckBox
mStorr = row1.getChildAt(1) as CheckBox
val row2 = getChildAt(2) as ViewGroup
mSto = row2.getChildAt(0) as CheckBox
mSdt1 = row2.getChildAt(1) as CheckBox
val row3 = getChildAt(3) as ViewGroup
mSdt2 = row3.getChildAt(0) as CheckBox
mSh = row3.getChildAt(1) as CheckBox
val row4 = getChildAt(4) as ViewGroup
mSr = row4.getChildAt(0) as CheckBox
mMinRating = row4.getChildAt(1) as TextInputLayout
val row5 = getChildAt(5) as ViewGroup
mSp = row5.getChildAt(0) as CheckBox
mSpf = row5.getChildAt(1) as EditText
mSpt = row5.getChildAt(3) as EditText
val row7 = getChildAt(7) as ViewGroup
mSfl = row7.getChildAt(0) as CheckBox
mSfu = row7.getChildAt(1) as CheckBox
mSft = row7.getChildAt(2) as CheckBox
mSpt.setOnEditorActionListener { v: TextView, _: Int, _: KeyEvent? ->
val nextView = v.focusSearch(
FOCUS_DOWN
)
nextView?.requestFocus(FOCUS_DOWN)
true
}
mMinRating.isEnabled = mSr.isChecked
mSr.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
mMinRating.isEnabled = isChecked
}
mSpt.isEnabled = mSp.isChecked
mSpf.isEnabled = mSp.isChecked
mSp.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
mSpt.isEnabled = isChecked
mSpf.isEnabled = isChecked
}
}
var advanceSearch: Int
get() {
var advanceSearch = 0
if (mSname.isChecked) advanceSearch = advanceSearch or SNAME
if (mStags.isChecked) advanceSearch = advanceSearch or STAGS
if (mSdesc.isChecked) advanceSearch = advanceSearch or SDESC
if (mStorr.isChecked) advanceSearch = advanceSearch or STORR
if (mSto.isChecked) advanceSearch = advanceSearch or STO
if (mSdt1.isChecked) advanceSearch = advanceSearch or SDT1
if (mSdt2.isChecked) advanceSearch = advanceSearch or SDT2
if (mSh.isChecked) advanceSearch = advanceSearch or SH
if (mSfl.isChecked) advanceSearch = advanceSearch or SFL
if (mSfu.isChecked) advanceSearch = advanceSearch or SFU
if (mSft.isChecked) advanceSearch = advanceSearch or SFT
return advanceSearch
}
set(advanceSearch) {
mSname.isChecked =
NumberUtils.int2boolean(advanceSearch and SNAME)
mStags.isChecked =
NumberUtils.int2boolean(advanceSearch and STAGS)
mSdesc.isChecked =
NumberUtils.int2boolean(advanceSearch and SDESC)
mStorr.isChecked = NumberUtils.int2boolean(advanceSearch and STORR)
mSto.isChecked = NumberUtils.int2boolean(advanceSearch and STO)
mSdt1.isChecked = NumberUtils.int2boolean(advanceSearch and SDT1)
mSdt2.isChecked = NumberUtils.int2boolean(advanceSearch and SDT2)
mSh.isChecked = NumberUtils.int2boolean(advanceSearch and SH)
mSfl.isChecked = NumberUtils.int2boolean(advanceSearch and SFL)
mSfu.isChecked = NumberUtils.int2boolean(advanceSearch and SFU)
mSft.isChecked = NumberUtils.int2boolean(advanceSearch and SFT)
}
var minRating: Int
get() {
val position = mArray.indexOf(mMinRating.editText!!.text.toString())
return if (mSr.isChecked && position >= 0) {
position + 2
} else {
-1
}
}
set(minRating) {
if (minRating in 2..5) {
mSr.isChecked = true
(mMinRating.editText!! as AutoCompleteTextView).setText(
mArray[minRating - 2],
false
)
} else {
mSr.isChecked = false
}
}
var pageFrom: Int
get() = if (mSp.isChecked) {
NumberUtils.parseIntSafely(mSpf.text.toString(), -1)
} else -1
set(pageFrom) {
if (pageFrom > 0) {
mSpf.setText(pageFrom.toString())
mSp.isChecked = true
} else {
mSp.isChecked = false
mSpf.text = null
}
}
var pageTo: Int
get() = if (mSp.isChecked) {
NumberUtils.parseIntSafely(mSpt.text.toString(), -1)
} else -1
set(pageTo) {
if (pageTo > 0) {
mSpt.setText(pageTo.toString())
mSp.isChecked = true
} else {
mSp.isChecked = false
}
}
companion object {
const val SNAME = 0x1
const val STAGS = 0x2
const val SDESC = 0x4
const val STORR = 0x8
const val STO = 0x10
const val SDT1 = 0x20
const val SDT2 = 0x40
const val SH = 0x80
const val SFL = 0x100
const val SFU = 0x200
const val SFT = 0x400
}
}
@@ -53,11 +53,6 @@ public class FixedThumb extends LoadImageView {
a.recycle();
}
public void setFix(float minAspect, float maxAspect) {
this.minAspect = minAspect;
this.maxAspect = maxAspect;
}
@Override
public void onPreSetImageDrawable(Drawable drawable, boolean isTarget) {
if (alwaysCutAndScale) {
@@ -30,8 +30,6 @@ import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.yorozuya.ViewUtils;
import rikka.core.res.ResourcesKt;
public class GalleryGuideView extends ViewGroup implements View.OnClickListener {
private final float[] mPoints = new float[3 * 4];
@@ -31,8 +31,14 @@ import java.util.List;
import java.util.Map;
public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelper<GalleryInfo> {
public int maxGid = -1;
public int minGid = -1;
private static final String KEY_DATA_MAP = "data_map";
private static final String KEY_MAX_GID = "max_gid";
private static final String KEY_MIN_GID = "min_gid";
public static final String KEY_NEXT_PAGE = "next_page";
private final FavouriteStatusRouter.Listener listener;
@SuppressLint("UseSparseArrays")
private Map<Long, GalleryInfo> map = new HashMap<>();
@@ -59,6 +65,12 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
@Override
protected void onAddData(List<GalleryInfo> data) {
for (GalleryInfo info : data) {
if (maxGid == -1)
maxGid = (int) info.gid;
if (minGid == -1)
minGid = (int) info.gid;
maxGid = (int) Math.max(maxGid, info.gid);
minGid = (int) Math.min(minGid, info.gid);
map.put(info.gid, info);
}
}
@@ -78,6 +90,8 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
@Override
protected void onClearData() {
map.clear();
maxGid = -1;
minGid = -1;
}
@Override
@@ -88,6 +102,8 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
FavouriteStatusRouter router = EhApplication.getFavouriteStatusRouter();
int id = router.saveDataMap(map);
bundle.putInt(KEY_DATA_MAP, id);
bundle.putInt(KEY_MIN_GID, minGid);
bundle.putInt(KEY_MAX_GID, maxGid);
return bundle;
}
@@ -104,6 +120,8 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
this.map = map;
}
}
minGid = bundle.getInt(KEY_MIN_GID);
maxGid = bundle.getInt(KEY_MAX_GID);
return super.restoreInstanceState(state);
}
@@ -0,0 +1,13 @@
package com.hippo.ehviewer.widget
import android.content.Context
import android.util.AttributeSet
import com.hippo.ehviewer.Settings
class ResizeableFixedThumb @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FixedThumb(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(Settings.getListThumbSize() * 2, Settings.getListThumbSize() * 3)
}
}
@@ -48,12 +48,10 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhTagDatabase;
import com.hippo.view.ViewTransition;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.ViewUtils;
@@ -61,8 +59,6 @@ import com.hippo.yorozuya.ViewUtils;
import java.util.ArrayList;
import java.util.List;
import rikka.core.res.ResourcesKt;
public class SearchBar extends MaterialCardView implements View.OnClickListener,
TextView.OnEditorActionListener, TextWatcher,
SearchEditText.SearchEditTextListener {
@@ -147,12 +143,6 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
mSuggestionAdapter = new SuggestionAdapter(LayoutInflater.from(getContext()));
mListView.setAdapter(mSuggestionAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(context.getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(false);
mListView.addItemDecoration(decoration);
mListView.setLayoutManager(layoutManager);
}
@@ -249,7 +249,7 @@ class SearchLayout @JvmOverloads constructor(
fun onSelectImage()
}
internal class SearchLayoutManager(context: Context?) : LinearLayoutManager(context) {
internal class SearchLayoutManager(context: Context?) : LinearLayoutManager(context!!) {
override fun onLayoutChildren(recycler: Recycler, state: State) {
try {
super.onLayoutChildren(recycler, state)
@@ -16,12 +16,11 @@
package com.hippo.glview.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.os.Build;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock;
@@ -42,10 +41,6 @@ import com.hippo.glview.glrenderer.UploadedTexture;
import com.hippo.glview.util.ApiHelper;
import com.hippo.glview.util.GalleryUtils;
import com.hippo.glview.util.MotionEventHelper;
import com.hippo.tuxiang.EGLConfigChooser;
import com.hippo.tuxiang.EGLContextFactory;
import com.hippo.tuxiang.GLSurfaceView;
import com.hippo.tuxiang.Renderer;
import com.hippo.yorozuya.AssertUtils;
import java.util.ArrayDeque;
@@ -89,12 +84,7 @@ public class GLRootView extends GLSurfaceView
private final ReentrantLock mRenderLock = new ReentrantLock();
private final Condition mFreezeCondition =
mRenderLock.newCondition();
private final Runnable mRequestRenderOnAnimationFrame = new Runnable() {
@Override
public void run() {
superRequestRender();
}
};
private final Runnable mRequestRenderOnAnimationFrame = this::superRequestRender;
private int mFrameCount = 0;
private long mFrameCountingStart = 0;
private int mInvalidateColor = 0;
@@ -187,11 +177,7 @@ public class GLRootView extends GLSurfaceView
}
if (mRenderRequested) return;
mRenderRequested = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
postOnAnimation(mRequestRenderOnAnimationFrame);
} else {
super.requestRender();
}
postOnAnimation(mRequestRenderOnAnimationFrame);
}
private void superRequestRender() {
@@ -281,7 +267,7 @@ public class GLRootView extends GLSurfaceView
++mFrameCount;
}
private void onDrawFrameLocked(GL10 gl) {
private void onDrawFrameLocked() {
if (DEBUG_FPS) outputFps();
// release the unbound textures and deleted buffers.
mCanvas.deleteRecycledResources();
@@ -391,9 +377,20 @@ public class GLRootView extends GLSurfaceView
@Override
public void onPause() {
unfreeze();
if (mContentView != null) {
mContentView.pause();
}
super.onPause();
}
@Override
public void onResume() {
if (mContentView != null) {
mContentView.resume();
}
super.onResume();
}
@Override
public void setOrientationSource(OrientationSource source) {
mOrientationSource = source;
@@ -430,7 +427,6 @@ public class GLRootView extends GLSurfaceView
}
@Override
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void setLightsOutMode(boolean enabled) {
if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
@@ -503,7 +499,7 @@ public class GLRootView extends GLSurfaceView
}
}
private class GLRootRenderer implements Renderer {
private class GLRootRenderer implements GLSurfaceView.Renderer {
/**
* Called when the context is created, possibly after automatic destruction.
@@ -547,7 +543,7 @@ public class GLRootView extends GLSurfaceView
}
@Override
public boolean onDrawFrame(GL10 gl) {
public void onDrawFrame(GL10 gl) {
AnimationTime.update();
long t0 = System.nanoTime();
@@ -559,7 +555,7 @@ public class GLRootView extends GLSurfaceView
}
try {
onDrawFrameLocked(gl);
onDrawFrameLocked();
} finally {
mRenderLock.unlock();
}
@@ -570,33 +566,6 @@ public class GLRootView extends GLSurfaceView
if (duration > 8) {
Log.v(TAG, "--- " + duration + " ---");
}
return true;
}
@Override
public void onGLThreadStart() {
}
@Override
public void onGLThreadExit() {
if (mContentView != null && mContentView.isAttachedToRoot()) {
mContentView.detachFromRoot();
}
}
@Override
public void onGLThreadPause() {
if (mContentView != null) {
mContentView.pause();
}
}
@Override
public void onGLThreadResume() {
if (mContentView != null) {
mContentView.resume();
}
}
}
@@ -662,7 +631,7 @@ public class GLRootView extends GLSurfaceView
throw new IllegalArgumentException("No config chosen");
}
int renderableType = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE, 0);
int renderableType = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE);
if ((renderableType & 0x0004) == 0x0004 /* EGL_OPENGL_ES2_BIT */) {
mEGLContextClientVersion = 2;
} else {
@@ -680,21 +649,21 @@ public class GLRootView extends GLSurfaceView
for (int i = 0, n = configs.length; i < n; i++) {
final EGLConfig config = configs[i];
final int redSize = findConfigAttrib(egl, display, config,
EGL10.EGL_RED_SIZE, 0);
EGL10.EGL_RED_SIZE);
final int greenSize = findConfigAttrib(egl, display, config,
EGL10.EGL_GREEN_SIZE, 0);
EGL10.EGL_GREEN_SIZE);
final int blueSize = findConfigAttrib(egl, display, config,
EGL10.EGL_BLUE_SIZE, 0);
EGL10.EGL_BLUE_SIZE);
final int alphaSize = findConfigAttrib(egl, display, config,
EGL10.EGL_ALPHA_SIZE, 0);
EGL10.EGL_ALPHA_SIZE);
final int sampleBuffers = findConfigAttrib(egl, display, config,
EGL10.EGL_SAMPLE_BUFFERS, 0);
EGL10.EGL_SAMPLE_BUFFERS);
final int samples = findConfigAttrib(egl, display, config,
EGL10.EGL_SAMPLES, 0);
EGL10.EGL_SAMPLES);
final int depth = findConfigAttrib(egl, display, config,
EGL10.EGL_DEPTH_SIZE, 0);
EGL10.EGL_DEPTH_SIZE);
final int stencil = findConfigAttrib(egl, display, config,
EGL10.EGL_STENCIL_SIZE, 0);
EGL10.EGL_STENCIL_SIZE);
final int score = redSize + greenSize + blueSize + alphaSize +
sampleBuffers + samples - depth - stencil;
@@ -709,11 +678,11 @@ public class GLRootView extends GLSurfaceView
}
private int findConfigAttrib(EGL10 egl, EGLDisplay display,
EGLConfig config, int attribute, int defaultValue) {
EGLConfig config, int attribute) {
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
return mValue[0];
}
return defaultValue;
return 0;
}
}
@@ -1,237 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
/*
* Created by Hippo on 2017/9/4.
*/
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import androidx.annotation.Nullable;
import com.hippo.database.MSQLiteBuilder;
import com.hippo.util.SqlUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.Cookie;
class CookieDatabase {
private static final String LOG_TAG = CookieDatabase.class.getSimpleName();
private static final int VERSION_1 = 1;
private static final String TABLE_COOKIE = "OK_HTTP_3_COOKIE";
private static final String COLUMN_ID = "_id";
private static final String COLUMN_NAME = "NAME";
private static final String COLUMN_VALUE = "VALUE";
private static final String COLUMN_EXPIRES_AT = "EXPIRES_AT";
private static final String COLUMN_DOMAIN = "DOMAIN";
private static final String COLUMN_PATH = "PATH";
private static final String COLUMN_SECURE = "SECURE";
private static final String COLUMN_HTTP_ONLY = "HTTP_ONLY";
private static final String COLUMN_PERSISTENT = "PERSISTENT";
private static final String COLUMN_HOST_ONLY = "HOST_ONLY";
private static final int DB_VERSION = VERSION_1;
private final Map<Cookie, Long> cookieIdMap = new HashMap<>();
private final SQLiteOpenHelper helper;
private final SQLiteDatabase db;
public CookieDatabase(Context context, String name) {
helper = new MSQLiteBuilder()
.version(VERSION_1)
.createTable(TABLE_COOKIE)
.insertColumn(TABLE_COOKIE, COLUMN_NAME, String.class)
.insertColumn(TABLE_COOKIE, COLUMN_VALUE, String.class)
.insertColumn(TABLE_COOKIE, COLUMN_EXPIRES_AT, long.class)
.insertColumn(TABLE_COOKIE, COLUMN_DOMAIN, String.class)
.insertColumn(TABLE_COOKIE, COLUMN_PATH, String.class)
.insertColumn(TABLE_COOKIE, COLUMN_SECURE, boolean.class)
.insertColumn(TABLE_COOKIE, COLUMN_HTTP_ONLY, boolean.class)
.insertColumn(TABLE_COOKIE, COLUMN_PERSISTENT, boolean.class)
.insertColumn(TABLE_COOKIE, COLUMN_HOST_ONLY, boolean.class)
.build(context, name, DB_VERSION);
db = helper.getWritableDatabase();
}
@Nullable
private static Cookie getCookie(Cursor cursor, long now) {
String name = SqlUtils.getString(cursor, COLUMN_NAME, null);
String value = SqlUtils.getString(cursor, COLUMN_VALUE, null);
long expiresAt = SqlUtils.getLong(cursor, COLUMN_EXPIRES_AT, 0);
String domain = SqlUtils.getString(cursor, COLUMN_DOMAIN, null);
String path = SqlUtils.getString(cursor, COLUMN_PATH, null);
boolean secure = SqlUtils.getBoolean(cursor, COLUMN_SECURE, false);
boolean httpOnly = SqlUtils.getBoolean(cursor, COLUMN_HTTP_ONLY, false);
boolean persistent = SqlUtils.getBoolean(cursor, COLUMN_PERSISTENT, false);
boolean hostOnly = SqlUtils.getBoolean(cursor, COLUMN_HOST_ONLY, false);
if (name == null || domain == null || path == null) {
return null;
}
// Check non-persistent or expired
if (!persistent || expiresAt <= now) {
return null;
}
Cookie.Builder builder = new Cookie.Builder();
builder.name(name);
builder.value(value);
if (hostOnly) {
builder.hostOnlyDomain(domain);
} else {
builder.domain(domain);
}
builder.path(path);
builder.expiresAt(expiresAt);
if (secure) builder.secure();
if (httpOnly) builder.httpOnly();
return builder.build();
}
public Map<String, CookieSet> getAllCookies() {
long now = System.currentTimeMillis();
Map<String, CookieSet> map = new HashMap<>();
List<Long> toRemove = new ArrayList<>();
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_COOKIE + ";", null)) {
while (cursor.moveToNext()) {
long id = SqlUtils.getLong(cursor, COLUMN_ID, 0);
Cookie cookie = getCookie(cursor, now);
if (cookie != null) {
// Save id of the cookie in db
cookieIdMap.put(cookie, id);
// Put cookie to set
CookieSet set = map.get(cookie.domain());
if (set == null) {
set = new CookieSet();
map.put(cookie.domain(), set);
}
set.add(cookie);
} else {
// Mark to remove the cookie
toRemove.add(id);
}
}
}
// Remove invalid or expired cookie
if (!toRemove.isEmpty()) {
SQLiteStatement statement = db.compileStatement(
"DELETE FROM " + TABLE_COOKIE + " WHERE " + COLUMN_ID + " = ?;");
db.beginTransaction();
try {
for (long id : toRemove) {
statement.bindLong(1, id);
statement.executeUpdateDelete();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
return map;
}
public ContentValues toContentValues(Cookie cookie) {
ContentValues contentValues = new ContentValues(9);
contentValues.put(COLUMN_NAME, cookie.name());
contentValues.put(COLUMN_VALUE, cookie.value());
contentValues.put(COLUMN_EXPIRES_AT, cookie.expiresAt());
contentValues.put(COLUMN_DOMAIN, cookie.domain());
contentValues.put(COLUMN_PATH, cookie.path());
contentValues.put(COLUMN_SECURE, cookie.secure());
contentValues.put(COLUMN_HTTP_ONLY, cookie.httpOnly());
contentValues.put(COLUMN_PERSISTENT, cookie.persistent());
contentValues.put(COLUMN_HOST_ONLY, cookie.hostOnly());
return contentValues;
}
public void add(Cookie cookie) {
long id = db.insert(TABLE_COOKIE, null, toContentValues(cookie));
if (id != -1L) {
Long oldId = cookieIdMap.put(cookie, id);
if (oldId != null) Log.e(LOG_TAG, "Add a duplicate cookie");
} else {
Log.e(LOG_TAG, "An error occurred when insert a cookie");
}
}
public void update(Cookie from, Cookie to) {
Long id = cookieIdMap.get(from);
if (id == null) {
Log.e(LOG_TAG, "Can't get id when update the cookie");
return;
}
ContentValues values = toContentValues(to);
String whereClause = COLUMN_ID + " = ?";
String[] whereArgs = {id.toString()};
int count = db.update(TABLE_COOKIE, values, whereClause, whereArgs);
if (count != 1) {
Log.e(LOG_TAG, "Bad result when update cookie: " + count);
}
// Update it in cookie-id map
cookieIdMap.remove(from);
cookieIdMap.put(to, id);
}
public void remove(Cookie cookie) {
Long id = cookieIdMap.get(cookie);
if (id == null) {
Log.e(LOG_TAG, "Can't get id when remove the cookie");
return;
}
String whereClause = COLUMN_ID + " = ?";
String[] whereArgs = {id.toString()};
int count = db.delete(TABLE_COOKIE, whereClause, whereArgs);
if (count != 1) {
Log.e(LOG_TAG, "Bad result when remove cookie: " + count);
}
// Remove it from cookie-id map
cookieIdMap.remove(cookie);
}
public void clear() {
db.delete(TABLE_COOKIE, null, null);
cookieIdMap.clear();
}
public void close() {
db.close();
helper.close();
}
}
@@ -0,0 +1,214 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.util.Log
import com.hippo.database.MSQLiteBuilder
import com.hippo.util.SqlUtils
import okhttp3.Cookie
internal class CookieDatabase(context: Context, name: String) {
private val cookieIdMap: MutableMap<Cookie, Long> = HashMap()
private val helper: SQLiteOpenHelper
private val db: SQLiteDatabase
val allCookies: MutableMap<String, CookieSet>
get() {
val now = System.currentTimeMillis()
val map: MutableMap<String, CookieSet> = HashMap()
val toRemove: MutableList<Long> = ArrayList()
db.rawQuery("SELECT * FROM $TABLE_COOKIE;", null).use { cursor ->
while (cursor.moveToNext()) {
val id = SqlUtils.getLong(cursor, COLUMN_ID, 0)
val cookie = getCookie(cursor, now)
if (cookie != null) {
// Save id of the cookie in db
cookieIdMap[cookie] = id
// Put cookie to set
var set = map[cookie.domain]
if (set == null) {
set = CookieSet()
map[cookie.domain] = set
}
set.add(cookie)
} else {
// Mark to remove the cookie
toRemove.add(id)
}
}
}
// Remove invalid or expired cookie
if (toRemove.isNotEmpty()) {
val statement = db.compileStatement(
"DELETE FROM $TABLE_COOKIE WHERE $COLUMN_ID = ?;"
)
db.beginTransaction()
try {
for (id in toRemove) {
statement.bindLong(1, id)
statement.executeUpdateDelete()
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
return map
}
private fun toContentValues(cookie: Cookie): ContentValues {
val contentValues = ContentValues(9)
contentValues.put(COLUMN_NAME, cookie.name)
contentValues.put(COLUMN_VALUE, cookie.value)
contentValues.put(COLUMN_EXPIRES_AT, cookie.expiresAt)
contentValues.put(COLUMN_DOMAIN, cookie.domain)
contentValues.put(COLUMN_PATH, cookie.path)
contentValues.put(COLUMN_SECURE, cookie.secure)
contentValues.put(COLUMN_HTTP_ONLY, cookie.httpOnly)
contentValues.put(COLUMN_PERSISTENT, cookie.persistent)
contentValues.put(COLUMN_HOST_ONLY, cookie.hostOnly)
return contentValues
}
fun add(cookie: Cookie) {
val id = db.insert(TABLE_COOKIE, null, toContentValues(cookie))
if (id != -1L) {
val oldId = cookieIdMap.put(cookie, id)
if (oldId != null) Log.e(LOG_TAG, "Add a duplicate cookie")
} else {
Log.e(LOG_TAG, "An error occurred when insert a cookie")
}
}
fun update(from: Cookie, to: Cookie) {
val id = cookieIdMap[from]
if (id == null) {
Log.e(LOG_TAG, "Can't get id when update the cookie")
return
}
val values = toContentValues(to)
val whereClause = "$COLUMN_ID = ?"
val whereArgs = arrayOf(id.toString())
val count = db.update(TABLE_COOKIE, values, whereClause, whereArgs)
if (count != 1) {
Log.e(LOG_TAG, "Bad result when update cookie: $count")
}
// Update it in cookie-id map
cookieIdMap.remove(from)
cookieIdMap[to] = id
}
fun remove(cookie: Cookie) {
val id = cookieIdMap[cookie]
if (id == null) {
Log.e(LOG_TAG, "Can't get id when remove the cookie")
return
}
val whereClause = "$COLUMN_ID = ?"
val whereArgs = arrayOf(id.toString())
val count = db.delete(TABLE_COOKIE, whereClause, whereArgs)
if (count != 1) {
Log.e(LOG_TAG, "Bad result when remove cookie: $count")
}
// Remove it from cookie-id map
cookieIdMap.remove(cookie)
}
fun clear() {
db.delete(TABLE_COOKIE, null, null)
cookieIdMap.clear()
}
fun close() {
db.close()
helper.close()
}
companion object {
private val LOG_TAG = CookieDatabase::class.java.simpleName
private const val VERSION_1 = 1
private const val TABLE_COOKIE = "OK_HTTP_3_COOKIE"
private const val COLUMN_ID = "_id"
private const val COLUMN_NAME = "NAME"
private const val COLUMN_VALUE = "VALUE"
private const val COLUMN_EXPIRES_AT = "EXPIRES_AT"
private const val COLUMN_DOMAIN = "DOMAIN"
private const val COLUMN_PATH = "PATH"
private const val COLUMN_SECURE = "SECURE"
private const val COLUMN_HTTP_ONLY = "HTTP_ONLY"
private const val COLUMN_PERSISTENT = "PERSISTENT"
private const val COLUMN_HOST_ONLY = "HOST_ONLY"
private const val DB_VERSION = VERSION_1
private fun getCookie(cursor: Cursor, now: Long): Cookie? {
val name = SqlUtils.getString(cursor, COLUMN_NAME, null)
val value = SqlUtils.getString(cursor, COLUMN_VALUE, null)
val expiresAt = SqlUtils.getLong(cursor, COLUMN_EXPIRES_AT, 0)
val domain = SqlUtils.getString(cursor, COLUMN_DOMAIN, null)
val path = SqlUtils.getString(cursor, COLUMN_PATH, null)
val secure = SqlUtils.getBoolean(cursor, COLUMN_SECURE, false)
val httpOnly = SqlUtils.getBoolean(cursor, COLUMN_HTTP_ONLY, false)
val persistent = SqlUtils.getBoolean(cursor, COLUMN_PERSISTENT, false)
val hostOnly = SqlUtils.getBoolean(cursor, COLUMN_HOST_ONLY, false)
if (name == null || domain == null || path == null) {
return null
}
// Check non-persistent or expired
if (!persistent || expiresAt <= now) {
return null
}
val builder = Cookie.Builder()
builder.name(name)
builder.value(value)
if (hostOnly) {
builder.hostOnlyDomain(domain)
} else {
builder.domain(domain)
}
builder.path(path)
builder.expiresAt(expiresAt)
if (secure) builder.secure()
if (httpOnly) builder.httpOnly()
return builder.build()
}
}
init {
helper = MSQLiteBuilder()
.version(VERSION_1)
.createTable(TABLE_COOKIE)
.insertColumn(TABLE_COOKIE, COLUMN_NAME, String::class.java)
.insertColumn(TABLE_COOKIE, COLUMN_VALUE, String::class.java)
.insertColumn(TABLE_COOKIE, COLUMN_EXPIRES_AT, Long::class.javaPrimitiveType)
.insertColumn(TABLE_COOKIE, COLUMN_DOMAIN, String::class.java)
.insertColumn(TABLE_COOKIE, COLUMN_PATH, String::class.java)
.insertColumn(TABLE_COOKIE, COLUMN_SECURE, Boolean::class.javaPrimitiveType)
.insertColumn(TABLE_COOKIE, COLUMN_HTTP_ONLY, Boolean::class.javaPrimitiveType)
.insertColumn(TABLE_COOKIE, COLUMN_PERSISTENT, Boolean::class.javaPrimitiveType)
.insertColumn(TABLE_COOKIE, COLUMN_HOST_ONLY, Boolean::class.javaPrimitiveType)
.build(context, name, DB_VERSION)
db = helper.writableDatabase
}
}
@@ -1,195 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
/*
* Created by Hippo on 2017/9/4.
*/
import android.content.Context;
import androidx.annotation.NonNull;
import com.hippo.yorozuya.ObjectUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class CookieRepository implements CookieJar {
/**
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
* of Android's private InetAddress#isNumeric API.
* <p>
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
* including dots after the first colon. It matches IPv4 addresses as strings containing only
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
* verification).
*/
private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
private final CookieDatabase db;
private final Map<String, CookieSet> map;
public CookieRepository(Context context, String name) {
db = new CookieDatabase(context, name);
map = db.getAllCookies();
}
/**
* Returns true if `host` is not a host name and might be an IP address.
*/
private static boolean verifyAsIpAddress(String host) {
return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
}
// okhttp3.Cookie.domainMatch(HttpUrl, String)
protected static boolean domainMatch(HttpUrl url, String domain) {
String urlHost = url.host();
if (urlHost.equals(domain)) {
return true; // As in 'example.com' matching 'example.com'.
}
return urlHost.endsWith(domain)
&& urlHost.charAt(urlHost.length() - domain.length() - 1) == '.'
&& !verifyAsIpAddress(urlHost); // As in 'example.com' matching 'www.example.com'.
}
public synchronized void addCookie(Cookie cookie) {
// For cookie database
Cookie toAdd = null;
Cookie toUpdate = null;
Cookie toRemove = null;
CookieSet set = map.get(cookie.domain());
if (set == null) {
set = new CookieSet();
map.put(cookie.domain(), set);
}
if (cookie.expiresAt() <= System.currentTimeMillis()) {
toRemove = set.remove(cookie);
// If the cookie is not persistent, it's not in database
if (toRemove != null && !toRemove.persistent()) {
toRemove = null;
}
} else {
toAdd = cookie;
toUpdate = set.add(cookie);
// If the cookie is not persistent, it's not in database
if (!toAdd.persistent()) toAdd = null;
if (toUpdate != null && !toUpdate.persistent()) toUpdate = null;
// Remove the cookie if it updates to null
if (toAdd == null && toUpdate != null) {
toRemove = toUpdate;
toUpdate = null;
}
}
if (toRemove != null) {
db.remove(toRemove);
}
if (toAdd != null) {
if (toUpdate != null) {
db.update(toUpdate, toAdd);
} else {
db.add(toAdd);
}
}
}
public String getCookieHeader(HttpUrl url) {
List<Cookie> cookies = getCookies(url);
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
public synchronized List<Cookie> getCookies(HttpUrl url) {
List<Cookie> accepted = new ArrayList<>();
List<Cookie> expired = new ArrayList<>();
for (Map.Entry<String, CookieSet> entry : map.entrySet()) {
String domain = entry.getKey();
CookieSet cookieSet = entry.getValue();
if (domainMatch(url, domain)) {
cookieSet.get(url, accepted, expired);
}
}
for (Cookie cookie : expired) {
if (cookie.persistent()) {
db.remove(cookie);
}
}
// RFC 6265 Section-5.4 step 2, sort the cookie-list
// Cookies with longer paths are listed before cookies with shorter paths.
// Ignore creation-time, we don't store them.
Collections.sort(accepted, (o1, o2) -> o2.path().length() - o1.path().length());
return accepted;
}
public boolean contains(HttpUrl url, String name) {
for (Cookie cookie : getCookies(url)) {
if (ObjectUtils.equal(cookie.name(), name)) {
return true;
}
}
return false;
}
/**
* Remove all cookies in this {@code CookieRepository}.
*/
public synchronized void clear() {
map.clear();
db.clear();
}
public synchronized void close() {
db.close();
}
@Override
public void saveFromResponse(@NonNull HttpUrl httpUrl, List<Cookie> list) {
for (Cookie cookie : list) {
addCookie(cookie);
}
}
@NonNull
@Override
public List<Cookie> loadForRequest(@NonNull HttpUrl httpUrl) {
return getCookies(httpUrl);
}
}
@@ -0,0 +1,181 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
import android.content.Context
import com.hippo.yorozuya.ObjectUtils
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import java.util.regex.Pattern
open class CookieRepository(context: Context, name: String) : CookieJar {
private val db: CookieDatabase
private val map: MutableMap<String, CookieSet>
@Synchronized
fun addCookie(cookie: Cookie) {
// For cookie database
var toAdd: Cookie? = null
var toUpdate: Cookie? = null
var toRemove: Cookie? = null
var set = map[cookie.domain]
if (set == null) {
set = CookieSet()
map[cookie.domain] = set
}
if (cookie.expiresAt <= System.currentTimeMillis()) {
toRemove = set.remove(cookie)
// If the cookie is not persistent, it's not in database
if (toRemove != null && !toRemove.persistent) {
toRemove = null
}
} else {
toAdd = cookie
toUpdate = set.add(cookie)
// If the cookie is not persistent, it's not in database
if (!toAdd.persistent) toAdd = null
if (toUpdate != null && !toUpdate.persistent) toUpdate = null
// Remove the cookie if it updates to null
if (toAdd == null && toUpdate != null) {
toRemove = toUpdate
toUpdate = null
}
}
if (toRemove != null) {
db.remove(toRemove)
}
if (toAdd != null) {
if (toUpdate != null) {
db.update(toUpdate, toAdd)
} else {
db.add(toAdd)
}
}
}
fun getCookieHeader(url: HttpUrl): String {
val cookies = getCookies(url)
val cookieHeader = StringBuilder()
var i = 0
val size = cookies.size
while (i < size) {
if (i > 0) {
cookieHeader.append("; ")
}
val cookie = cookies[i]
cookieHeader.append(cookie.name).append('=').append(cookie.value)
i++
}
return cookieHeader.toString()
}
@Synchronized
fun getCookies(url: HttpUrl): List<Cookie> {
val accepted: MutableList<Cookie> = ArrayList()
val expired: MutableList<Cookie> = ArrayList()
for ((domain, cookieSet) in map) {
if (domainMatch(url, domain)) {
cookieSet[url, accepted, expired]
}
}
for (cookie in expired) {
if (cookie.persistent) {
db.remove(cookie)
}
}
// RFC 6265 Section-5.4 step 2, sort the cookie-list
// Cookies with longer paths are listed before cookies with shorter paths.
// Ignore creation-time, we don't store them.
accepted.sortWith { o1: Cookie, o2: Cookie -> o2.path.length - o1.path.length }
return accepted
}
fun contains(url: HttpUrl, name: String?): Boolean {
for (cookie in getCookies(url)) {
if (ObjectUtils.equal(cookie.name, name)) {
return true
}
}
return false
}
/**
* Remove all cookies in this `CookieRepository`.
*/
@Synchronized
fun clear() {
map.clear()
db.clear()
}
@Synchronized
fun close() {
db.close()
}
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
for (cookie in cookies) {
addCookie(cookie)
}
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return getCookies(url)
}
companion object {
/**
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
* of Android's private InetAddress#isNumeric API.
*
*
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
* including dots after the first colon. It matches IPv4 addresses as strings containing only
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
* verification).
*/
private val VERIFY_AS_IP_ADDRESS =
Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)")
/**
* Returns true if `host` is not a host name and might be an IP address.
*/
private fun verifyAsIpAddress(host: String): Boolean {
return VERIFY_AS_IP_ADDRESS.matcher(host).matches()
}
// okhttp3.Cookie.domainMatch(HttpUrl, String)
@JvmStatic
protected fun domainMatch(url: HttpUrl, domain: String?): Boolean {
val urlHost = url.host
return if (urlHost == domain) {
true // As in 'example.com' matching 'example.com'.
} else urlHost.endsWith(domain!!)
&& urlHost[urlHost.length - domain.length - 1] == '.' && !verifyAsIpAddress(
urlHost
)
// As in 'example.com' matching 'www.example.com'.
}
}
init {
db = CookieDatabase(context, name)
map = db.allCookies
}
}
@@ -1,106 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
/*
* Created by Hippo on 2017/9/4.
*/
import com.hippo.util.HashCodeUtils;
import com.hippo.yorozuya.ObjectUtils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
class CookieSet {
private final Map<Key, Cookie> map = new HashMap<>();
/**
* Adds a cookie to this {@code CookieSet}.
* Returns a previous cookie with
* the same name, domain and path or {@code null}.
*/
public Cookie add(Cookie cookie) {
return map.put(new Key(cookie), cookie);
}
/**
* Removes a cookie with the same name,
* domain and path as the cookie.
* Returns the removed cookie or {@code null}.
*/
public Cookie remove(Cookie cookie) {
return map.remove(new Key(cookie));
}
/**
* Get cookies for the url. Fill {@code accepted} and {@code expired}.
*/
public void get(HttpUrl url, List<Cookie> accepted, List<Cookie> expired) {
long now = System.currentTimeMillis();
Iterator<Map.Entry<Key, Cookie>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Cookie cookie = iterator.next().getValue();
if (cookie.expiresAt() <= now) {
iterator.remove();
expired.add(cookie);
} else if (cookie.matches(url)) {
accepted.add(cookie);
}
}
}
static class Key {
private final String name;
private final String domain;
private final String path;
public Key(Cookie cookie) {
this.name = cookie.name();
this.domain = cookie.domain();
this.path = cookie.path();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Key) {
Key key = (Key) obj;
return ObjectUtils.equal(key.name, this.name) &&
ObjectUtils.equal(key.domain, this.domain) &&
ObjectUtils.equal(key.path, this.path);
}
return false;
}
@Override
public int hashCode() {
return HashCodeUtils.hashCode(name, domain, path);
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
import com.hippo.util.HashCodeUtils
import com.hippo.yorozuya.ObjectUtils
import okhttp3.Cookie
import okhttp3.HttpUrl
internal class CookieSet {
private val map: MutableMap<Key, Cookie> = HashMap()
/**
* Adds a cookie to this `CookieSet`.
* Returns a previous cookie with
* the same name, domain and path or `null`.
*/
fun add(cookie: Cookie): Cookie? {
return map.put(Key(cookie), cookie)
}
/**
* Removes a cookie with the same name,
* domain and path as the cookie.
* Returns the removed cookie or `null`.
*/
fun remove(cookie: Cookie): Cookie? {
return map.remove(Key(cookie))
}
/**
* Get cookies for the url. Fill `accepted` and `expired`.
*/
operator fun get(url: HttpUrl?, accepted: MutableList<Cookie>, expired: MutableList<Cookie>) {
val now = System.currentTimeMillis()
val iterator: MutableIterator<Map.Entry<Key, Cookie>> = map.entries.iterator()
while (iterator.hasNext()) {
val cookie = iterator.next().value
if (cookie.expiresAt <= now) {
iterator.remove()
expired.add(cookie)
} else if (cookie.matches(url!!)) {
accepted.add(cookie)
}
}
}
internal class Key(cookie: Cookie) {
private val name: String
private val domain: String
private val path: String
override fun equals(other: Any?): Boolean {
if (other === this) {
return true
}
if (other is Key) {
return ObjectUtils.equal(other.name, name) &&
ObjectUtils.equal(other.domain, domain) &&
ObjectUtils.equal(other.path, path)
}
return false
}
override fun hashCode(): Int {
return HashCodeUtils.hashCode(name, domain, path)
}
init {
name = cookie.name
domain = cookie.domain
path = cookie.path
}
}
}
@@ -1,67 +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.network;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class InetValidator {
private static final String IPV4_REGEX =
"^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$";
private static final Pattern IPV4_PATTERN = Pattern.compile(IPV4_REGEX);
private InetValidator() {
}
public static boolean isValidInet4Address(String inet4Address) {
if (null == inet4Address) {
return false;
}
Matcher matcher = IPV4_PATTERN.matcher(inet4Address);
if (!matcher.find()) {
return false;
}
// verify that address subgroups are legal
for (int i = 1; i <= 4; i++) {
String ipSegment = matcher.group(i);
if (ipSegment == null || ipSegment.length() == 0) {
return false;
}
int iIpSegment;
try {
iIpSegment = Integer.parseInt(ipSegment);
} catch (NumberFormatException e) {
return false;
}
if (iIpSegment > 255) {
return false;
}
if (ipSegment.length() > 1 && ipSegment.startsWith("0")) {
return false;
}
}
return true;
}
public static boolean isValidInetPort(int inetPort) {
return inetPort >= 0 && inetPort <= 65535;
}
}
@@ -0,0 +1,59 @@
/*
* 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.network
import java.util.regex.Pattern
object InetValidator {
private const val IPV4_REGEX = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"
private val IPV4_PATTERN = Pattern.compile(IPV4_REGEX)
@JvmStatic
fun isValidInet4Address(inet4Address: String?): Boolean {
if (null == inet4Address) {
return false
}
val matcher = IPV4_PATTERN.matcher(inet4Address)
if (!matcher.find()) {
return false
}
// verify that address subgroups are legal
for (i in 1..4) {
val ipSegment = matcher.group(i)
if (ipSegment == null || ipSegment.isEmpty()) {
return false
}
val iIpSegment: Int = try {
ipSegment.toInt()
} catch (e: NumberFormatException) {
return false
}
if (iIpSegment > 255) {
return false
}
if (ipSegment.length > 1 && ipSegment.startsWith("0")) {
return false
}
}
return true
}
@JvmStatic
fun isValidInetPort(inetPort: Int): Boolean {
return inetPort in 0..65535
}
}
@@ -1,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.network;
import android.content.Context;
import android.content.res.Resources;
import android.util.SparseArray;
import com.hippo.ehviewer.R;
public class StatusCodeException extends Exception {
private static final SparseArray<String> ERROR_MESSAGE_ARRAY = new SparseArray<>(24);
private static final String DEFAULT_ERROR_MESSAGE = "Error response code";
private final int mResponseCode;
private final String mMessage;
public StatusCodeException(int responseCode) {
mResponseCode = responseCode;
mMessage = ERROR_MESSAGE_ARRAY.get(responseCode, DEFAULT_ERROR_MESSAGE);
}
public StatusCodeException(int responseCode, String message) {
mResponseCode = responseCode;
mMessage = message;
}
public static void initialize(Context context) {
Resources resources = context.getApplicationContext().getResources();
ERROR_MESSAGE_ARRAY.append(400, resources.getString(R.string.error_status_code_400));
ERROR_MESSAGE_ARRAY.append(401, resources.getString(R.string.error_status_code_401));
ERROR_MESSAGE_ARRAY.append(402, resources.getString(R.string.error_status_code_402));
ERROR_MESSAGE_ARRAY.append(403, resources.getString(R.string.error_status_code_403));
ERROR_MESSAGE_ARRAY.append(404, resources.getString(R.string.error_status_code_404));
ERROR_MESSAGE_ARRAY.append(405, resources.getString(R.string.error_status_code_405));
ERROR_MESSAGE_ARRAY.append(406, resources.getString(R.string.error_status_code_406));
ERROR_MESSAGE_ARRAY.append(407, resources.getString(R.string.error_status_code_407));
ERROR_MESSAGE_ARRAY.append(408, resources.getString(R.string.error_status_code_408));
ERROR_MESSAGE_ARRAY.append(409, resources.getString(R.string.error_status_code_409));
ERROR_MESSAGE_ARRAY.append(410, resources.getString(R.string.error_status_code_410));
ERROR_MESSAGE_ARRAY.append(411, resources.getString(R.string.error_status_code_411));
ERROR_MESSAGE_ARRAY.append(412, resources.getString(R.string.error_status_code_412));
ERROR_MESSAGE_ARRAY.append(413, resources.getString(R.string.error_status_code_413));
ERROR_MESSAGE_ARRAY.append(414, resources.getString(R.string.error_status_code_414));
ERROR_MESSAGE_ARRAY.append(415, resources.getString(R.string.error_status_code_415));
ERROR_MESSAGE_ARRAY.append(416, resources.getString(R.string.error_status_code_416));
ERROR_MESSAGE_ARRAY.append(417, resources.getString(R.string.error_status_code_417));
ERROR_MESSAGE_ARRAY.append(500, resources.getString(R.string.error_status_code_500));
ERROR_MESSAGE_ARRAY.append(501, resources.getString(R.string.error_status_code_501));
ERROR_MESSAGE_ARRAY.append(502, resources.getString(R.string.error_status_code_502));
ERROR_MESSAGE_ARRAY.append(503, resources.getString(R.string.error_status_code_503));
ERROR_MESSAGE_ARRAY.append(504, resources.getString(R.string.error_status_code_504));
ERROR_MESSAGE_ARRAY.append(505, resources.getString(R.string.error_status_code_505));
}
public int getResponseCode() {
return mResponseCode;
}
public boolean isIdentifiedResponseCode() {
return !DEFAULT_ERROR_MESSAGE.equals(mMessage);
}
@Override
public String getMessage() {
return mMessage;
}
@Override
public String getLocalizedMessage() {
return getMessage();
}
}
@@ -0,0 +1,66 @@
/*
* 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.network
import android.content.Context
import android.util.SparseArray
import com.hippo.ehviewer.R
class StatusCodeException(val responseCode: Int) : Exception() {
override val message: String = ERROR_MESSAGE_ARRAY[responseCode, DEFAULT_ERROR_MESSAGE]
val isIdentifiedResponseCode: Boolean
get() = DEFAULT_ERROR_MESSAGE != message
override fun getLocalizedMessage(): String {
return message
}
companion object {
private val ERROR_MESSAGE_ARRAY = SparseArray<String>(24)
private const val DEFAULT_ERROR_MESSAGE = "Error response code"
@JvmStatic
fun initialize(context: Context) {
val resources = context.applicationContext.resources
ERROR_MESSAGE_ARRAY.append(400, resources.getString(R.string.error_status_code_400))
ERROR_MESSAGE_ARRAY.append(401, resources.getString(R.string.error_status_code_401))
ERROR_MESSAGE_ARRAY.append(402, resources.getString(R.string.error_status_code_402))
ERROR_MESSAGE_ARRAY.append(403, resources.getString(R.string.error_status_code_403))
ERROR_MESSAGE_ARRAY.append(404, resources.getString(R.string.error_status_code_404))
ERROR_MESSAGE_ARRAY.append(405, resources.getString(R.string.error_status_code_405))
ERROR_MESSAGE_ARRAY.append(406, resources.getString(R.string.error_status_code_406))
ERROR_MESSAGE_ARRAY.append(407, resources.getString(R.string.error_status_code_407))
ERROR_MESSAGE_ARRAY.append(408, resources.getString(R.string.error_status_code_408))
ERROR_MESSAGE_ARRAY.append(409, resources.getString(R.string.error_status_code_409))
ERROR_MESSAGE_ARRAY.append(410, resources.getString(R.string.error_status_code_410))
ERROR_MESSAGE_ARRAY.append(411, resources.getString(R.string.error_status_code_411))
ERROR_MESSAGE_ARRAY.append(412, resources.getString(R.string.error_status_code_412))
ERROR_MESSAGE_ARRAY.append(413, resources.getString(R.string.error_status_code_413))
ERROR_MESSAGE_ARRAY.append(414, resources.getString(R.string.error_status_code_414))
ERROR_MESSAGE_ARRAY.append(415, resources.getString(R.string.error_status_code_415))
ERROR_MESSAGE_ARRAY.append(416, resources.getString(R.string.error_status_code_416))
ERROR_MESSAGE_ARRAY.append(417, resources.getString(R.string.error_status_code_417))
ERROR_MESSAGE_ARRAY.append(500, resources.getString(R.string.error_status_code_500))
ERROR_MESSAGE_ARRAY.append(501, resources.getString(R.string.error_status_code_501))
ERROR_MESSAGE_ARRAY.append(502, resources.getString(R.string.error_status_code_502))
ERROR_MESSAGE_ARRAY.append(503, resources.getString(R.string.error_status_code_503))
ERROR_MESSAGE_ARRAY.append(504, resources.getString(R.string.error_status_code_504))
ERROR_MESSAGE_ARRAY.append(505, resources.getString(R.string.error_status_code_505))
}
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class UrlBuilder {
public String mRootUrl;
public Map<String, Object> mQueryMap = new HashMap<>();
public UrlBuilder(String rootUrl) {
mRootUrl = rootUrl;
}
public void addQuery(String key, Object value) {
mQueryMap.put(key, value);
}
public String build() {
if (mQueryMap.size() == 0) {
return mRootUrl;
} else {
StringBuilder sb = new StringBuilder(mRootUrl);
sb.append("?");
Iterator<String> iter = mQueryMap.keySet().iterator();
if (iter.hasNext()) {
String key = iter.next();
Object value = mQueryMap.get(key);
sb.append(key).append("=").append(value);
}
while (iter.hasNext()) {
String key = iter.next();
Object value = mQueryMap.get(key);
sb.append("&").append(key).append("=").append(value);
}
return sb.toString();
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
class UrlBuilder(private var mRootUrl: String) {
private var mQueryMap: MutableMap<String, Any> = HashMap()
fun addQuery(key: String, value: Any) {
mQueryMap[key] = value
}
fun build(): String {
return if (mQueryMap.isEmpty()) {
mRootUrl
} else {
val sb = StringBuilder(mRootUrl)
sb.append("?")
val iter: Iterator<String> = mQueryMap.keys.iterator()
if (iter.hasNext()) {
val key = iter.next()
val value = mQueryMap[key]
sb.append(key).append("=").append(value)
}
while (iter.hasNext()) {
val key = iter.next()
val value = mQueryMap[key]
sb.append("&").append(key).append("=").append(value)
}
sb.toString()
}
}
}
@@ -13,26 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.okhttp
package com.hippo.okhttp;
import okhttp3.Request
import okhttp3.Request;
public class ChromeRequestBuilder extends Request.Builder {
private static final String CHROME_USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36";
private static final String CHROME_ACCEPT =
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9";
private static final String CHROME_ACCEPT_LANGUAGE =
"en-US,en;q=0.5";
public ChromeRequestBuilder(String url) {
url(url);
addHeader("User-Agent", CHROME_USER_AGENT);
addHeader("Accept", CHROME_ACCEPT);
addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE);
open class ChromeRequestBuilder(url: String) : Request.Builder() {
companion object {
private const val CHROME_USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
private const val CHROME_ACCEPT =
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
private const val CHROME_ACCEPT_LANGUAGE = "en-US,en;q=0.5"
}
}
init {
this.url(url)
this.addHeader("User-Agent", CHROME_USER_AGENT)
this.addHeader("Accept", CHROME_ACCEPT)
this.addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE)
}
}
@@ -18,6 +18,7 @@ package com.hippo.preference;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.view.View;
@@ -26,7 +27,6 @@ import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
import com.hippo.text.Html;
public class MessagePreference extends DialogPreference {
@@ -52,7 +52,7 @@ public class MessagePreference extends DialogPreference {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MessagePreference, defStyleAttr, defStyleRes);
String message = a.getString(R.styleable.MessagePreference_dialogMessage);
if (a.getBoolean(R.styleable.MessagePreference_dialogMessageHtml, false)) {
mDialogMessage = Html.fromHtml(message);
mDialogMessage = Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY);
} else {
mDialogMessage = message;
}
@@ -1,27 +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.scene;
import android.content.Context;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
public interface TransitionHelper {
boolean onTransition(Context context, FragmentTransaction transaction, Fragment exit, Fragment enter);
}
File diff suppressed because it is too large. Load diff
@@ -17,6 +17,7 @@
package com.hippo.text;
import android.graphics.drawable.Drawable;
import android.text.Html;
import com.hippo.conaco.Conaco;
import com.hippo.drawable.UnikeryDrawable;
@@ -1,85 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
public abstract class BaseConfigChooser implements EGLConfigChooser {
private final int mEGLContextClientVersion;
public BaseConfigChooser(int eglContextClientVersion, int[] configSpec) {
mEGLContextClientVersion = eglContextClientVersion;
mConfigSpec = filterConfigSpec(configSpec);
}
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
final int[] num_config = new int[1];
if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
num_config)) {
throw new IllegalArgumentException("eglChooseConfig failed");
}
final int numConfigs = num_config[0];
if (numConfigs <= 0) {
throw new IllegalArgumentException(
"No configs match configSpec");
}
final EGLConfig[] configs = new EGLConfig[numConfigs];
if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
num_config)) {
throw new IllegalArgumentException("eglChooseConfig#2 failed");
}
final EGLConfig config = chooseConfig(egl, display, configs);
if (config == null) {
throw new IllegalArgumentException("No config chosen");
}
return config;
}
public abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
EGLConfig[] configs);
protected int[] mConfigSpec;
private int[] filterConfigSpec(int[] configSpec) {
if (mEGLContextClientVersion != 2 && mEGLContextClientVersion != 3) {
return configSpec;
}
/* We know none of the subclasses define EGL_RENDERABLE_TYPE.
* And we know the configSpec is well formed.
*/
final int len = configSpec.length;
final int[] newConfigSpec = new int[len + 2];
System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);
newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;
if (mEGLContextClientVersion == 2) {
newConfigSpec[len] = 0x0004; /* EGL_OPENGL_ES2_BIT */
} else {
newConfigSpec[len] = 0x0040; /* EGL_OPENGL_ES3_BIT_KHR */
}
newConfigSpec[len+1] = EGL10.EGL_NONE;
return newConfigSpec;
}
}
@@ -1,94 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
/**
* Choose a configuration with exactly the specified r,g,b,a sizes,
* and at least the specified depth and stencil sizes.
*/
public class ComponentSizeChooser extends BaseConfigChooser {
public ComponentSizeChooser(int eglContextClientVersion,
int redSize, int greenSize, int blueSize,
int alphaSize, int depthSize, int stencilSize) {
super(eglContextClientVersion, new int[] {
EGL10.EGL_RED_SIZE, redSize,
EGL10.EGL_GREEN_SIZE, greenSize,
EGL10.EGL_BLUE_SIZE, blueSize,
EGL10.EGL_ALPHA_SIZE, alphaSize,
EGL10.EGL_DEPTH_SIZE, depthSize,
EGL10.EGL_STENCIL_SIZE, stencilSize,
EGL10.EGL_NONE});
mValue = new int[1];
mRedSize = redSize;
mGreenSize = greenSize;
mBlueSize = blueSize;
mAlphaSize = alphaSize;
mDepthSize = depthSize;
mStencilSize = stencilSize;
}
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
EGLConfig[] configs) {
for (final EGLConfig config : configs) {
final int d = findConfigAttrib(egl, display, config,
EGL10.EGL_DEPTH_SIZE, 0);
final int s = findConfigAttrib(egl, display, config,
EGL10.EGL_STENCIL_SIZE, 0);
if ((d >= mDepthSize) && (s >= mStencilSize)) {
final int r = findConfigAttrib(egl, display, config,
EGL10.EGL_RED_SIZE, 0);
final int g = findConfigAttrib(egl, display, config,
EGL10.EGL_GREEN_SIZE, 0);
final int b = findConfigAttrib(egl, display, config,
EGL10.EGL_BLUE_SIZE, 0);
final int a = findConfigAttrib(egl, display, config,
EGL10.EGL_ALPHA_SIZE, 0);
if ((r == mRedSize) && (g == mGreenSize)
&& (b == mBlueSize) && (a == mAlphaSize)) {
return config;
}
}
}
return null;
}
private int findConfigAttrib(EGL10 egl, EGLDisplay display,
EGLConfig config, int attribute, int defaultValue) {
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
return mValue[0];
}
return defaultValue;
}
private final int[] mValue;
// Subclasses can adjust these values:
protected int mRedSize;
protected int mGreenSize;
protected int mBlueSize;
protected int mAlphaSize;
protected int mDepthSize;
protected int mStencilSize;
}
@@ -1,58 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.util.Log;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
public class DefaultContextFactory implements EGLContextFactory {
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private final int mEGLContextClientVersion;
public DefaultContextFactory(int eglContextClientVersion) {
mEGLContextClientVersion = eglContextClientVersion;
}
@Override
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
final int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
EGL10.EGL_NONE };
return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
mEGLContextClientVersion != 0 ? attrib_list : null);
}
@Override
public void destroyContext(EGL10 egl, EGLDisplay display,
EGLContext context) {
if (!egl.eglDestroyContext(display, context)) {
Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
if (GLStuff.LOG_THREADS) {
Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId());
}
EglHelper.throwEglException("eglDestroyContex", egl.eglGetError());
}
}
}
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.util.Log;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
// android-9.0.0_r30
public class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory {
@Override
public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,
EGLConfig config, Object nativeWindow) {
EGLSurface result = null;
try {
result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
} catch (IllegalArgumentException e) {
// This exception indicates that the surface flinger surface
// is not valid. This can happen if the surface flinger surface has
// been torn down, but the application has not yet been
// notified via SurfaceHolder.Callback.surfaceDestroyed.
// In theory the application should be notified first,
// but in practice sometimes it is not. See b/4588890
Log.e("DefWinSurfaceFactory", "eglCreateWindowSurface", e);
}
return result;
}
@Override
public void destroySurface(EGL10 egl, EGLDisplay display,
EGLSurface surface) {
egl.eglDestroySurface(display, surface);
}
}
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
/**
* An interface for choosing an EGLConfig configuration from a list of
* potential configurations.
* <p>
* This interface must be implemented by clients wishing to call
* {@link GLStuff#setEGLConfigChooser(EGLConfigChooser)}
*/
public interface EGLConfigChooser {
/**
* Choose a configuration from the list. Implementors typically
* implement this method by calling
* {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the
* EGL specification available from The Khronos Group to learn how to call eglChooseConfig.
* @param egl the EGL10 for the current display.
* @param display the current display.
* @return the chosen configuration.
*/
EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* An interface for customizing the eglCreateContext and eglDestroyContext calls.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
/**
* An interface for customizing the eglCreateContext and eglDestroyContext calls.
* <p>
* This interface must be implemented by clients wishing to call
* {@link GLStuff#setEGLContextFactory(EGLContextFactory)}
*/
public interface EGLContextFactory {
EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig);
void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context);
}
@@ -1,39 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
// android-9.0.0_r30
/**
* An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls.
* <p>
* This interface must be implemented by clients wishing to call
* {@link GLStuff#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)}
*/
public interface EGLWindowSurfaceFactory {
/**
* @return null if the surface cannot be constructed.
*/
EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config,
Object nativeWindow);
void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface);
}
@@ -1,298 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.opengl.GLDebugHelper;
import android.opengl.GLUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.Writer;
import java.lang.ref.WeakReference;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
// android-9.0.0_r30
/**
* An EGL helper class.
*/
class EglHelper {
public EglHelper(WeakReference<GLStuff> glStuffWeakRef) {
mGLStuffViewWeakRef = glStuffWeakRef;
}
/**
* Initialize EGL for a given configuration spec.
*/
public void start() {
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
}
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
/*
* We can now initialize EGL for that display
*/
final int[] version = new int[2];
if(!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff == null) {
mEglConfig = null;
mEglContext = null;
} else {
mEglConfig = stuff.getEGLConfigChooser().chooseConfig(mEgl, mEglDisplay);
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = stuff.getEGLContextFactory().createContext(mEgl, mEglDisplay, mEglConfig);
}
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
mEglContext = null;
throwEglException("createContext");
}
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
}
mEglSurface = null;
}
/**
* Create an egl surface for the current SurfaceHolder surface. If a surface
* already exists, destroy it before creating the new surface.
*
* @return true if the surface was created successfully.
*/
public boolean createSurface() {
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
}
/*
* Check preconditions.
*/
if (mEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (mEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (mEglConfig == null) {
throw new RuntimeException("mEglConfig not initialized");
}
/*
* The window size has changed, so we need to create a new
* surface.
*/
destroySurfaceImp();
/*
* Create an EGL surface we can render into.
*/
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff != null) {
mEglSurface = stuff.getEGLWindowSurfaceFactory().createWindowSurface(mEgl,
mEglDisplay, mEglConfig, stuff.getNativeWindow());
} else {
mEglSurface = null;
}
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
final int error = mEgl.eglGetError();
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
}
return false;
}
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying
* SurfaceView surface has been destroyed.
*/
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
return true;
}
/**
* Create a GL object for the current EGL context.
*/
GL createGL() {
GL gl = mEglContext.getGL();
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff != null) {
final GLWrapper glWrapper = stuff.getGLWrapper();
if (glWrapper != null) {
gl = glWrapper.wrap(gl);
}
final int debugFlags = stuff.getDebugFlags();
if ((debugFlags & (GLStuff.DEBUG_CHECK_GL_ERROR | GLStuff.DEBUG_LOG_GL_CALLS)) != 0) {
int configFlags = 0;
Writer log = null;
if ((debugFlags & GLStuff.DEBUG_CHECK_GL_ERROR) != 0) {
configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR;
}
if ((debugFlags & GLStuff.DEBUG_LOG_GL_CALLS) != 0) {
log = new LogWriter();
}
gl = GLDebugHelper.wrap(gl, configFlags, log);
}
}
return gl;
}
/**
* Display the current render surface.
* @return the EGL error code from eglSwapBuffers.
*/
public int swap() {
if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError();
}
return EGL10.EGL_SUCCESS;
}
public void destroySurface() {
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId());
}
destroySurfaceImp();
}
private void destroySurfaceImp() {
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT);
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff != null) {
stuff.getEGLWindowSurfaceFactory().destroySurface(mEgl, mEglDisplay, mEglSurface);
}
mEglSurface = null;
}
}
public void finish() {
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId());
}
if (mEglContext != null) {
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff != null) {
stuff.getEGLContextFactory().destroyContext(mEgl, mEglDisplay, mEglContext);
}
mEglContext = null;
}
if (mEglDisplay != null) {
mEgl.eglTerminate(mEglDisplay);
mEglDisplay = null;
}
}
private void throwEglException(String function) {
throwEglException(function, mEgl.eglGetError());
}
public static void throwEglException(String function, int error) {
final String message = formatEglError(function, error);
if (GLStuff.LOG_THREADS) {
Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " "
+ message);
}
throw new RuntimeException(message);
}
public static void logEglErrorAsWarning(String tag, String function, int error) {
Log.w(tag, formatEglError(function, error));
}
public static String formatEglError(String function, int error) {
return function + " failed: " + GLUtils.getEGLErrorString(error);
}
private final WeakReference<GLStuff> mGLStuffViewWeakRef;
EGL10 mEgl;
EGLDisplay mEglDisplay;
EGLSurface mEglSurface;
EGLConfig mEglConfig;
EGLContext mEglContext;
static class LogWriter extends Writer {
@Override public void close() {
flushBuilder();
}
@Override public void flush() {
flushBuilder();
}
@Override public void write(@NonNull char[] buf, int offset, int count) {
for(int i = 0; i < count; i++) {
final char c = buf[offset + i];
if ( c == '\n') {
flushBuilder();
}
else {
mBuilder.append(c);
}
}
}
private void flushBuilder() {
if (mBuilder.length() > 0) {
Log.v("GLSurfaceView", mBuilder.toString());
mBuilder.delete(0, mBuilder.length());
}
}
private final StringBuilder mBuilder = new StringBuilder();
}
}
@@ -1,320 +0,0 @@
/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.opengles.GL;
// android-9.0.0_r30
public interface GLStuff {
boolean LOG_ATTACH_DETACH = false;
boolean LOG_THREADS = false;
boolean LOG_PAUSE_RESUME = false;
boolean LOG_SURFACE = false;
boolean LOG_RENDERER = false;
boolean LOG_RENDERER_DRAW_FRAME = false;
boolean LOG_EGL = false;
/**
* The renderer only renders
* when the surface is created, or when {@link #requestRender} is called.
*
* @see #getRenderMode()
* @see #setRenderMode(int)
* @see #requestRender()
*/
int RENDERMODE_WHEN_DIRTY = 0;
/**
* The renderer is called
* continuously to re-render the scene.
*
* @see #getRenderMode()
* @see #setRenderMode(int)
*/
int RENDERMODE_CONTINUOUSLY = 1;
/**
* Check glError() after every GL call and throw an exception if glError indicates
* that an error has occurred. This can be used to help track down which OpenGL ES call
* is causing an error.
*
* @see #getDebugFlags
* @see #setDebugFlags
*/
int DEBUG_CHECK_GL_ERROR = 1;
/**
* Log GL calls to the system log at "verbose" level with tag "GLSurfaceView".
*
* @see #getDebugFlags
* @see #setDebugFlags
*/
int DEBUG_LOG_GL_CALLS = 2;
/**
* Get the native window object
*/
Object getNativeWindow();
/**
* Set the glWrapper. If the glWrapper is not null, its
* {@link GLWrapper#wrap(GL)} method is called
* whenever a surface is created. A GLWrapper can be used to wrap
* the GL object that's passed to the renderer. Wrapping a GL
* object enables examining and modifying the behavior of the
* GL calls made by the renderer.
* <p>
* Wrapping is typically used for debugging purposes.
* <p>
* The default value is null.
* @param glWrapper the new GLWrapper
*/
void setGLWrapper(GLWrapper glWrapper);
/**
* @return Returns the {@code GLWrapper}
*/
GLWrapper getGLWrapper();
/**
* Set the debug flags to a new value. The value is
* constructed by OR-together zero or more
* of the DEBUG_CHECK_* constants. The debug flags take effect
* whenever a surface is created. The default value is zero.
* @param debugFlags the new debug flags
* @see #DEBUG_CHECK_GL_ERROR
* @see #DEBUG_LOG_GL_CALLS
*/
void setDebugFlags(int debugFlags);
/**
* Get the current value of the debug flags.
* @return the current value of the debug flags.
*/
int getDebugFlags();
/**
* Control whether the EGL context is preserved when the GLSurfaceView is paused and
* resumed.
* <p>
* If set to true, then the EGL context may be preserved when the GLSurfaceView is paused.
* <p>
* Prior to API level 11, whether the EGL context is actually preserved or not
* depends upon whether the Android device can support an arbitrary number of
* EGL contexts or not. Devices that can only support a limited number of EGL
* contexts must release the EGL context in order to allow multiple applications
* to share the GPU.
* <p>
* If set to false, the EGL context will be released when the GLSurfaceView is paused,
* and recreated when the GLSurfaceView is resumed.
* <p>
*
* The default is false.
*
* @param preserveOnPause preserve the EGL context when paused
*/
void setPreserveEGLContextOnPause(boolean preserveOnPause);
/**
* @return true if the EGL context will be preserved when paused
*/
boolean getPreserveEGLContextOnPause();
/**
* Set the renderer associated with this view. Also starts the thread that
* will call the renderer, which in turn causes the rendering to start.
* <p>This method should be called once and only once in the life-cycle of
* a GLSurfaceView.
* <p>The following GLSurfaceView methods can only be called <em>before</em>
* setRenderer is called:
* <ul>
* <li>{@link #setEGLConfigChooser(EGLConfigChooser)}
* </ul>
* <p>
* The following GLSurfaceView methods can only be called <em>after</em>
* setRenderer is called:
* <ul>
* <li>{@link #getRenderMode()}
* <li>{@link #onPause()}
* <li>{@link #onResume()}
* <li>{@link #queueEvent(Runnable)}
* <li>{@link #requestRender()}
* <li>{@link #setRenderMode(int)}
* </ul>
*
* @param renderer the renderer to use to perform OpenGL drawing.
*/
void setRenderer(Renderer renderer);
/**
* @return Returns the {@code Renderer}
*/
Renderer getRenderer();
/**
* Install a custom EGLContextFactory.
* <p>If this method is
* called, it must be called before {@link #setRenderer(Renderer)}
* is called.
* <p>
* If this method is not called, then by default
* a context will be created with no shared context and
* with a null attribute list.
*/
void setEGLContextFactory(EGLContextFactory factory);
/**
* @return Returns the {@code EGLWindowSurfaceFactory}
*/
EGLWindowSurfaceFactory getEGLWindowSurfaceFactory();
/**
* Install a custom EGLWindowSurfaceFactory.
* <p>If this method is
* called, it must be called before {@link #setRenderer(Renderer)}
* is called.
* <p>
* If this method is not called, then by default
* a window surface will be created with a null attribute list.
*/
void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory);
/**
* @return Returns the {@code EGLContextFactory}
*/
EGLContextFactory getEGLContextFactory();
/**
* Install a custom EGLConfigChooser.
* <p>If this method is
* called, it must be called before {@link #setRenderer(Renderer)}
* is called.
* <p>
* If no setEGLConfigChooser method is called, then by default the
* view will choose an EGLConfig that is compatible with the current
* android.view.Surface, with a depth buffer depth of
* at least 16 bits.
*/
void setEGLConfigChooser(EGLConfigChooser configChooser);
/**
* @return Returns the {@code EGLConfigChooser}
*/
EGLConfigChooser getEGLConfigChooser();
/**
* Inform the default EGLContextFactory and default EGLConfigChooser
* which EGLContext client version to pick.
* <p>Use this method to create an OpenGL ES 2.0-compatible context.
* Example:
* <pre class="prettyprint">
* public MyView(Context context) {
* super(context);
* setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
* setRenderer(new MyRenderer());
* }
* </pre>
* <p>Note: Activities which require OpenGL ES 2.0 should indicate this by
* setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's
* AndroidManifest.xml file.
* <p>If this method is called, it must be called before {@link #setRenderer(Renderer)}
* is called.
* <p>This method only affects the behavior of the default EGLContexFactory and the
* default EGLConfigChooser. If
* {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied
* EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context.
* If
* {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied
* EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config.
* @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0
*/
void setEGLContextClientVersion(int version);
/**
* Get EGLContextClientVersion
*/
int getEGLContextClientVersion();
/**
* Set the rendering mode. When renderMode is
* RENDERMODE_CONTINUOUSLY, the renderer is called
* repeatedly to re-render the scene. When renderMode
* is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
* is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
* <p>
* Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance
* by allowing the GPU and CPU to idle when the view does not need to be updated.
* <p>
* This method can only be called after {@link #setRenderer(Renderer)}
*
* @param renderMode one of the RENDERMODE_X constants
* @see #RENDERMODE_CONTINUOUSLY
* @see #RENDERMODE_WHEN_DIRTY
*/
void setRenderMode(int renderMode);
/**
* Get the current rendering mode. May be called
* from any thread. Must not be called before a renderer has been set.
* @return the current rendering mode.
* @see #RENDERMODE_CONTINUOUSLY
* @see #RENDERMODE_WHEN_DIRTY
*/
int getRenderMode();
/**
* Request that the renderer render a frame.
* This method is typically used when the render mode has been set to
* {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand.
* May be called
* from any thread. Must not be called before a renderer has been set.
*/
void requestRender();
/**
* Pause the rendering thread, optionally tearing down the EGL context
* depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}.
*
* This method should be called when it is no longer desirable for the
* GLSurfaceView to continue rendering, such as in response to
* {@link android.app.Activity#onStop Activity.onStop}.
*
* Must not be called before a renderer has been set.
*/
void onPause();
/**
* Resumes the rendering thread, re-creating the OpenGL context if necessary. It
* is the counterpart to {@link #onPause()}.
*
* This method should typically be called in
* {@link android.app.Activity#onStart Activity.onStart}.
*
* Must not be called before a renderer has been set.
*/
void onResume();
/**
* Queue a runnable to be run on the GL rendering thread. This can be used
* to communicate with the Renderer on the rendering thread.
* Must not be called before a renderer has been set.
* @param r the runnable to be run on the GL rendering thread.
*/
void queueEvent(Runnable r);
}
@@ -1,434 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.lang.ref.WeakReference;
// android-9.0.0_r30
/**
* An implementation of SurfaceView that uses the dedicated surface for
* displaying OpenGL rendering.
* <p>
* A GLSurfaceView provides the following features:
* <p>
* <ul>
* <li>Manages a surface, which is a special piece of memory that can be
* composited into the Android view system.
* <li>Manages an EGL display, which enables OpenGL to render into a surface.
* <li>Accepts a user-provided Renderer object that does the actual rendering.
* <li>Renders on a dedicated thread to decouple rendering performance from the
* UI thread.
* <li>Supports both on-demand and continuous rendering.
* <li>Optionally wraps, traces, and/or error-checks the renderer's OpenGL calls.
* </ul>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about how to use OpenGL, read the
* <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p>
* </div>
*
* <h3>Using GLSurfaceView</h3>
* <p>
* Typically you use GLSurfaceView by subclassing it and overriding one or more of the
* View system input event methods. If your application does not need to override event
* methods then GLSurfaceView can be used as-is. For the most part
* GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing.
* For example, unlike a regular View, drawing is delegated to a separate Renderer object which
* is registered with the GLSurfaceView
* using the {@link #setRenderer(Renderer)} call.
* <p>
* <h3>Initializing GLSurfaceView</h3>
* All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}.
* However, if desired, you can modify the default behavior of GLSurfaceView by calling one or
* more of these methods before calling setRenderer:
* <ul>
* <li>{@link #setDebugFlags(int)}
* <li>{@link #setEGLConfigChooser(EGLConfigChooser)}
* <li>{@link #setGLWrapper(GLWrapper)}
* </ul>
* <p>
* <h4>Specifying the android.view.Surface</h4>
* By default GLSurfaceView will create a PixelFormat.RGB_888 format surface. If a translucent
* surface is required, call getHolder().setFormat(PixelFormat.TRANSLUCENT).
* The exact format of a TRANSLUCENT surface is device dependent, but it will be
* a 32-bit-per-pixel surface with 8 bits per component.
* <p>
* <h4>Choosing an EGL Configuration</h4>
* A given Android device may support multiple EGLConfig rendering configurations.
* The available configurations may differ in how many channels of data are present, as
* well as how many bits are allocated to each channel. Therefore, the first thing
* GLSurfaceView has to do when starting to render is choose what EGLConfig to use.
* <p>
* By default GLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format,
* with at least a 16-bit depth buffer and no stencil.
* <p>
* If you would prefer a different EGLConfig
* you can override the default behavior by calling one of the
* setEGLConfigChooser methods.
* <p>
* <h4>Debug Behavior</h4>
* You can optionally modify the behavior of GLSurfaceView by calling
* one or more of the debugging methods {@link #setDebugFlags(int)},
* and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but
* typically they are called before setRenderer so that they take effect immediately.
* <p>
* <h4>Setting a Renderer</h4>
* Finally, you must call {@link #setRenderer} to register a {@link Renderer}.
* The renderer is
* responsible for doing the actual OpenGL rendering.
* <p>
* <h3>Rendering Mode</h3>
* Once the renderer is set, you can control whether the renderer draws
* continuously or on-demand by calling
* {@link #setRenderMode}. The default is continuous rendering.
* <p>
* <h3>Activity Life-cycle</h3>
* A GLSurfaceView must be notified when to pause and resume rendering. GLSurfaceView clients
* are required to call {@link #onPause()} when the activity stops and
* {@link #onResume()} when the activity starts. These calls allow GLSurfaceView to
* pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate
* the OpenGL display.
* <p>
* <h3>Handling events</h3>
* <p>
* To handle an event you will typically subclass GLSurfaceView and override the
* appropriate method, just as you would with any other View. However, when handling
* the event, you may need to communicate with the Renderer object
* that's running in the rendering thread. You can do this using any
* standard Java cross-thread communication mechanism. In addition,
* one relatively easy way to communicate with your renderer is
* to call
* {@link #queueEvent(Runnable)}. For example:
* <pre class="prettyprint">
* class MyGLSurfaceView extends GLSurfaceView {
*
* private MyRenderer mMyRenderer;
*
* public void start() {
* mMyRenderer = ...;
* setRenderer(mMyRenderer);
* }
*
* public boolean onKeyDown(int keyCode, KeyEvent event) {
* if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
* queueEvent(new Runnable() {
* // This method will be called on the rendering
* // thread:
* public void run() {
* mMyRenderer.handleDpadCenter();
* }});
* return true;
* }
* return super.onKeyDown(keyCode, event);
* }
* }
* </pre>
*
*/
public class GLSurfaceView extends SurfaceView implements GLStuff, SurfaceHolder.Callback2 {
private final static String TAG = "GLSurfaceView";
/**
* Standard View constructor. In order to render something, you
* must call {@link #setRenderer} to register a renderer.
*/
public GLSurfaceView(Context context) {
super(context);
init();
}
/**
* Standard View constructor. In order to render something, you
* must call {@link #setRenderer} to register a renderer.
*/
public GLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
protected void finalize() throws Throwable {
try {
if (mGLThread != null) {
// GLThread may still be running if this view was never
// attached to a window.
mGLThread.requestExitAndWait();
}
} finally {
super.finalize();
}
}
private void init() {
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment
// this statement if back-porting to 2.2 or older:
// holder.setFormat(PixelFormat.RGB_565);
//
// setType is not needed for SDK 2.0 or newer. Uncomment this
// statement if back-porting this code to older SDKs.
// holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
}
@Override
public Object getNativeWindow() {
return getHolder();
}
@Override
public void setGLWrapper(GLWrapper glWrapper) {
mGLWrapper = glWrapper;
}
@Override
public GLWrapper getGLWrapper() {
return mGLWrapper;
}
@Override
public void setDebugFlags(int debugFlags) {
mDebugFlags = debugFlags;
}
@Override
public int getDebugFlags() {
return mDebugFlags;
}
@Override
public void setPreserveEGLContextOnPause(boolean preserveOnPause) {
mPreserveEGLContextOnPause = preserveOnPause;
}
@Override
public boolean getPreserveEGLContextOnPause() {
return mPreserveEGLContextOnPause;
}
private void checkRenderThreadState() {
if (mGLThread != null) {
throw new IllegalStateException(
"setRenderer has already been called for this instance.");
}
}
@Override
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(mEGLContextClientVersion, true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion);
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
@Override
public Renderer getRenderer() {
return mRenderer;
}
@Override
public void setEGLContextFactory(EGLContextFactory factory) {
checkRenderThreadState();
mEGLContextFactory = factory;
}
@Override
public EGLContextFactory getEGLContextFactory() {
return mEGLContextFactory;
}
@Override
public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {
checkRenderThreadState();
mEGLWindowSurfaceFactory = factory;
}
@Override
public EGLWindowSurfaceFactory getEGLWindowSurfaceFactory() {
return mEGLWindowSurfaceFactory;
}
@Override
public void setEGLConfigChooser(EGLConfigChooser configChooser) {
checkRenderThreadState();
mEGLConfigChooser = configChooser;
}
@Override
public EGLConfigChooser getEGLConfigChooser() {
return mEGLConfigChooser;
}
@Override
public void setEGLContextClientVersion(int version) {
checkRenderThreadState();
mEGLContextClientVersion = version;
}
@Override
public int getEGLContextClientVersion() {
return mEGLContextClientVersion;
}
@Override
public void setRenderMode(int renderMode) {
mGLThread.setRenderMode(renderMode);
}
@Override
public int getRenderMode() {
return mGLThread.getRenderMode();
}
@Override
public void requestRender() {
mGLThread.requestRender();
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
mGLThread.surfaceCreated();
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return
mGLThread.surfaceDestroyed();
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
mGLThread.onWindowResize(w, h);
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) {
if (mGLThread != null) {
mGLThread.requestRenderAndNotify(finishDrawing);
}
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
if (mGLThread != null) {
mGLThread.requestRenderAndWait();
}
}
@Override
public void onPause() {
mGLThread.onPause();
}
@Override
public void onResume() {
mGLThread.onResume();
}
@Override
public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}
/**
* This method is used as part of the View class and is not normally
* called or subclassed by clients of GLSurfaceView.
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
}
if (mDetached && (mRenderer != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (mGLThread != null) {
renderMode = mGLThread.getRenderMode();
}
mGLThread = new GLThread(mThisWeakRef);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
mGLThread.setRenderMode(renderMode);
}
mGLThread.start();
}
mDetached = false;
}
@Override
protected void onDetachedFromWindow() {
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onDetachedFromWindow");
}
if (mGLThread != null) {
mGLThread.requestExitAndWait();
}
mDetached = true;
super.onDetachedFromWindow();
}
private final WeakReference<GLStuff> mThisWeakRef =
new WeakReference<>((GLStuff) this);
private GLThread mGLThread;
private Renderer mRenderer;
private boolean mDetached;
private EGLConfigChooser mEGLConfigChooser;
private EGLContextFactory mEGLContextFactory;
private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
private GLWrapper mGLWrapper;
private int mDebugFlags;
private int mEGLContextClientVersion;
private boolean mPreserveEGLContextOnPause;
}
@@ -1,292 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.tuxiang;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.util.AttributeSet;
import android.util.Log;
import android.view.TextureView;
import java.lang.ref.WeakReference;
/**
* TextureView version {@link GLSurfaceView}
*/
public class GLTextureView extends TextureView implements GLStuff, TextureView.SurfaceTextureListener {
private final static String TAG = "GLSurfaceView";
public GLTextureView(Context context) {
super(context);
init();
}
public GLTextureView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GLTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void finalize() throws Throwable {
try {
if (mGLThread != null) {
// GLThread may still be running if this view was never
// attached to a window.
mGLThread.requestExitAndWait();
}
} finally {
super.finalize();
}
}
private void init() {
setSurfaceTextureListener(this);
}
@Override
public Object getNativeWindow() {
return getSurfaceTexture();
}
@Override
public void setGLWrapper(GLWrapper glWrapper) {
mGLWrapper = glWrapper;
}
@Override
public GLWrapper getGLWrapper() {
return mGLWrapper;
}
@Override
public void setDebugFlags(int debugFlags) {
mDebugFlags = debugFlags;
}
@Override
public int getDebugFlags() {
return mDebugFlags;
}
@Override
public void setPreserveEGLContextOnPause(boolean preserveOnPause) {
mPreserveEGLContextOnPause = preserveOnPause;
}
@Override
public boolean getPreserveEGLContextOnPause() {
return mPreserveEGLContextOnPause;
}
private void checkRenderThreadState() {
if (mGLThread != null) {
throw new IllegalStateException(
"setRenderer has already been called for this instance.");
}
}
@Override
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(mEGLContextClientVersion, true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion);
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
@Override
public Renderer getRenderer() {
return mRenderer;
}
@Override
public void setEGLContextFactory(EGLContextFactory factory) {
checkRenderThreadState();
mEGLContextFactory = factory;
}
@Override
public EGLContextFactory getEGLContextFactory() {
return mEGLContextFactory;
}
@Override
public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {
checkRenderThreadState();
mEGLWindowSurfaceFactory = factory;
}
@Override
public EGLWindowSurfaceFactory getEGLWindowSurfaceFactory() {
return mEGLWindowSurfaceFactory;
}
@Override
public void setEGLConfigChooser(EGLConfigChooser configChooser) {
checkRenderThreadState();
mEGLConfigChooser = configChooser;
}
@Override
public EGLConfigChooser getEGLConfigChooser() {
return mEGLConfigChooser;
}
@Override
public void setEGLContextClientVersion(int version) {
checkRenderThreadState();
mEGLContextClientVersion = version;
}
@Override
public int getEGLContextClientVersion() {
return mEGLContextClientVersion;
}
@Override
public void setRenderMode(int renderMode) {
mGLThread.setRenderMode(renderMode);
}
@Override
public int getRenderMode() {
return mGLThread.getRenderMode();
}
@Override
public void requestRender() {
mGLThread.requestRender();
}
/**
* This method is part of the {@link android.view.TextureView.SurfaceTextureListener}
* interface, and is not normally called or subclassed by clients of GLTextureView.
*/
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mGLThread.surfaceCreated();
mGLThread.onWindowResize(width, height);
}
/**
* This method is part of the {@link android.view.TextureView.SurfaceTextureListener}
* interface, and is not normally called or subclassed by clients of GLTextureView.
*/
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
mGLThread.onWindowResize(width, height);
}
/**
* This method is part of the {@link android.view.TextureView.SurfaceTextureListener}
* interface, and is not normally called or subclassed by clients of GLTextureView.
*/
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// Surface will be destroyed when we return
mGLThread.surfaceDestroyed();
return true;
}
/**
* This method is part of the {@link android.view.TextureView.SurfaceTextureListener}
* interface, and is not normally called or subclassed by clients of GLTextureView.
*/
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
requestRender();
}
@Override
public void onPause() {
mGLThread.onPause();
}
@Override
public void onResume() {
mGLThread.onResume();
}
@Override
public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}
/**
* This method is used as part of the View class and is not normally
* called or subclassed by clients of GLTextureView.
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
}
if (mDetached && (mRenderer != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (mGLThread != null) {
renderMode = mGLThread.getRenderMode();
}
mGLThread = new GLThread(mThisWeakRef);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
mGLThread.setRenderMode(renderMode);
}
mGLThread.start();
}
mDetached = false;
}
@Override
protected void onDetachedFromWindow() {
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onDetachedFromWindow");
}
if (mGLThread != null) {
mGLThread.requestExitAndWait();
}
mDetached = true;
super.onDetachedFromWindow();
}
private final WeakReference<GLStuff> mThisWeakRef =
new WeakReference<>((GLStuff) this);
private GLThread mGLThread;
private Renderer mRenderer;
private boolean mDetached;
private EGLConfigChooser mEGLConfigChooser;
private EGLContextFactory mEGLContextFactory;
private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
private GLWrapper mGLWrapper;
private int mDebugFlags;
private int mEGLContextClientVersion;
private boolean mPreserveEGLContextOnPause;
}
Loaded 100 of 253 files, more files were not shown because too many files have changed in this diff. Show more