Compare commits

..
105 Commits
Author SHA1 Message Date
txyyh 3f699973b9 Release 1.8.2.4
Releases / Build (push) Has been cancelled
2022-10-05 14:25:33 +00:00
renovate[bot] 3f27c9139c Update plugin com.android.application to v8.0.0-alpha02 (1.8.x.x) (#313)
Update plugin com.android.application to v8.0.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-27 22:42:23 +00:00
tarsin 9eee6fc4af Clipboard management refactor, kill all tips when copy to clipboard on Tiramisu above 2022-09-27 00:45:23 +08:00
tarsin a343dbe8f8 Rename .java to .kt 2022-09-27 00:45:23 +08:00
tarsin bc08a09621 Fix read archive 2022-09-23 09:20:15 +08:00
tarsin 586ad66bff Upgrade to AGP 8
And avoid annoying cmake warnings as well
2022-09-22 20:29:53 +08:00
tarsin 1a426e1f5f Simplify code 2022-09-22 20:29:18 +08:00
tarsin e9a9f1070c Kotlinize 2022-09-22 20:29:18 +08:00
tarsin 7d3dd61bc4 Rename .java to .kt 2022-09-22 20:29:18 +08:00
老虎豆 f37894af88 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-09-22 19:41:56 +08:00
renovate[bot] 14129545b8 Update dependency androidx.recyclerview:recyclerview to v1.3.0-rc01 (1.8.x.x) (#297)
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 10:30:25 +08:00
renovate[bot] f06346b6c7 Update dependency androidx.customview:customview to v1.2.0-alpha02 (1.8.x.x) (#296)
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 09:51:53 +08:00
renovate[bot] 13e3e8c534 Update dependency androidx.constraintlayout:constraintlayout to v2.2.0-alpha04 (1.8.x.x) (#295)
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 08:14:13 +08:00
renovate[bot] 4be282813c Update dependency androidx.compose.material3:material3 to v1.0.0-beta03 (1.8.x.x) (#294)
Update dependency androidx.compose.material3:material3 to v1.0.0-beta03

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 08:14:01 +08:00
renovate[bot] 74e66a407d Update compose_version to v1.3.0-beta03 (1.8.x.x) (#291)
Update compose_version to v1.3.0-beta03

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 06:55:08 +08:00
renovate[bot] b19da4c82e Update dependency androidx.annotation:annotation to v1.5.0 (1.8.x.x) (#292)
Update dependency androidx.annotation:annotation to v1.5.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-22 06:54:41 +08:00
renovate[bot] 2f49764e1b Update dependency androidx.biometric:biometric-ktx to v1.2.0-alpha05 (1.8.x.x) (#293)
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 06:54:23 +08:00
renovate[bot] 08dfb0f72d Update activity_version to v1.6.0 (1.8.x.x) (#290)
Update activity_version to v1.6.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-21 21:38:55 +00:00
txyyh 9251ed238c Fix archive code error (#289) 2022-09-21 08:56:56 +08:00
tarsin 3b96328765 Use fixed size buffer 2022-09-20 18:29:35 +08:00
tarsin 108928e524 Implement save, share image from archive 2022-09-20 18:09:41 +08:00
LaLucid b1191bbf9b Add Gradle cache clean (#287) 2022-09-18 18:00:24 +08:00
txyyh 3424260cfd Release 1.8.2.3
Releases / Build (push) Has been cancelled
2022-09-18 03:31:58 +00:00
EdwardWu e1fe56160e Ehviewer: Translate: Update zh-rTW Translate (09/17) (#284)
* Update string from beadc7 (Merge Account and Signout)
* Update string from 1a6e03 (Add Lock Delay)

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

Signed-off-by: Edward Wu <bluehome.wu@gmail.com>
2022-09-18 01:26:10 +08:00
tarsin 5838ea3e3f Fix #282 2022-09-17 15:11:34 +08:00
txyyh 0906b17ed1 Use example version code in issue template 2022-09-17 13:44:56 +08:00
txyyh 49b9cb72d3 Apply format to all docs 2022-09-16 12:40:28 +00:00
tarsin 01508cc3c1 Update README.md 2022-09-16 13:17:45 +08:00
tarsin 79eba83812 fixup! Release 1.8.2.2
Releases / Build (push) Has been cancelled
2022-09-16 12:42:27 +08:00
tarsin a455dee24c Release 1.8.2.2 2022-09-16 12:41:39 +08:00
tarsin f58b4959e4 Reformat code 2022-09-16 12:39:50 +08:00
tarsin a6b4182ecd Don't require External storage [1.8.x.x] 2022-09-16 12:37:06 +08:00
tarsin 9010591ffc Import Download label first, Fix #245 2022-09-16 11:53:48 +08:00
tarsin e0dde2c982 Read database permissively, Fix #274 2022-09-16 11:40:02 +08:00
tarsin 6109d97bf7 Do not record last leave time if app not unlocked, And Fix #275 2022-09-16 11:14:11 +08:00
tarsin 95936e01ce Drop nativelib version dialog 2022-09-16 10:50:04 +08:00
tarsin beadc7b185 Merge Account and Signout 2022-09-16 10:46:47 +08:00
tarsin 0bc2e45137 Merge Account and Cookies 2022-09-16 10:35:40 +08:00
tarsin b8e649e995 Add support to visit login status 2022-09-16 10:30:16 +08:00
tarsin bdcd21a535 Rename .java to .kt 2022-09-16 10:30:15 +08:00
tarsin 1b779a8b77 Disable unlock delay seekbar as well if Authenticators not available 2022-09-16 09:52:16 +08:00
renovate[bot] 6c59a341ee Update plugin com.android.application to v7.3.0 (1.8.x.x) (#279)
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:00 +00:00
老虎豆 1a6e03c8f8 Add Lock Delay(#276) (#278)
* Add Lock Delay(#276)

* Remove zh-TW translation

* Use SeekBarPreference instead of SimpleMenuPreference
2022-09-16 02:24:49 +08:00
txyyh 653c0a2ac6 Add thanks to issue template (#272)
* Add thanks

* Shorten description
2022-09-12 18:51:43 +08:00
renovate[bot] e4189f367a Update nav_version to v2.5.2 (1.8.x.x) (#265)
Update nav_version to v2.5.2

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 20:11:10 +00:00
renovate[bot] 48b7355ba4 Update dependency androidx.compose.material3:material3 to v1.0.0-beta02 (1.8.x.x) (#263)
Update dependency androidx.compose.material3:material3 to v1.0.0-beta02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 15:35:49 +08:00
renovate[bot] d7d3a903ff Update lifecycle_version to v2.6.0-alpha02 (1.8.x.x) (#264)
Update lifecycle_version to v2.6.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 15:35:36 +08:00
renovate[bot] bab3067273 Update core_version to v1.9.0 (1.8.x.x) (#259)
Update core_version to v1.9.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 10:32:34 +08:00
renovate[bot] fdd70a6908 Update dependency androidx.annotation:annotation to v1.5.0-rc01 (1.8.x.x) (#260)
Update dependency androidx.annotation:annotation to v1.5.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 10:32:22 +08:00
renovate[bot] 3699a8fe30 Update dependency androidx.annotation:annotation-experimental to v1.3.0 (1.8.x.x) (#261)
Update dependency androidx.annotation:annotation-experimental to v1.3.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 10:30:05 +08:00
renovate[bot] 32f98f8a15 Update dependency androidx.appcompat:appcompat to v1.6.0-rc01 (1.8.x.x) (#262)
Update dependency androidx.appcompat:appcompat to v1.6.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 10:27:18 +08:00
renovate[bot] c5366760a2 Update compose_version to v1.3.0-beta02 (1.8.x.x) (#258)
Update compose_version to v1.3.0-beta02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-08 02:03:25 +00:00
renovate[bot] c98ec2a7a4 Update activity_version to v1.6.0-rc02 (1.8.x.x) (#257)
Update activity_version to v1.6.0-rc02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-07 21:47:13 +00:00
txyyh e4c729e51b Add additional context textarea (#255)
* Use other word

* Add additional context to bug report
2022-09-07 08:31:10 +00:00
EdwardWu f36726d8fb 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 13:44:49 +08:00
LittleLoli d2fc2ee136 add backup remote favorite list to local. (#252) 2022-09-06 01:21:53 +08:00
EdwardWu 6edc2164af 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 23:55:22 +08:00
txyyh e34dc30f1f Update material-components (#249) 2022-09-03 08:21:16 +08:00
tarsin c03081d3fa Fix #246 2022-08-31 08:48:36 +08:00
renovate[bot] 97287e4538 Update dependency com.google.android.material:material to v1.7.0-rc01 (1.8.x.x) (#247)
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:42 +08:00
txyyh fc44507a34 Use label on info card (#243) 2022-08-29 16:24:26 +08:00
txyyh 2597004750 Use LabelLarge on CardMessage (#244) 2022-08-29 16:24:11 +08:00
txyyh d1bd9c5fae Use title small on comment (#242) 2022-08-29 16:23:32 +08:00
renovate[bot] 54542f9246 Update room_version to v2.5.0-alpha03 (1.8.x.x) (#236)
Update room_version to v2.5.0-alpha03

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 14:40:38 +08:00
renovate[bot] 377b23458f Update fragment_version to v1.6.0-alpha02 (1.8.x.x) (#235)
Update fragment_version to v1.6.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 14:40:13 +08:00
renovate[bot] b7973cdb76 Update dependency androidx.webkit:webkit to v1.5.0 (1.8.x.x) (#234)
Update dependency androidx.webkit:webkit to v1.5.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 06:24:40 +00:00
renovate[bot] 5f1a70886c Update dependency androidx.annotation:annotation-experimental to v1.3.0-rc01 (1.8.x.x) (#232)
Update dependency androidx.annotation:annotation-experimental to v1.3.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 07:49:36 +08:00
renovate[bot] 5fbc8b0b66 Update dependency androidx.compose.material3:material3 to v1.0.0-beta01 (1.8.x.x) (#233)
Update dependency androidx.compose.material3:material3 to v1.0.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 07:49:24 +08:00
renovate[bot] 780b2b6e1d Update compose_version to v1.3.0-beta01 (1.8.x.x) (#229)
Update compose_version to v1.3.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 07:45:25 +08:00
renovate[bot] 039ad2032c Update core_version to v1.9.0-rc01 (1.8.x.x) (#230)
Update core_version to v1.9.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 07:45:05 +08:00
renovate[bot] a24222b693 Update dependency androidx.annotation:annotation to v1.5.0-beta01 (1.8.x.x) (#231)
Update dependency androidx.annotation:annotation to v1.5.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-25 07:44:53 +08:00
renovate[bot] 6047d91d7e Update activity_version to v1.6.0-rc01 (1.8.x.x) (#228)
Update activity_version to v1.6.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-24 22:18:38 +00:00
tarsin f0158f6946 Make dialog takes some blur 2022-08-24 21:00:41 +08:00
tarsin 00ecdc58e6 Upgrade ndk to 25.1.8937393 2022-08-24 18:04:55 +08:00
renovate[bot] 0a075a97af Update dependency org.jsoup:jsoup to v1.15.3 (1.8.x.x) (#225)
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:11 +00:00
tarsin 34ee70ca7d Make download card takes proper round corner 2022-08-23 19:38:53 +08:00
tarsin 338751ef2b Drop unused code 2022-08-23 19:34:04 +08:00
tarsin d4465ba864 Drop unused color res
* Color should be dynamic most time
2022-08-23 19:11:23 +08:00
tarsin 9a8fdd4edb Make swiperefreshlayout and ProgressIndicator follow dyncolor 2022-08-23 19:05:10 +08:00
txyyh 338848f1eb Make release
Releases / Build (push) Has been cancelled
2022-08-22 11:01:25 +00:00
tarsin 9b918ec036 Revert "Reformat Code"
This reverts commit e0f7dab34f.
2022-08-22 17:27:52 +08:00
txyyh 24439c7853 Update screenshots 2022-08-22 07:20:07 +00:00
txyyh a7f63a5b57 Make release
Releases / Build (push) Has been cancelled
2022-08-22 05:02:51 +00:00
tarsin 1e68782056 Use Tiramisu's imageselector 2022-08-22 12:24:12 +08:00
tarsin e0f7dab34f Reformat Code 2022-08-22 11:38:03 +08:00
tarsin 5513d47b6d Fix #215 2022-08-22 11:30:25 +08:00
tarsin e12abb66f7 Rename .java to .kt 2022-08-22 11:30:25 +08:00
tarsin 106be8c0ca Nuke in-app locale selector on Tiramisu above 2022-08-22 10:18:17 +08:00
txyyh 2e0d065e4d Implement #128 (#217)
* Implement #128

* Add LICENSE Header

* Fix zh_CN zh_TW
2022-08-22 10:06:10 +08:00
tarsin 073298cfee Implement #127 2022-08-22 08:49:57 +08:00
tarsin ebd0f4b4f8 Rename .java to .kt 2022-08-22 08:49:57 +08:00
tarsin 0ba71b060c Fix a race condition 2022-08-21 12:31:10 +08:00
tarsin 144e384441 Refine error handling 2022-08-21 12:00:29 +08:00
tarsin 9bb694171b Implement multithreading archive read 2022-08-21 09:21:53 +08:00
tarsin 647362cd82 Implement multiple archive context, Fix #213
* This is useful for multithreading decompress with lock usage
2022-08-20 23:38:13 +08:00
tarsin 56996558df Fix a use-after-free 2022-08-20 20:23:57 +08:00
txyyh 956b7c719e Make release
Releases / Build (push) Has been cancelled
2022-08-15 14:30:55 +00:00
txyyh 56e13559a0 Describe CI pages 2022-08-15 14:27:45 +00:00
txyyh 261ed86047 Rename apks on releases 2022-08-15 14:24:48 +00:00
tarsin d9b18358d2 Fix a wrong place res 2022-08-15 22:00:19 +08:00
tarsin a5161bba6f Support resize thumb, Implement #193 2022-08-15 21:52:49 +08:00
tarsin 62b00c758c IsLightStatusBar always follow night mode, Fix #199 2022-08-15 20:42:44 +08:00
tarsin 8663b4fa40 Rename .java to .kt 2022-08-15 20:42:44 +08:00
tarsin 6304c12bd0 Fix #196 2022-08-15 20:05:29 +08:00
EdwardWu efe2473ecf Ehviewer: Re-Add warning (#195) 2022-08-12 22:38:46 +08:00
99 changed files with 2030 additions and 2109 deletions

No files matched your search

+26 -4
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,7 +79,7 @@ body:
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
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
@@ -119,4 +141,4 @@ body:
required: true
- label: 您确保已使用最新 CI 版本测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest CI version
required: true
required: true
+14 -5
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,7 +65,7 @@ body:
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
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
@@ -82,4 +91,4 @@ body:
required: true
- label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
required: true
required: true
+2 -1
View File
@@ -25,11 +25,12 @@ jobs:
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --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
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
+14 -6
View File
@@ -23,24 +23,32 @@ jobs:
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
with:
arguments: assembleRelease
- name: Rename Apks
run: |
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal-A12+.apk
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a-A12+.apk
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64-A12+.apk
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86-A12+.apk
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a-A12+.apk
- name: Releases
uses: softprops/action-gh-release@v1
with:
body: Bump Version
discussion_category_name: Announcements
files: |
app/build/outputs/apk/release/app-universal-release.apk
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-A12+.apk
app/build/outputs/apk/release/EhViewer-arm64-v8a-A12+.apk
app/build/outputs/apk/release/EhViewer-x86_64-A12+.apk
app/build/outputs/apk/release/EhViewer-x86-A12+.apk
app/build/outputs/apk/release/EhViewer-armeabi-v7a-A12+.apk
- name: Collect Apks
run: |
+1 -1
View File
@@ -79,7 +79,7 @@ App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 a
Here is the libraries
- [AOSP&AndroidX](http://source.android.com/)
- [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)
+21 -26
View File
@@ -7,12 +7,7 @@ plugins {
android {
compileSdkVersion 33
buildToolsVersion "33.0.0"
ndkVersion "25.0.8775105"
Properties localProperties = new Properties()
if (project.rootProject.file('local.properties').exists()) {
localProperties.load(project.rootProject.file('local.properties').newDataInputStream())
}
ndkVersion "25.1.8937393"
splits {
abi {
@@ -36,8 +31,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 31
targetSdkVersion 33
versionCode 180007
versionName "1.8.1.0"
versionCode 180013
versionName "1.8.2.4"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -98,26 +93,26 @@ android {
}
dependencies {
def activity_version = "1.6.0-beta01"
def activity_version = "1.6.0"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.annotation:annotation:1.5.0-alpha02"
implementation "androidx.annotation:annotation-experimental:1.3.0-beta01"
implementation "androidx.appcompat:appcompat:1.6.0-beta01"
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-alpha04"
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"
def compose_version = "1.3.0-alpha03"
def compose_version = "1.3.0-beta03"
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
@@ -125,23 +120,23 @@ dependencies {
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material3:material3:1.0.0-alpha16"
implementation "androidx.compose.material3:material3:1.0.0-beta03"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
def core_version = "1.9.0-beta01"
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-alpha01"
implementation "androidx.customview:customview:1.2.0-alpha02"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.6.0-alpha01"
def fragment_version = "1.6.0-alpha02"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.6.0-alpha01"
def lifecycle_version = "2.6.0-alpha02"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
@@ -151,7 +146,7 @@ dependencies {
implementation "androidx.loader:loader:1.1.0"
def nav_version = "2.5.1"
def nav_version = "2.5.2"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
@@ -160,9 +155,9 @@ dependencies {
implementation "androidx.paging:paging-runtime:3.2.0-alpha02"
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta02'
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
def room_version = "2.5.0-alpha02"
def room_version = "2.5.0-alpha03"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-paging:$room_version"
@@ -174,10 +169,10 @@ dependencies {
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-rc01'
implementation 'androidx.webkit:webkit:1.5.0'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.7.0-beta01'
implementation 'com.google.android.material:material:1.8.0-alpha01'
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp'
@@ -189,7 +184,7 @@ dependencies {
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jsoup:jsoup:1.15.2'
implementation 'org.jsoup:jsoup:1.15.3'
}
configurations.all {
+2 -5
View File
@@ -23,15 +23,11 @@
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 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" />
@@ -46,6 +42,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme">
+1 -1
View File
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
add_subdirectory(libarchive)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED main.c archive.c image.c)
add_library(${PROJECT_NAME} SHARED main.c archive.c image.c fdutils.c)
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
+212 -71
View File
@@ -17,10 +17,12 @@
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <sys/mman.h>
#include <jni.h>
@@ -37,9 +39,17 @@
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
static JNIEnv *env;
static int cur_index = 0; // Current entry we haven't read any data from it yet
static struct archive *arc = NULL;
typedef struct {
int using;
int next_index;
struct archive *arc;
struct archive_entry *entry;
} archive_ctx;
pthread_mutex_t ctx_lock;
static archive_ctx **ctx_pool;
#define CTX_POOL_SIZE 20
static bool need_encrypt = false;
static char *passwd = NULL;
static void *archiveAddr = NULL;
@@ -57,7 +67,7 @@ const char supportExt[9][6] = {
"heif"
};
static int filename_is_playable_file(const char *name) {
static inline int filename_is_playable_file(const char *name) {
const char *dotptr = strrchr(name, '.');
if (!dotptr++)
return false;
@@ -68,56 +78,150 @@ static int filename_is_playable_file(const char *name) {
return false;
}
static long archive_list_all_entries() {
struct archive_entry *entry;
static long archive_list_all_entries(archive_ctx *ctx) {
long count = 0;
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
if (filename_is_playable_file(archive_entry_pathname(entry)))
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
if (filename_is_playable_file(archive_entry_pathname(ctx->entry)))
count++;
}
if (!count)
LOGE("%s", archive_error_string(arc));
LOGE("%s", archive_error_string(ctx->arc));
return count;
}
static void archive_release() {
if (arc) {
archive_read_close(arc);
archive_read_free(arc);
arc = NULL;
static void archive_release_ctx(archive_ctx *ctx) {
if (ctx) {
archive_read_close(ctx->arc);
archive_read_free(ctx->arc);
free(ctx);
}
}
static int archive_alloc() {
archive_release();
arc = archive_read_new();
archive_read_support_format_all(arc);
archive_read_support_filter_all(arc);
archive_read_set_option(arc, "zip", "ignorecrc32", "1");
static int archive_alloc_ctx(archive_ctx **ctxptr) {
archive_ctx *ctx;
int err;
ctx = calloc(1, sizeof(archive_ctx));
if (!ctx)
return -ENOMEM;
ctx->arc = archive_read_new();
ctx->using = 1;
if (!ctx->arc) {
free(ctx);
return -ENOMEM;
}
archive_read_support_format_all(ctx->arc);
archive_read_support_filter_all(ctx->arc);
archive_read_set_option(ctx->arc, "zip", "ignorecrc32", "1");
if (passwd)
archive_read_add_passphrase(arc, passwd);
return archive_read_open_memory(arc, archiveAddr, archiveSize);
archive_read_add_passphrase(ctx->arc, passwd);
err = archive_read_open_memory(ctx->arc, archiveAddr, archiveSize);
if (err) {
archive_read_free(ctx->arc);
free(ctx);
return err;
}
*ctxptr = ctx;
return 0;
}
static int archive_skip_to_index(archive_ctx *ctx, int index) {
assert(!(ctx->next_index > index));
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname(ctx->entry)))
continue;
if (ctx->next_index++ == index) {
return ctx->next_index - 1;
}
}
return ARCHIVE_FATAL;
}
static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
int ret;
archive_ctx *ctx = NULL;
pthread_mutex_lock(&ctx_lock);
for (int i = 0; i < CTX_POOL_SIZE; i++) {
if (!ctx_pool[i])
continue;
if (ctx_pool[i]->using)
continue;
if (ctx_pool[i]->next_index > idx)
continue;
if (!ctx || ctx_pool[i]->next_index > ctx->next_index)
ctx = ctx_pool[i];
if (ctx->next_index == idx)
break;
}
if (ctx)
ctx->using = 1;
pthread_mutex_unlock(&ctx_lock);
if (!ctx) {
archive_ctx *victimCtx = NULL;
int victimIdx = 0;
int replace = 1;
ret = archive_alloc_ctx(&ctx);
if (ret)
return ret;
pthread_mutex_lock(&ctx_lock);
for (int i = 0; i < CTX_POOL_SIZE; i++) {
if (!ctx_pool[i]) {
ctx_pool[i] = ctx;
replace = 0;
break;
}
if (ctx_pool[i]->using)
continue;
if (!victimCtx || ctx_pool[i]->next_index > victimCtx->next_index) {
victimCtx = ctx_pool[i];
victimIdx = i;
}
}
if (replace) {
archive_release_ctx(victimCtx);
ctx_pool[victimIdx] = ctx;
}
pthread_mutex_unlock(&ctx_lock);
}
ret = archive_skip_to_index(ctx, idx);
if (ret != idx) {
ret = archive_errno(ctx->arc);
LOGE("Skip to index failed:%s", archive_error_string(ctx->arc));
archive_release_ctx(ctx);
return ret;
}
*ctxptr = ctx;
return 0;
}
JNIEXPORT jint JNICALL
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jint fd, jlong size) {
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd, jlong size) {
(void) env; /* UNUSED */
(void) thiz; /* UNUSED */
archive_ctx *ctx = NULL;
archiveAddr = mmap64(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (archiveAddr == MAP_FAILED) {
LOGE("%s%d", "mmap64 failed with errno ", errno);
return 0;
}
archiveSize = size;
env = _;
long r = archive_alloc();
pthread_mutex_init(&ctx_lock, 0);
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
if (!ctx_pool) {
LOGE("Allocate archive ctx pool failed:ENOMEM");
return 0;
}
long r = archive_alloc_ctx(&ctx);
if (r) {
if (r == -ENOMEM)
LOGE("%s", "Mem alloc failed");
r = 0;
LOGE("%s%s", "Archive open failed:", archive_error_string(arc));
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
} else {
r = archive_list_all_entries();
r = archive_list_all_entries(ctx);
LOGI("%s%ld%s", "Found ", r, " image entries in archive");
// We must read through the file|vm then we can know whether it is encrypted
int encryptRet = archive_read_has_encrypted_entries(arc);
int encryptRet = archive_read_has_encrypted_entries(ctx->arc);
switch (encryptRet) {
case 1: // At lease 1 encrypted entry
need_encrypt = true;
@@ -128,86 +232,123 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jint fd,
}
// madvise for readahead optimization
int format = archive_format(arc);
int format = archive_format(ctx->arc);
switch (format) {
case ARCHIVE_FORMAT_ZIP:
case ARCHIVE_FORMAT_RAR:
case ARCHIVE_FORMAT_RAR_V5:
madvise(archiveAddr, archiveSize, MADV_SEQUENTIAL | MADV_WILLNEED);
break;
default:
;
default:;
}
}
archive_release();
archive_release_ctx(ctx);
return r;
}
typedef struct Memarea {
void *buffer;
long size;
} Memarea;
JNIEXPORT jlong JNICALL
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *_, jobject thiz, jint index) {
struct archive_entry *entry;
Java_com_hippo_UriArchiveAccessor_extractToAddr(JNIEnv *env, jobject thiz, jint index) {
(void) env; /* UNUSED */
(void) thiz; /* UNUSED */
archive_ctx *ctx = NULL;
int ret;
if (!arc || index < cur_index) {
archive_alloc();
cur_index = 0;
ret = archive_get_ctx(&ctx, index);
if (ret)
return 0;
long long size = archive_entry_size(ctx->entry);
void *buffer = malloc(size + sizeof(long long));
if (!buffer) {
ctx->using = 0;
LOGE("Allocate buffer for decompression failed:ENOMEM");
return 0;
}
Memarea *memarea = malloc(sizeof(Memarea));
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname(entry)))
continue;
if (cur_index++ == index) {
memarea->size = (long) archive_entry_size(entry);
memarea->buffer = malloc(memarea->size);
ret = archive_read_data(arc, memarea->buffer, memarea->size);
if (ret != memarea->size)
LOGE("%s", "No enough data read, WTF?");
if (ret < 0)
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
break;
}
}
return (jlong) memarea;
*(long long *) buffer = size;
ret = archive_read_data(ctx->arc, buffer + sizeof(long long), size);
ctx->using = 0;
if (ret == size)
return (jlong) buffer;
if (ret != size)
LOGE("%s", "No enough data read, WTF?");
if (ret < 0)
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
free(buffer);
return 0;
}
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *jniEnv, jobject thiz) {
archive_release();
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
(void) env; /* UNUSED */
(void) thiz; /* UNUSED */
for (int i = 0; i < CTX_POOL_SIZE; i++)
archive_release_ctx(ctx_pool[i]);
free(passwd);
free(ctx_pool);
pthread_mutex_destroy(&ctx_lock);
passwd = NULL;
need_encrypt = false;
munmap(archiveAddr, archiveSize);
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *_, jobject thiz) {
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *env, jobject thiz) {
(void) env; /* UNUSED */
(void) thiz; /* UNUSED */
return need_encrypt;
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *_, jobject thiz, jstring str) {
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jstring str) {
(void) thiz; /* UNUSED */
struct archive_entry *entry;
archive_ctx *ctx;
jboolean ret = true;
int len = (*env)->GetStringUTFLength(env, str);
if (passwd)
free(passwd);
passwd = calloc(len, sizeof(char));
passwd = realloc(passwd, len * sizeof(char));
if (!passwd) {
LOGE("Allocate passwd buffer failed");
return false;
}
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
archive_alloc();
archive_alloc_ctx(&ctx);
void *tmpBuf = alloca(4096);
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
while (archive_read_next_header(ctx->arc, &entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname(entry)))
continue;
if (!archive_entry_is_encrypted(entry))
continue;
if (archive_read_data(arc, tmpBuf, 4096) < ARCHIVE_OK) {
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
if (archive_read_data(ctx->arc, tmpBuf, 4096) < ARCHIVE_OK) {
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
ret = false;
}
break;
}
archive_release();
archive_release_ctx(ctx);
return ret;
}
JNIEXPORT jstring JNICALL
Java_com_hippo_UriArchiveAccessor_getFilename(JNIEnv *env, jobject thiz, jint index) {
(void) env; /* UNUSED */
(void) thiz; /* UNUSED */
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
if (ret)
return NULL;
jstring str = (*env)->NewStringUTF(env, archive_entry_pathname(ctx->entry));
ctx->using = 0;
return str;
}
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint index, jint fd) {
(void) env; /* UNUSED */
(void) thiz; /* UNUSED */
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
if (!ret) {
archive_read_data_into_fd(ctx->arc, fd);
ctx->using = 0;
}
}
+16
View File
@@ -0,0 +1,16 @@
#include <jni.h>
JNIEXPORT jint JNICALL
Java_com_hippo_Native_getFd(JNIEnv *env, jclass clazz, jobject fileDescriptor) {
jint fd = -1;
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
if (fdClass != NULL) {
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
fd = (*env)->GetIntField(env, fileDescriptor, fdClassDescriptorFieldID);
}
}
return fd;
}
+11 -37
View File
@@ -84,11 +84,6 @@ static void recycle(IMAGE *image) {
free(image);
}
typedef struct Memarea {
void *buffer;
long size;
} Memarea;
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
int width, int height) {
@@ -159,17 +154,17 @@ bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
// First line
dst_pos += (int)dst_blank_length;
dst_pos += (int) dst_blank_length;
memcpy(dst + dst_pos, src + src_pos, line_stride);
dst_pos += (int)line_stride;
dst_pos += (int) line_stride;
src_pos += src_stride;
// Other lines
dst_blank_length = (size_t) ((dst_w - width) * 4);
for (line = 1; line < height; line++) {
dst_pos += (int)dst_blank_length;
dst_pos += (int) dst_blank_length;
memcpy(dst + dst_pos, src + src_pos, line_stride);
dst_pos += (int)line_stride;
dst_pos += (int) line_stride;
src_pos += src_stride;
}
@@ -216,9 +211,9 @@ IMAGE *createFromFd(int fd) {
return image;
}
IMAGE *createFromAddr(void *addr, long size) {
IMAGE *createFromAddr(void *addr, long long size) {
AImageDecoder *decoder;
AImageDecoder_createFromBuffer((const void *) addr, size, &decoder);
AImageDecoder_createFromBuffer((const void *) addr + sizeof(long long), size, &decoder);
IMAGE *image = calloc(1, sizeof(IMAGE));
SOURCE *src = calloc(1, sizeof(SOURCE));
src->decoder = decoder;
@@ -263,32 +258,12 @@ void advance(IMAGE *image) {
}
}
static int jniGetFDFromFileDescriptor(JNIEnv *env, jobject fileDescriptor) {
jint fd = -1;
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
if (fdClass != NULL) {
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
fd = (*env)->GetIntField(env, fileDescriptor, fdClassDescriptorFieldID);
}
}
return fd;
}
JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeDecodeFdInt(JNIEnv *env, jclass clazz, jint fd) {
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jint fd) {
IMAGE *image = createFromFd(fd);
return (jlong) image;
}
JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jobject fd) {
return Java_com_hippo_image_Image_nativeDecodeFdInt(env, clazz,
jniGetFDFromFileDescriptor(env, fd));
}
JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeCreate(JNIEnv *env, jclass clazz, jobject bitmap) {
AndroidBitmapInfo info;
@@ -298,7 +273,7 @@ Java_com_hippo_image_Image_nativeCreate(JNIEnv *env, jclass clazz, jobject bitma
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, &pixels);
image = create((int)info.width, (int)info.height, pixels);
image = create((int) info.width, (int) info.height, pixels);
AndroidBitmap_unlockPixels(env, bitmap);
return (jlong) image;
}
@@ -313,7 +288,8 @@ Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jlong ptr, ji
AndroidBitmap_getInfo(env, dst, &info);
AndroidBitmap_lockPixels(env, dst, &pixels);
render((IMAGE *) ptr, src_x, src_y, pixels, (int)info.width, (int)info.height, dst_x, dst_y, width,
render((IMAGE *) ptr, src_x, src_y, pixels, (int) info.width, (int) info.height, dst_x, dst_y,
width,
height);
AndroidBitmap_unlockPixels(env, dst);
@@ -358,9 +334,7 @@ Java_com_hippo_image_Image_nativeRecycle(JNIEnv *env, jclass clazz, jlong ptr) {
JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeDecodeAddr(JNIEnv *env, jclass clazz, jlong addr) {
Memarea *memarea = (Memarea *) addr;
IMAGE *image = createFromAddr(memarea->buffer, memarea->size);
free(memarea);
IMAGE *image = createFromAddr((void *) addr, *(long long *) addr);
return (jlong) image;
}
-20
View File
@@ -43,23 +43,3 @@ JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved) {
image_onUnload();
}
JNIEXPORT jstring JNICALL
Java_com_hippo_Native_getlibarchiveVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, ARCHIVE_VERSION_STRING);
}
JNIEXPORT jstring JNICALL
Java_com_hippo_Native_getliblzmaVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, lzma_version_string());
}
JNIEXPORT jstring JNICALL
Java_com_hippo_Native_getzlibVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, ZLIB_VERSION);
}
JNIEXPORT jstring JNICALL
Java_com_hippo_Native_getnettleVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, PACKAGE_STRING);
}
+3 -8
View File
@@ -18,17 +18,12 @@
package com.hippo;
public class Native {
import java.io.FileDescriptor;
public class Native {
public static void initialize() {
System.loadLibrary("ehviewer");
}
public static native String getlibarchiveVersion();
public static native String getliblzmaVersion();
public static native String getzlibVersion();
public static native String getnettleVersion();
public static native int getFd(FileDescriptor fd);
}
@@ -21,10 +21,6 @@ package com.hippo;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.system.Os;
import android.system.OsConstants;
import java.io.FileDescriptor;
public class UriArchiveAccessor {
ParcelFileDescriptor pfd;
@@ -43,7 +39,11 @@ public class UriArchiveAccessor {
private native int openArchive(int fd, long size);
public native long extracttoOutputStream(int index);
public native long extractToAddr(int index);
public native void extractToFd(int index, int fd);
public native String getFilename(int index);
public native boolean needPassword();
@@ -0,0 +1,20 @@
package com.hippo.app
import android.content.Context
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
open class BaseDialogBuilder constructor(
context: Context
) : MaterialAlertDialogBuilder(context) {
override fun create(): AlertDialog {
return super.create().apply {
window?.run {
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
attributes.blurBehindRadius = 32
}
}
}
}
@@ -18,7 +18,6 @@ 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(
@@ -26,7 +25,7 @@ class CheckBoxDialogBuilder constructor(
message: String?,
checkText: String?,
checked: Boolean
) : MaterialAlertDialogBuilder(
) : BaseDialogBuilder(
context
) {
private val mCheckBox: CheckBox
@@ -23,7 +23,6 @@ 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
@@ -31,7 +30,7 @@ class EditTextDialogBuilder constructor(
context: Context,
text: String?,
hint: String?
) : MaterialAlertDialogBuilder(
) : BaseDialogBuilder(
context
), OnEditorActionListener {
private val mTextInputLayout: TextInputLayout
@@ -23,14 +23,13 @@ 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(
) : BaseDialogBuilder(
context
) {
private val mCheckBox: CheckBox
@@ -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();
}
@@ -26,6 +26,7 @@ import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Debug;
import android.text.Html;
import android.text.method.LinkMovementMethod;
@@ -43,8 +44,8 @@ import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.Native;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.conaco.Conaco;
import com.hippo.ehviewer.client.EhClient;
@@ -60,13 +61,13 @@ import com.hippo.ehviewer.client.parser.EventPaneParser;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.EhActivity;
import com.hippo.image.Image;
import com.hippo.image.ImageBitmap;
import com.hippo.network.StatusCodeException;
import com.hippo.scene.SceneApplication;
import com.hippo.unifile.UniFile;
import com.hippo.util.BitmapUtils;
import com.hippo.util.ClipboardUtil;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.util.ReadableTime;
@@ -108,6 +109,7 @@ public class EhApplication extends SceneApplication {
private static final long DEBUG_PRINT_INTERVAL = 3000L;
public static boolean locked = true;
public static long locked_last_leave_time = 0;
private static EhApplication instance;
@@ -326,7 +328,6 @@ public class EhApplication extends SceneApplication {
super.onCreate();
Native.initialize();
ClipboardUtil.initialize(this);
GetText.initialize(this);
StatusCodeException.initialize(this);
Settings.initialize(this);
@@ -337,7 +338,10 @@ public class EhApplication extends SceneApplication {
EhEngine.initialize();
BitmapUtils.initialize(this);
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
// Locales can be managed by system automatically above Snow Cone v2
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
// Do io tasks in new thread
@@ -425,7 +429,7 @@ public class EhApplication extends SceneApplication {
Activity activity = getTopActivity();
if (activity != null) {
activity.runOnUiThread(() -> {
AlertDialog dialog = new MaterialAlertDialogBuilder(activity)
AlertDialog dialog = new BaseDialogBuilder(activity)
.setMessage(Html.fromHtml(html))
.setPositiveButton(android.R.string.ok, null)
.create();
@@ -528,9 +532,9 @@ public class EhApplication extends SceneApplication {
}
@Nullable
public Activity getTopActivity() {
public EhActivity getTopActivity() {
if (!mActivityList.isEmpty()) {
return mActivityList.get(mActivityList.size() - 1);
return (EhActivity) mActivityList.get(mActivityList.size() - 1);
} else {
return null;
}
@@ -586,6 +590,8 @@ public class EhApplication extends SceneApplication {
static class EhlifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onPause(@NonNull LifecycleOwner owner) {
if (!locked)
locked_last_leave_time = System.currentTimeMillis() / 1000;
locked = true;
}
}
+52 -39
View File
@@ -490,60 +490,73 @@ public class EhDB {
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
.createFromFile(file).allowMainThreadQueries().build();
// Download label
DownloadManager manager = EhApplication.getDownloadManager(context);
try {
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
manager.addDownloadLabel(downloadLabelList);
} catch (Exception ignored) {}
// Downloads
DownloadManager manager = EhApplication.getDownloadManager(context);
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
manager.addDownload(downloadInfoList, false);
// Download label
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
manager.addDownloadLabel(downloadLabelList);
try {
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
manager.addDownload(downloadInfoList, false);
} catch (Exception ignored) {}
// Download dirname
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
for (DownloadDirname dirname : downloadDirnameList) {
putDownloadDirname(dirname.getGid(), dirname.getDirname());
}
try {
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
for (DownloadDirname dirname : downloadDirnameList) {
putDownloadDirname(dirname.getGid(), dirname.getDirname());
}
} catch (Exception ignored) {}
// History
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
putHistoryInfo(historyInfoList);
try {
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
putHistoryInfo(historyInfoList);
} catch (Exception ignored) {}
// QuickSearch
List<QuickSearch> quickSearchList = oldRoomDatabase.quickSearchDao().list();
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
List<QuickSearch> importList = new ArrayList<>();
for (QuickSearch quickSearch : quickSearchList) {
String name = quickSearch.name;
for (QuickSearch q : currentQuickSearchList) {
if (ObjectUtils.equal(q.name, name)) {
// The same name
name = null;
break;
try {
List<QuickSearch> quickSearchList = oldRoomDatabase.quickSearchDao().list();
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
List<QuickSearch> importList = new ArrayList<>();
for (QuickSearch quickSearch : quickSearchList) {
String name = quickSearch.name;
for (QuickSearch q : currentQuickSearchList) {
if (ObjectUtils.equal(q.name, name)) {
// The same name
name = null;
break;
}
}
if (null == name) {
continue;
}
importList.add(quickSearch);
}
if (null == name) {
continue;
}
importList.add(quickSearch);
}
importQuickSearch(importList);
importQuickSearch(importList);
} catch (Exception ignored) {}
// LocalFavorites
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
for (LocalFavoriteInfo info : localFavoriteInfoList) {
putLocalFavorites(info);
}
try {
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
for (LocalFavoriteInfo info : localFavoriteInfoList) {
putLocalFavorites(info);
}
} catch (Exception ignored) {}
// Filter
List<Filter> filterList = oldRoomDatabase.filterDao().list();
List<Filter> currentFilterList = db.filterDao().list();
for (Filter filter : filterList) {
if (!currentFilterList.contains(filter)) {
addFilter(filter);
try {
List<Filter> filterList = oldRoomDatabase.filterDao().list();
List<Filter> currentFilterList = db.filterDao().list();
for (Filter filter : filterList) {
if (!currentFilterList.contains(filter)) {
addFilter(filter);
}
}
}
} catch (Exception ignored) {}
return null;
} catch (Throwable e) {
@@ -50,6 +50,8 @@ public class Settings {
********************/
public static final String KEY_THEME = "theme";
public static final String KEY_ACCOUNT = "account";
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
public static final int THEME_LIGHT = 1;
public static final int THEME_SYSTEM = -1;
@@ -76,6 +78,8 @@ public class Settings {
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
public static final String KEY_NOTIFICATION_REQUIRED = "notification_required";
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
public static final String KEY_MEDIA_SCAN = "media_scan";
@@ -88,6 +92,7 @@ public class Settings {
********************/
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
public static final String KEY_SECURITY = "require_unlock";
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
public static final String DEFAULT_SECURITY = "";
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
@@ -100,6 +105,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";
@@ -426,6 +436,7 @@ public class Settings {
putInt(KEY_VERSION_CODE, value);
}
@Nullable
public static String getDisplayName() {
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
}
@@ -1056,6 +1067,9 @@ public class Settings {
public static void putSecurity(boolean value) {
putBoolean(KEY_SECURITY, value);
}
public static int getSecurityDelay() {
return getInt(KEY_SECURITY_DELAY, 0);
}
public static boolean getEnableFingerprint() {
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
@@ -1168,4 +1182,22 @@ public class Settings {
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;
}
public static boolean getNotificationRequired() {
return getBoolean(KEY_NOTIFICATION_REQUIRED, false);
}
public static void putNotificationRequired() {
putBoolean(KEY_NOTIFICATION_REQUIRED, true);
}
}
@@ -1,581 +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.download;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.dao.DownloadInfo;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.ui.scene.DownloadsScene;
import com.hippo.scene.StageActivity;
import com.hippo.util.ReadableTime;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.collect.LongList;
import com.hippo.yorozuya.collect.SparseJBArray;
import com.hippo.yorozuya.collect.SparseJLArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class DownloadService extends Service implements DownloadManager.DownloadListener {
public static final String ACTION_START = "start";
public static final String ACTION_START_RANGE = "start_range";
public static final String ACTION_START_ALL = "start_all";
public static final String ACTION_STOP = "stop";
public static final String ACTION_STOP_RANGE = "stop_range";
public static final String ACTION_STOP_CURRENT = "stop_current";
public static final String ACTION_STOP_ALL = "stop_all";
public static final String ACTION_DELETE = "delete";
public static final String ACTION_DELETE_RANGE = "delete_range";
public static final String ACTION_CLEAR = "clear";
public static final String KEY_GALLERY_INFO = "gallery_info";
public static final String KEY_LABEL = "label";
public static final String KEY_GID = "gid";
public static final String KEY_GID_LIST = "gid_list";
private static final int ID_DOWNLOADING = 1;
private static final int ID_DOWNLOADED = 2;
private static final int ID_509 = 3;
private final static SparseJBArray sItemStateArray = new SparseJBArray();
private final static SparseJLArray<String> sItemTitleArray = new SparseJLArray<>();
private static int sFailedCount;
private static int sFinishedCount;
private static int sDownloadedCount;
@Nullable
private NotificationManagerCompat mNotifyManager;
@Nullable
private DownloadManager mDownloadManager;
private NotificationCompat.Builder mDownloadingBuilder;
private NotificationCompat.Builder mDownloadedBuilder;
private NotificationCompat.Builder m509dBuilder;
private NotificationDelay mDownloadingDelay;
private NotificationDelay mDownloadedDelay;
private NotificationDelay m509Delay;
private String CHANNEL_ID;
public static void clear() {
sFailedCount = 0;
sFinishedCount = 0;
sDownloadedCount = 0;
sItemStateArray.clear();
sItemTitleArray.clear();
}
@Override
public void onCreate() {
super.onCreate();
CHANNEL_ID = getPackageName() + ".download";
mNotifyManager = NotificationManagerCompat.from(this);
mNotifyManager.createNotificationChannel(
new NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.download_service))
.build());
mDownloadManager = EhApplication.getDownloadManager(getApplicationContext());
mDownloadManager.setDownloadListener(this);
ensureDownloadingBuilder();
mDownloadingBuilder.setContentTitle(getString(R.string.download_service))
.setContentText(null)
.setSubText(null)
.setProgress(0, 0, true);
startForeground(ID_DOWNLOADING, mDownloadingBuilder.build());
}
@Override
public void onDestroy() {
super.onDestroy();
mNotifyManager = null;
if (mDownloadManager != null) {
mDownloadManager.setDownloadListener(null);
mDownloadManager = null;
}
mDownloadingBuilder = null;
mDownloadedBuilder = null;
m509dBuilder = null;
if (mDownloadingDelay != null) {
mDownloadingDelay.release();
}
if (mDownloadedDelay != null) {
mDownloadedDelay.release();
}
if (m509Delay != null) {
m509Delay.release();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handleIntent(intent);
return START_STICKY;
}
private void handleIntent(Intent intent) {
String action = null;
if (intent != null) {
action = intent.getAction();
}
if (ACTION_START.equals(action)) {
GalleryInfo gi = intent.getParcelableExtra(KEY_GALLERY_INFO);
String label = intent.getStringExtra(KEY_LABEL);
if (gi != null && mDownloadManager != null) {
mDownloadManager.startDownload(gi, label);
}
} else if (ACTION_START_RANGE.equals(action)) {
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
if (gidList != null && mDownloadManager != null) {
mDownloadManager.startRangeDownload(gidList);
}
} else if (ACTION_START_ALL.equals(action)) {
if (mDownloadManager != null) {
mDownloadManager.startAllDownload();
}
} else if (ACTION_STOP.equals(action)) {
long gid = intent.getLongExtra(KEY_GID, -1);
if (gid != -1 && mDownloadManager != null) {
mDownloadManager.stopDownload(gid);
}
} else if (ACTION_STOP_CURRENT.equals(action)) {
if (mDownloadManager != null) {
mDownloadManager.stopCurrentDownload();
}
} else if (ACTION_STOP_RANGE.equals(action)) {
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
if (gidList != null && mDownloadManager != null) {
mDownloadManager.stopRangeDownload(gidList);
}
} else if (ACTION_STOP_ALL.equals(action)) {
if (mDownloadManager != null) {
mDownloadManager.stopAllDownload();
}
} else if (ACTION_DELETE.equals(action)) {
long gid = intent.getLongExtra(KEY_GID, -1);
if (gid != -1 && mDownloadManager != null) {
mDownloadManager.deleteDownload(gid);
}
} else if (ACTION_DELETE_RANGE.equals(action)) {
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
if (gidList != null && mDownloadManager != null) {
mDownloadManager.deleteRangeDownload(gidList);
}
} else if (ACTION_CLEAR.equals(action)) {
clear();
}
checkStopSelf();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
throw new IllegalStateException("No bindService");
}
private void ensureDownloadingBuilder() {
if (mDownloadingBuilder != null) {
return;
}
Intent stopAllIntent = new Intent(this, DownloadService.class);
stopAllIntent.setAction(ACTION_STOP_ALL);
PendingIntent piStopAll = PendingIntent.getService(this, 0, stopAllIntent, PendingIntent.FLAG_IMMUTABLE);
mDownloadingBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setOngoing(true)
.setAutoCancel(false)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.addAction(R.drawable.v_pause_x24, getString(R.string.stat_download_action_stop_all), piStopAll)
.setShowWhen(false)
.setChannelId(CHANNEL_ID);
mDownloadingDelay = new NotificationDelay(this, mNotifyManager, mDownloadingBuilder, ID_DOWNLOADING);
}
private void ensureDownloadedBuilder() {
if (mDownloadedBuilder != null) {
return;
}
Intent clearIntent = new Intent(this, DownloadService.class);
clearIntent.setAction(ACTION_CLEAR);
PendingIntent piClear = PendingIntent.getService(this, 0, clearIntent, PendingIntent.FLAG_IMMUTABLE);
Bundle bundle = new Bundle();
bundle.putString(DownloadsScene.KEY_ACTION, DownloadsScene.ACTION_CLEAR_DOWNLOAD_SERVICE);
Intent activityIntent = new Intent(this, MainActivity.class);
activityIntent.setAction(StageActivity.ACTION_START_SCENE);
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene.class.getName());
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle);
PendingIntent piActivity = PendingIntent.getActivity(DownloadService.this, 0,
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mDownloadedBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentTitle(getString(R.string.stat_download_done_title))
.setDeleteIntent(piClear)
.setOngoing(false)
.setAutoCancel(true)
.setContentIntent(piActivity);
mDownloadedDelay = new NotificationDelay(this, mNotifyManager, mDownloadedBuilder, ID_DOWNLOADED);
}
private void ensure509Builder() {
if (m509dBuilder != null) {
return;
}
m509dBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
.setSmallIcon(R.drawable.ic_baseline_warning_24)
.setContentText(getString(R.string.stat_509_alert_title))
.setContentText(getString(R.string.stat_509_alert_text))
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.stat_509_alert_text)))
.setAutoCancel(true)
.setOngoing(false)
.setCategory(NotificationCompat.CATEGORY_ERROR);
m509Delay = new NotificationDelay(this, mNotifyManager, m509dBuilder, ID_509);
}
@Override
public void onGet509() {
if (mDownloadManager != null) {
mDownloadManager.stopAllDownload();
}
if (mNotifyManager == null) {
return;
}
ensure509Builder();
m509dBuilder.setWhen(System.currentTimeMillis());
m509Delay.show();
}
@Override
public void onStart(DownloadInfo info) {
if (mNotifyManager == null) {
return;
}
ensureDownloadingBuilder();
Bundle bundle = new Bundle();
bundle.putLong(DownloadsScene.KEY_GID, info.gid);
Intent activityIntent = new Intent(this, MainActivity.class);
activityIntent.setAction(StageActivity.ACTION_START_SCENE);
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene.class.getName());
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle);
PendingIntent piActivity = PendingIntent.getActivity(DownloadService.this, 0,
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mDownloadingBuilder.setContentTitle(EhUtils.getSuitableTitle(info))
.setContentText(null)
.setSubText(null)
.setProgress(0, 0, true)
.setContentIntent(piActivity);
mDownloadingDelay.startForeground();
}
private void onUpdate(DownloadInfo info) {
if (mNotifyManager == null) {
return;
}
ensureDownloadingBuilder();
long speed = info.speed;
if (speed < 0) {
speed = 0;
}
String text = FileUtils.humanReadableByteCount(speed, false) + "/s";
long remaining = info.remaining;
if (remaining >= 0) {
text = getString(R.string.download_speed_text_2, text, ReadableTime.getShortTimeInterval(remaining));
} else {
text = getString(R.string.download_speed_text, text);
}
mDownloadingBuilder.setContentTitle(EhUtils.getSuitableTitle(info))
.setContentText(text)
.setStyle(new NotificationCompat.BigTextStyle().bigText(text))
.setSubText(info.total == -1 || info.finished == -1 ? null : info.finished + "/" + info.total)
.setProgress(info.total, info.finished, false);
mDownloadingDelay.startForeground();
}
@Override
public void onDownload(DownloadInfo info) {
onUpdate(info);
}
@Override
public void onGetPage(DownloadInfo info) {
onUpdate(info);
}
@Override
public void onFinish(DownloadInfo info) {
if (mNotifyManager == null) {
return;
}
if (null != mDownloadingDelay) {
mDownloadingDelay.cancel();
}
ensureDownloadedBuilder();
boolean finish = info.state == DownloadInfo.STATE_FINISH;
long gid = info.gid;
int index = sItemStateArray.indexOfKey(gid);
if (index < 0) { // Not contain
sItemStateArray.put(gid, finish);
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info));
sDownloadedCount++;
if (finish) {
sFinishedCount++;
} else {
sFailedCount++;
}
} else { // Contain
boolean oldFinish = sItemStateArray.valueAt(index);
sItemStateArray.put(gid, finish);
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info));
if (oldFinish && !finish) {
sFinishedCount--;
sFailedCount++;
} else if (!oldFinish && finish) {
sFinishedCount++;
sFailedCount--;
}
}
String text;
boolean needStyle;
if (sFinishedCount != 0 && sFailedCount == 0) {
if (sFinishedCount == 1) {
if (sItemTitleArray.size() >= 1) {
text = getString(R.string.stat_download_done_line_succeeded, sItemTitleArray.valueAt(0));
} else {
Log.d("TAG", "WTF, sItemTitleArray is null");
text = getString(R.string.error_unknown);
}
needStyle = false;
} else {
text = getString(R.string.stat_download_done_text_succeeded, sFinishedCount);
needStyle = true;
}
} else if (sFinishedCount == 0 && sFailedCount != 0) {
if (sFailedCount == 1) {
if (sItemTitleArray.size() >= 1) {
text = getString(R.string.stat_download_done_line_failed, sItemTitleArray.valueAt(0));
} else {
Log.d("TAG", "WTF, sItemTitleArray is null");
text = getString(R.string.error_unknown);
}
needStyle = false;
} else {
text = getString(R.string.stat_download_done_text_failed, sFailedCount);
needStyle = true;
}
} else {
text = getString(R.string.stat_download_done_text_mix, sFinishedCount, sFailedCount);
needStyle = true;
}
NotificationCompat.InboxStyle style;
if (needStyle) {
style = new NotificationCompat.InboxStyle();
style.setBigContentTitle(getString(R.string.stat_download_done_title));
SparseJBArray stateArray = sItemStateArray;
for (int i = 0, n = stateArray.size(); i < n; i++) {
long id = stateArray.keyAt(i);
boolean fin = stateArray.valueAt(i);
String title = sItemTitleArray.get(id);
if (title == null) {
continue;
}
style.addLine(getString(fin ? R.string.stat_download_done_line_succeeded :
R.string.stat_download_done_line_failed, title));
}
} else {
style = null;
}
mDownloadedBuilder.setContentText(text)
.setStyle(style)
.setWhen(System.currentTimeMillis())
.setNumber(sDownloadedCount);
mDownloadedDelay.show();
checkStopSelf();
}
@Override
public void onCancel(DownloadInfo info) {
if (mNotifyManager == null) {
return;
}
if (null != mDownloadingDelay) {
mDownloadingDelay.cancel();
}
checkStopSelf();
}
private void checkStopSelf() {
if (mDownloadManager == null || mDownloadManager.isIdle()) {
stopForeground(true);
stopSelf();
}
}
// TODO Include all notification in one delay
// Avoid frequent notification
private static class NotificationDelay implements Runnable {
private static final int OPS_NOTIFY = 0;
private static final int OPS_CANCEL = 1;
private static final int OPS_START_FOREGROUND = 2;
private static final long DELAY = 1000; // 1s
private final NotificationManagerCompat mNotifyManager;
private final NotificationCompat.Builder mBuilder;
private final int mId;
private Service mService;
private long mLastTime;
private boolean mPosted;
// false for show, true for cancel
@Ops
private int mOps;
public NotificationDelay(Service service, NotificationManagerCompat notifyManager,
NotificationCompat.Builder builder, int id) {
mService = service;
mNotifyManager = notifyManager;
mBuilder = builder;
mId = id;
}
public void release() {
mService = null;
}
public void show() {
if (mPosted) {
mOps = OPS_NOTIFY;
} else {
long now = SystemClock.currentThreadTimeMillis();
if (now - mLastTime > DELAY) {
// Wait long enough, do it now
mNotifyManager.notify(mId, mBuilder.build());
} else {
// Too quick, post delay
mOps = OPS_NOTIFY;
mPosted = true;
SimpleHandler.getInstance().postDelayed(this, DELAY);
}
mLastTime = now;
}
}
public void cancel() {
if (mPosted) {
mOps = OPS_CANCEL;
} else {
long now = SystemClock.currentThreadTimeMillis();
if (now - mLastTime > DELAY) {
// Wait long enough, do it now
mNotifyManager.cancel(mId);
} else {
// Too quick, post delay
mOps = OPS_CANCEL;
mPosted = true;
SimpleHandler.getInstance().postDelayed(this, DELAY);
}
}
}
public void startForeground() {
if (mPosted) {
mOps = OPS_START_FOREGROUND;
} else {
long now = SystemClock.currentThreadTimeMillis();
if (now - mLastTime > DELAY) {
// Wait long enough, do it now
if (mService != null) {
mService.startForeground(mId, mBuilder.build());
}
} else {
// Too quick, post delay
mOps = OPS_START_FOREGROUND;
mPosted = true;
SimpleHandler.getInstance().postDelayed(this, DELAY);
}
}
}
@Override
public void run() {
mPosted = false;
switch (mOps) {
case OPS_NOTIFY:
mNotifyManager.notify(mId, mBuilder.build());
break;
case OPS_CANCEL:
mNotifyManager.cancel(mId);
break;
case OPS_START_FOREGROUND:
if (mService != null) {
mService.startForeground(mId, mBuilder.build());
}
break;
}
}
@IntDef({OPS_NOTIFY, OPS_CANCEL, OPS_START_FOREGROUND})
@Retention(RetentionPolicy.SOURCE)
private @interface Ops {
}
}
}
@@ -0,0 +1,550 @@
/*
* 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.download
import android.Manifest
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.IBinder
import android.os.SystemClock
import android.util.Log
import androidx.annotation.IntDef
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.ui.MainActivity
import com.hippo.ehviewer.ui.scene.DownloadsScene
import com.hippo.scene.StageActivity
import com.hippo.util.ReadableTime
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.SimpleHandler
import com.hippo.yorozuya.collect.LongList
import com.hippo.yorozuya.collect.SparseJBArray
import com.hippo.yorozuya.collect.SparseJLArray
class DownloadService : Service(), DownloadManager.DownloadListener {
private var mNotifyManager: NotificationManagerCompat? = null
private var mDownloadManager: DownloadManager? = null
private var mDownloadingBuilder: NotificationCompat.Builder? = null
private var mDownloadedBuilder: NotificationCompat.Builder? = null
private var m509dBuilder: NotificationCompat.Builder? = null
private var mDownloadingDelay: NotificationDelay? = null
private var mDownloadedDelay: NotificationDelay? = null
private var m509Delay: NotificationDelay? = null
private var mChannelID: String? = null
override fun onCreate() {
super.onCreate()
mChannelID = "$packageName.download"
mNotifyManager = NotificationManagerCompat.from(this)
mNotifyManager!!.createNotificationChannel(
NotificationChannelCompat.Builder(
mChannelID!!,
NotificationManagerCompat.IMPORTANCE_LOW
)
.setName(getString(R.string.download_service))
.build()
)
mDownloadManager = EhApplication.getDownloadManager(applicationContext)
mDownloadManager!!.setDownloadListener(this)
ensureDownloadingBuilder()
mDownloadingBuilder!!.setContentTitle(getString(R.string.download_service))
.setContentText(null)
.setSubText(null)
.setProgress(0, 0, true)
startForeground(ID_DOWNLOADING, mDownloadingBuilder!!.build())
}
override fun onDestroy() {
super.onDestroy()
mNotifyManager = null
if (mDownloadManager != null) {
mDownloadManager!!.setDownloadListener(null)
mDownloadManager = null
}
mDownloadingBuilder = null
mDownloadedBuilder = null
m509dBuilder = null
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
handleIntent(intent)
return START_STICKY
}
private fun handleIntent(intent: Intent?) {
var action: String? = null
if (intent != null) {
action = intent.action
}
if (ACTION_START == action) {
val gi = intent!!.getParcelableExtra<GalleryInfo>(KEY_GALLERY_INFO)
val label = intent.getStringExtra(KEY_LABEL)
if (gi != null && mDownloadManager != null) {
mDownloadManager!!.startDownload(gi, label)
}
} else if (ACTION_START_RANGE == action) {
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
if (gidList != null && mDownloadManager != null) {
mDownloadManager!!.startRangeDownload(gidList)
}
} else if (ACTION_START_ALL == action) {
if (mDownloadManager != null) {
mDownloadManager!!.startAllDownload()
}
} else if (ACTION_STOP == action) {
val gid = intent!!.getLongExtra(KEY_GID, -1)
if (gid != -1L && mDownloadManager != null) {
mDownloadManager!!.stopDownload(gid)
}
} else if (ACTION_STOP_CURRENT == action) {
if (mDownloadManager != null) {
mDownloadManager!!.stopCurrentDownload()
}
} else if (ACTION_STOP_RANGE == action) {
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
if (gidList != null && mDownloadManager != null) {
mDownloadManager!!.stopRangeDownload(gidList)
}
} else if (ACTION_STOP_ALL == action) {
if (mDownloadManager != null) {
mDownloadManager!!.stopAllDownload()
}
} else if (ACTION_DELETE == action) {
val gid = intent!!.getLongExtra(KEY_GID, -1)
if (gid != -1L && mDownloadManager != null) {
mDownloadManager!!.deleteDownload(gid)
}
} else if (ACTION_DELETE_RANGE == action) {
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
if (gidList != null && mDownloadManager != null) {
mDownloadManager!!.deleteRangeDownload(gidList)
}
} else if (ACTION_CLEAR == action) {
clear()
}
checkStopSelf()
}
override fun onBind(intent: Intent): IBinder? {
throw IllegalStateException("No bindService")
}
private fun ensureDownloadingBuilder() {
if (mDownloadingBuilder != null) {
return
}
val stopAllIntent = Intent(this, DownloadService::class.java)
stopAllIntent.action = ACTION_STOP_ALL
val piStopAll =
PendingIntent.getService(this, 0, stopAllIntent, PendingIntent.FLAG_IMMUTABLE)
mDownloadingBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setOngoing(true)
.setAutoCancel(false)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.addAction(
R.drawable.v_pause_x24,
getString(R.string.stat_download_action_stop_all),
piStopAll
)
.setShowWhen(false)
.setChannelId(mChannelID!!)
mDownloadingDelay =
NotificationDelay(this, mNotifyManager!!, mDownloadingBuilder!!, ID_DOWNLOADING)
}
private fun ensureDownloadedBuilder() {
if (mDownloadedBuilder != null) {
return
}
val clearIntent = Intent(this, DownloadService::class.java)
clearIntent.action = ACTION_CLEAR
val piClear = PendingIntent.getService(this, 0, clearIntent, PendingIntent.FLAG_IMMUTABLE)
val bundle = Bundle()
bundle.putString(DownloadsScene.KEY_ACTION, DownloadsScene.ACTION_CLEAR_DOWNLOAD_SERVICE)
val activityIntent = Intent(this, MainActivity::class.java)
activityIntent.action = StageActivity.ACTION_START_SCENE
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene::class.java.name)
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle)
val piActivity = PendingIntent.getActivity(
this@DownloadService, 0,
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
mDownloadedBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentTitle(getString(R.string.stat_download_done_title))
.setDeleteIntent(piClear)
.setOngoing(false)
.setAutoCancel(true)
.setContentIntent(piActivity)
mDownloadedDelay =
NotificationDelay(this, mNotifyManager!!, mDownloadedBuilder!!, ID_DOWNLOADED)
}
private fun ensure509Builder() {
if (m509dBuilder != null) {
return
}
m509dBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
.setSmallIcon(R.drawable.ic_baseline_warning_24)
.setContentText(getString(R.string.stat_509_alert_title))
.setContentText(getString(R.string.stat_509_alert_text))
.setStyle(
NotificationCompat.BigTextStyle().bigText(getString(R.string.stat_509_alert_text))
)
.setAutoCancel(true)
.setOngoing(false)
.setCategory(NotificationCompat.CATEGORY_ERROR)
m509Delay = NotificationDelay(this, mNotifyManager!!, m509dBuilder!!, ID_509)
}
override fun onGet509() {
if (mDownloadManager != null) {
mDownloadManager!!.stopAllDownload()
}
if (mNotifyManager == null) {
return
}
ensure509Builder()
m509dBuilder!!.setWhen(System.currentTimeMillis())
m509Delay!!.show()
}
override fun onStart(info: DownloadInfo) {
if (mNotifyManager == null) {
return
}
ensureDownloadingBuilder()
val bundle = Bundle()
bundle.putLong(DownloadsScene.KEY_GID, info.gid)
val activityIntent = Intent(this, MainActivity::class.java)
activityIntent.action = StageActivity.ACTION_START_SCENE
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene::class.java.name)
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle)
val piActivity = PendingIntent.getActivity(
this@DownloadService, 0,
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
mDownloadingBuilder!!.setContentTitle(EhUtils.getSuitableTitle(info))
.setContentText(null)
.setSubText(null)
.setProgress(0, 0, true)
.setContentIntent(piActivity)
mDownloadingDelay!!.startForeground()
}
private fun onUpdate(info: DownloadInfo) {
if (mNotifyManager == null) {
return
}
ensureDownloadingBuilder()
var speed = info.speed
if (speed < 0) {
speed = 0
}
var text = FileUtils.humanReadableByteCount(speed, false) + "/s"
val remaining = info.remaining
text = if (remaining >= 0) {
getString(
R.string.download_speed_text_2,
text,
ReadableTime.getShortTimeInterval(remaining)
)
} else {
getString(R.string.download_speed_text, text)
}
mDownloadingBuilder!!.setContentTitle(EhUtils.getSuitableTitle(info))
.setContentText(text)
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
.setSubText(if (info.total == -1 || info.finished == -1) null else info.finished.toString() + "/" + info.total)
.setProgress(info.total, info.finished, false)
mDownloadingDelay!!.startForeground()
}
override fun onDownload(info: DownloadInfo) {
onUpdate(info)
}
override fun onGetPage(info: DownloadInfo) {
onUpdate(info)
}
override fun onFinish(info: DownloadInfo) {
if (mNotifyManager == null) {
return
}
if (null != mDownloadingDelay) {
mDownloadingDelay!!.cancel()
}
ensureDownloadedBuilder()
val finish = info.state == DownloadInfo.STATE_FINISH
val gid = info.gid
val index = sItemStateArray.indexOfKey(gid)
if (index < 0) { // Not contain
sItemStateArray.put(gid, finish)
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info))
sDownloadedCount++
if (finish) {
sFinishedCount++
} else {
sFailedCount++
}
} else { // Contain
val oldFinish = sItemStateArray.valueAt(index)
sItemStateArray.put(gid, finish)
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info))
if (oldFinish && !finish) {
sFinishedCount--
sFailedCount++
} else if (!oldFinish && finish) {
sFinishedCount++
sFailedCount--
}
}
val text: String
val needStyle: Boolean
if (sFinishedCount != 0 && sFailedCount == 0) {
if (sFinishedCount == 1) {
text = if (sItemTitleArray.size() >= 1) {
getString(
R.string.stat_download_done_line_succeeded,
sItemTitleArray.valueAt(0)
)
} else {
Log.d("TAG", "WTF, sItemTitleArray is null")
getString(R.string.error_unknown)
}
needStyle = false
} else {
text = getString(R.string.stat_download_done_text_succeeded, sFinishedCount)
needStyle = true
}
} else if (sFinishedCount == 0 && sFailedCount != 0) {
if (sFailedCount == 1) {
text = if (sItemTitleArray.size() >= 1) {
getString(
R.string.stat_download_done_line_failed,
sItemTitleArray.valueAt(0)
)
} else {
Log.d("TAG", "WTF, sItemTitleArray is null")
getString(R.string.error_unknown)
}
needStyle = false
} else {
text = getString(R.string.stat_download_done_text_failed, sFailedCount)
needStyle = true
}
} else {
text = getString(R.string.stat_download_done_text_mix, sFinishedCount, sFailedCount)
needStyle = true
}
val style: NotificationCompat.InboxStyle?
if (needStyle) {
style = NotificationCompat.InboxStyle()
style.setBigContentTitle(getString(R.string.stat_download_done_title))
val stateArray = sItemStateArray
var i = 0
val n = stateArray.size()
while (i < n) {
val id = stateArray.keyAt(i)
val fin = stateArray.valueAt(i)
val title = sItemTitleArray[id]
if (title == null) {
i++
continue
}
style.addLine(
getString(
if (fin) R.string.stat_download_done_line_succeeded else R.string.stat_download_done_line_failed,
title
)
)
i++
}
} else {
style = null
}
mDownloadedBuilder!!.setContentText(text)
.setStyle(style)
.setWhen(System.currentTimeMillis())
.setNumber(sDownloadedCount)
mDownloadedDelay!!.show()
checkStopSelf()
}
override fun onCancel(info: DownloadInfo) {
if (mNotifyManager == null) {
return
}
if (null != mDownloadingDelay) {
mDownloadingDelay!!.cancel()
}
checkStopSelf()
}
private fun checkStopSelf() {
if (mDownloadManager == null || mDownloadManager!!.isIdle) {
stopForeground(true)
stopSelf()
}
}
private class NotificationDelay(
private var mService: Service,
private val mNotifyManager: NotificationManagerCompat,
private val mBuilder: NotificationCompat.Builder,
private val mId: Int
) : Runnable {
private var mLastTime: Long = 0
private var mPosted = false
// false for show, true for cancel
@Ops
private var mOps = 0
fun show() {
if (mPosted) {
mOps = OPS_NOTIFY
} else {
val now = SystemClock.currentThreadTimeMillis()
if (now - mLastTime > DELAY) {
// Wait long enough, do it now
if (ActivityCompat.checkSelfPermission(
mService,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return
}
mNotifyManager.notify(mId, mBuilder.build())
} else {
// Too quick, post delay
mOps = OPS_NOTIFY
mPosted = true
SimpleHandler.getInstance().postDelayed(this, DELAY)
}
mLastTime = now
}
}
fun cancel() {
if (mPosted) {
mOps = OPS_CANCEL
} else {
val now = SystemClock.currentThreadTimeMillis()
if (now - mLastTime > DELAY) {
// Wait long enough, do it now
mNotifyManager.cancel(mId)
} else {
// Too quick, post delay
mOps = OPS_CANCEL
mPosted = true
SimpleHandler.getInstance().postDelayed(this, DELAY)
}
}
}
fun startForeground() {
if (mPosted) {
mOps = OPS_START_FOREGROUND
} else {
val now = SystemClock.currentThreadTimeMillis()
if (now - mLastTime > DELAY) {
// Wait long enough, do it now
mService.startForeground(mId, mBuilder.build())
} else {
// Too quick, post delay
mOps = OPS_START_FOREGROUND
mPosted = true
SimpleHandler.getInstance().postDelayed(this, DELAY)
}
}
}
override fun run() {
mPosted = false
when (mOps) {
OPS_NOTIFY -> {
if (ActivityCompat.checkSelfPermission(
mService,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return
}
mNotifyManager.notify(mId, mBuilder.build())
}
OPS_CANCEL -> mNotifyManager.cancel(mId)
OPS_START_FOREGROUND -> mService.startForeground(mId, mBuilder.build())
}
}
@IntDef(OPS_NOTIFY, OPS_CANCEL, OPS_START_FOREGROUND)
@Retention(AnnotationRetention.SOURCE)
private annotation class Ops
companion object {
private const val OPS_NOTIFY = 0
private const val OPS_CANCEL = 1
private const val OPS_START_FOREGROUND = 2
private const val DELAY: Long = 1000 // 1s
}
}
companion object {
const val ACTION_START = "start"
const val ACTION_START_RANGE = "start_range"
const val ACTION_START_ALL = "start_all"
const val ACTION_STOP = "stop"
const val ACTION_STOP_RANGE = "stop_range"
const val ACTION_STOP_CURRENT = "stop_current"
const val ACTION_STOP_ALL = "stop_all"
const val ACTION_DELETE = "delete"
const val ACTION_DELETE_RANGE = "delete_range"
const val ACTION_CLEAR = "clear"
const val KEY_GALLERY_INFO = "gallery_info"
const val KEY_LABEL = "label"
const val KEY_GID = "gid"
const val KEY_GID_LIST = "gid_list"
private const val ID_DOWNLOADING = 1
private const val ID_DOWNLOADED = 2
private const val ID_509 = 3
private val sItemStateArray = SparseJBArray()
private val sItemTitleArray = SparseJLArray<String>()
private var sFailedCount = 0
private var sFinishedCount = 0
private var sDownloadedCount = 0
@JvmStatic
fun clear() {
sFailedCount = 0
sFinishedCount = 0
sDownloadedCount = 0
sItemStateArray.clear()
sItemTitleArray.clear()
}
}
}
@@ -24,16 +24,19 @@ import android.os.Process;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.Native;
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.FileUtils;
import com.hippo.yorozuya.thread.PVLock;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -48,11 +51,15 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
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);
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private Thread archiveThread;
private Thread decodeThread;
private Thread[] decodeThread = new Thread[] {
new Thread(new DecodeTask()),
new Thread(new DecodeTask()),
new Thread(new DecodeTask()),
new Thread(new DecodeTask())
};
private volatile int size = STATE_WAIT;
private String error;
@@ -74,25 +81,22 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
int id = sIdGenerator.incrementAndGet();
archiveThread = new PriorityThread(
new ArchiveTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
new ArchiveHostTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
archiveThread.start();
decodeThread = new PriorityThread(
new DecodeTask(), "DecodeTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
decodeThread.start();
for (Thread i : decodeThread) {
i.start();
}
}
@Override
public void stop() {
super.stop();
if (archiveThread != null) {
archiveThread.interrupt();
archiveThread = null;
}
if (decodeThread != null) {
decodeThread.interrupt();
decodeThread = null;
for (Thread i : decodeThread) {
i.interrupt();
}
try {
archiveAccessor.close();
@@ -110,11 +114,11 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
protected void onRequest(int index) {
boolean inDecodeTask;
synchronized (streams) {
inDecodeTask = streams.containsKey(index) || index == decodingIndex.get();
inDecodeTask = streams.containsKey(index);
}
synchronized (requests) {
boolean inArchiveTask = requests.contains(index) || index == extractingIndex.get();
boolean inArchiveTask = requests.contains(index);
if (!inArchiveTask && !inDecodeTask) {
requests.add(index);
requests.notify();
@@ -143,31 +147,53 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
@NonNull
@Override
public String getImageFilename(int index) {
// TODO
return Integer.toString(index);
}
@Override
public boolean save(int index, @NonNull UniFile file) {
// TODO
return false;
}
@Nullable
@Override
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
// TODO
return null;
return FileUtils.getNameFromFilename(getImageFilenameWithExtension(index));
}
@NonNull
@Override
public String getImageFilenameWithExtension(int index) {
// TODO
return Integer.toString(index);
return FileUtils.sanitizeFilename(archiveAccessor.getFilename(index));
}
private class ArchiveTask implements Runnable {
@Override
public boolean save(int index, @NonNull UniFile file) {
int fd;
FileOutputStream stream;
try {
stream = (FileOutputStream)file.openOutputStream();
fd = Native.getFd(stream.getFD());
} catch (IOException e) {
e.printStackTrace();
return false;
}
archiveAccessor.extractToFd(index, fd);
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
@Nullable
@Override
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
String extension = FileUtils.getExtensionFromFilename(getImageFilenameWithExtension(index));
UniFile dst = dir.subFile(null != extension ? filename + "." + extension : filename);
save(index, dst);
return dst;
}
private class ArchiveHostTask implements Runnable {
public final Thread[] archiveThreads = new Thread[] {
new Thread(new ArchiveTask()),
new Thread(new ArchiveTask()),
new Thread(new ArchiveTask()),
new Thread(new ArchiveTask())
};
private void waitPasswd() throws InterruptedException {
showPasswd.sendEmptyMessage(0);
pv.p();
@@ -225,7 +251,26 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
}
}
}
for (Thread i : archiveThreads) {
i.start();
}
Object o = new Object();
synchronized (o) {
try {
o.wait();
} catch (InterruptedException e) {
for (Thread i : archiveThreads) {
i.interrupt();
}
}
}
}
}
private class ArchiveTask implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (requests) {
@@ -239,12 +284,10 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
index = requests.pop();
extractingIndex.lazySet(index);
}
// Check index valid
if (index < 0 || index >= size) {
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
continue;
}
@@ -255,7 +298,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
}
}
long addr = archiveAccessor.extracttoOutputStream(index);
long addr = archiveAccessor.extractToAddr(index);
synchronized (streams) {
streams.put(index, addr);
@@ -276,7 +319,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
try {
streams.wait();
} catch (InterruptedException e) {
// Interrupted
break;
}
continue;
@@ -287,18 +329,13 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
iterator.remove();
index = entry.getKey();
addr = entry.getValue();
decodingIndex.lazySet(index);
}
try {
Image image = Image.decodeAddr(addr, true);
if (image != null) {
notifyPageSucceed(index, image);
} else {
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
}
} finally {
decodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
Image image = Image.decodeAddr(addr, true);
if (image != null) {
notifyPageSucceed(index, image);
} else {
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
}
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2018 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.preference
import android.content.*
import android.text.Html
import android.util.AttributeSet
import androidx.appcompat.app.AlertDialog
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.ui.SettingsActivity
import com.hippo.ehviewer.ui.scene.BaseScene
import com.hippo.preference.MessagePreference
import com.hippo.util.addTextToClipboard
import com.hippo.util.getClipboardManager
import okhttp3.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.*
class AccountPreference @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MessagePreference(context, attrs) {
private val mActivity = context as SettingsActivity
private var message: String? = null
init {
val store = EhApplication.getEhCookieStore(context)
val eCookies = store.getCookies(EhUrl.HOST_E.toHttpUrl())
val exCookies = store.getCookies(EhUrl.HOST_EX.toHttpUrl())
val cookies: MutableList<Cookie> = LinkedList(eCookies)
cookies.addAll(exCookies)
var ipbMemberId: String? = null
var ipbPassHash: String? = null
var igneous: String? = null
var i = 0
val n = cookies.size
while (i < n) {
val cookie = cookies[i]
when (cookie.name) {
EhCookieStore.KEY_IPD_MEMBER_ID -> ipbMemberId = cookie.value
EhCookieStore.KEY_IPD_PASS_HASH -> ipbPassHash = cookie.value
EhCookieStore.KEY_IGNEOUS -> igneous = cookie.value
}
i++
}
if (ipbMemberId != null || ipbPassHash != null || igneous != null) {
message = (EhCookieStore.KEY_IPD_MEMBER_ID + ": " + ipbMemberId + "<br>"
+ EhCookieStore.KEY_IPD_PASS_HASH + ": " + ipbPassHash + "<br>"
+ EhCookieStore.KEY_IGNEOUS + ": " + igneous)
setDialogMessage(
Html.fromHtml(
context.getString(
R.string.settings_eh_identity_cookies_signed,
message
), Html.FROM_HTML_MODE_LEGACY
)
)
message = message!!.replace("<br>", "\n")
} else {
setDialogMessage(context.getString(R.string.settings_eh_identity_cookies_tourist))
}
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
if (message != null) {
builder.setNeutralButton(R.string.settings_eh_identity_cookies_copy) { dialog: DialogInterface?, which: Int ->
context.getClipboardManager().addTextToClipboard(message, true) {
mActivity.showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
}
this@AccountPreference.onClick(dialog, which)
}
}
builder.setPositiveButton(R.string.settings_eh_sign_out) { _: DialogInterface?, _: Int ->
EhUtils.signOut(context)
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT)
}
}
}
@@ -1,117 +0,0 @@
/*
* Copyright 2018 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.preference;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.Html;
import android.util.AttributeSet;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhCookieStore;
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.util.ClipboardUtil;
import java.util.LinkedList;
import java.util.List;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
public class IdentityCookiePreference extends MessagePreference {
@SuppressLint("StaticFieldLeak")
private final SettingsActivity mActivity;
private String message;
public IdentityCookiePreference(Context context) {
super(context);
mActivity = (SettingsActivity) context;
init();
}
public IdentityCookiePreference(Context context, AttributeSet attrs) {
super(context, attrs);
mActivity = (SettingsActivity) context;
init();
}
public IdentityCookiePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivity = (SettingsActivity) context;
init();
}
private void init() {
EhCookieStore store = EhApplication.getEhCookieStore(getContext());
List<Cookie> eCookies = store.getCookies(HttpUrl.get(EhUrl.HOST_E));
List<Cookie> exCookies = store.getCookies(HttpUrl.get(EhUrl.HOST_EX));
List<Cookie> cookies = new LinkedList<>(eCookies);
cookies.addAll(exCookies);
String ipbMemberId = null;
String ipbPassHash = null;
String igneous = null;
for (int i = 0, n = cookies.size(); i < n; i++) {
Cookie cookie = cookies.get(i);
switch (cookie.name()) {
case EhCookieStore.KEY_IPD_MEMBER_ID:
ipbMemberId = cookie.value();
break;
case EhCookieStore.KEY_IPD_PASS_HASH:
ipbPassHash = cookie.value();
break;
case EhCookieStore.KEY_IGNEOUS:
igneous = cookie.value();
break;
}
}
if (ipbMemberId != null || ipbPassHash != null || igneous != null) {
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), Html.FROM_HTML_MODE_LEGACY));
message = message.replace("<br>", "\n");
} else {
setDialogMessage(getContext().getString(R.string.settings_eh_identity_cookies_tourist));
}
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
if (message != null) {
builder.setPositiveButton(R.string.settings_eh_identity_cookies_copy, (dialog, which) -> {
ClipboardUtil.addTextToClipboard(message);
mActivity.showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT);
IdentityCookiePreference.this.onClick(dialog, which);
});
builder.setNegativeButton(android.R.string.cancel, null);
} else {
builder.setPositiveButton(android.R.string.ok, null);
}
}
}
@@ -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.ehviewer.preference;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.ui.SettingsActivity;
import com.hippo.ehviewer.ui.scene.BaseScene;
import com.hippo.preference.MessagePreference;
public class SignOutPreference extends MessagePreference {
@SuppressLint("StaticFieldLeak")
private final SettingsActivity mActivity;
public SignOutPreference(Context context) {
super(context);
init();
mActivity = (SettingsActivity) context;
}
public SignOutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
mActivity = (SettingsActivity) context;
}
public SignOutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
mActivity = (SettingsActivity) context;
}
private void init() {
setDialogMessage(getContext().getString(R.string.settings_eh_sign_out_warning));
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
builder.setPositiveButton(R.string.settings_eh_sign_out_yes, this);
builder.setNegativeButton(android.R.string.cancel, null);
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
EhUtils.signOut(getContext());
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT);
}
}
}
@@ -18,14 +18,12 @@ package com.hippo.ehviewer.preference
import android.content.Context
import android.content.pm.PackageManager
import android.util.AttributeSet
import androidx.appcompat.app.AlertDialog
import com.hippo.Native
import androidx.preference.Preference
import com.hippo.ehviewer.R
import com.hippo.preference.MessagePreference
class VersionPreference @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MessagePreference(context, attrs) {
) : Preference(context, attrs) {
init {
setTitle(R.string.settings_about_version)
@Suppress("DEPRECATION") val version: String = try {
@@ -37,31 +35,4 @@ class VersionPreference @JvmOverloads constructor(
}
summary = version
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setTitle(R.string.show_library_version)
val info = NativeLibInfo().addLib(Native.getlibarchiveVersion())
.addLib("lzma(xz-utils)", Native.getliblzmaVersion())
.addLib("zlib", Native.getzlibVersion())
.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
}
}
}
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.ui;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import androidx.core.content.ContextCompat;
@@ -42,6 +43,7 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public final class CommonOperations {
@@ -110,8 +112,14 @@ public final class CommonOperations {
startDownload(activity, Collections.singletonList(galleryInfo), forceDefault);
}
// TODO Add context if activity and context are different style
public static void startDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Objects.requireNonNull(EhApplication.getInstance().getTopActivity()).checkAndRequestNotificationPermission();
}
doStartDownload(activity, galleryInfos, forceDefault);
}
private static void doStartDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
final DownloadManager dm = EhApplication.getDownloadManager(activity);
LongList toStart = new LongList();
@@ -1,138 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import rikka.core.res.ResourcesKt;
import rikka.insets.WindowInsetsHelper;
import rikka.layoutinflater.view.LayoutInflaterFactory;
public abstract class EhActivity extends AppCompatActivity {
private static final String THEME_DEFAULT = "DEFAULT";
private static final String THEME_BLACK = "BLACK";
public static boolean isBlackNightTheme() {
return Settings.getBoolean("black_dark_theme", false);
}
public static String getTheme(Context context) {
if (isBlackNightTheme() && isNightMode(context.getResources().getConfiguration()))
return THEME_BLACK;
return THEME_DEFAULT;
}
public static boolean isNightMode(Configuration configuration) {
return (configuration.uiMode & Configuration.UI_MODE_NIGHT_YES) > 0;
}
@StyleRes
public int getThemeStyleRes(Context context) {
if (THEME_BLACK.equals(getTheme(context))) {
return R.style.ThemeOverlay_Black;
}
return R.style.ThemeOverlay;
}
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
if (getParent() == null) {
theme.applyStyle(resid, true);
} else {
try {
theme.setTo(getParent().getTheme());
} catch (Exception ignored) {
}
theme.applyStyle(resid, false);
}
theme.applyStyle(getThemeStyleRes(this), true);
super.onApplyThemeResource(theme, R.style.ThemeOverlay, first);
}
@Override
protected void onNightModeChanged(int mode) {
getTheme().applyStyle(getThemeStyleRes(this), true);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
getLayoutInflater().setFactory2((new LayoutInflaterFactory(getDelegate())).addOnViewCreatedListener(WindowInsetsHelper.getLISTENER()));
super.onCreate(savedInstanceState);
Window window = getWindow();
window.setStatusBarColor(Color.TRANSPARENT);
window.getDecorView().post(() -> {
WindowInsets rootWindowInsets = window.getDecorView().getRootWindowInsets();
if (rootWindowInsets != null && rootWindowInsets.getSystemWindowInsetBottom() >= Resources.getSystem().getDisplayMetrics().density * 40) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.setNavigationBarDividerColor(getColor(R.color.navigation_bar_divider));
}
window.setNavigationBarColor(ResourcesKt.resolveColor(getTheme(), android.R.attr.navigationBarColor) & 0x00ffffff | -0x20000000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(false);
}
} else {
window.setNavigationBarColor(Color.TRANSPARENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
}
});
((EhApplication) getApplication()).registerActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
((EhApplication) getApplication()).unregisterActivity(this);
}
@Override
protected void onResume() {
if (Settings.getSecurity() && SecurityActivity.Companion.isAuthenticationSupported(this) && EhApplication.locked) {
startActivity(new Intent(this, SecurityActivity.class));
}
super.onResume();
if (Settings.getEnabledSecurity()) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
}
@@ -0,0 +1,156 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhApplication.locked_last_leave_time
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
import rikka.core.res.resolveColor
import rikka.insets.WindowInsetsHelper
import rikka.layoutinflater.view.LayoutInflaterFactory
abstract class EhActivity : AppCompatActivity() {
@StyleRes
fun getThemeStyleRes(context: Context): Int {
return if (THEME_BLACK == getTheme(context)) {
R.style.ThemeOverlay_Black
} else R.style.ThemeOverlay
}
override fun onApplyThemeResource(theme: Theme, resid: Int, first: Boolean) {
if (parent == null) {
theme.applyStyle(resid, true)
} else {
try {
theme.setTo(parent.theme)
} catch (ignored: Exception) {
}
theme.applyStyle(resid, false)
}
theme.applyStyle(getThemeStyleRes(this), true)
super.onApplyThemeResource(theme, R.style.ThemeOverlay, first)
}
override fun onNightModeChanged(mode: Int) {
theme.applyStyle(getThemeStyleRes(this), true)
}
override fun onCreate(savedInstanceState: Bundle?) {
layoutInflater.factory2 =
LayoutInflaterFactory(delegate).addOnViewCreatedListener(
WindowInsetsHelper.LISTENER
)
super.onCreate(savedInstanceState)
val window = window
window.statusBarColor = Color.TRANSPARENT
window.decorView.post {
val rootWindowInsets = window.decorView.rootWindowInsets
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
}
window.navigationBarColor =
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
} else {
window.navigationBarColor = Color.TRANSPARENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = true
}
}
}
(application as EhApplication).registerActivity(this)
}
override fun onDestroy() {
super.onDestroy()
(application as EhApplication).unregisterActivity(this)
}
override fun onResume() {
val locked_resume_time = System.currentTimeMillis() / 1000
val locked_delay_time = locked_resume_time - locked_last_leave_time
if (Settings.getSecurity() && locked_delay_time >= Settings.getSecurityDelay() * 60
&& isAuthenticationSupported(this) && EhApplication.locked
) {
startActivity(Intent(this, SecurityActivity::class.java))
}
super.onResume()
if (Settings.getEnabledSecurity()) {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) {
Settings.putNotificationRequired()
}
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
fun checkAndRequestNotificationPermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
return
}
if (Settings.getNotificationRequired())
return
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
companion object {
private const val THEME_DEFAULT = "DEFAULT"
private const val THEME_BLACK = "BLACK"
private val isBlackNightTheme: Boolean
get() = Settings.getBoolean("black_dark_theme", false)
fun getTheme(context: Context): String {
return if (isBlackNightTheme && isNightMode(context.resources.configuration)) THEME_BLACK else THEME_DEFAULT
}
private fun isNightMode(configuration: Configuration): Boolean {
return configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0
}
}
}
@@ -67,8 +67,8 @@ import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
@@ -290,16 +290,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
outState.putInt(KEY_PAGE, mPage);
outState.putInt(KEY_CURRENT_INDEX, mCurrentIndex);
} 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 attachBaseContext(@NonNull Context newBase) {
@@ -312,7 +303,16 @@ 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"})
@@ -406,17 +406,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);
}
}
}
@@ -853,7 +858,8 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
intent.setAction(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.putExtra(Intent.EXTRA_TEXT, EhUrl.getGalleryDetailUrl(mGalleryInfo.gid, mGalleryInfo.token));
if (mGalleryInfo != null)
intent.putExtra(Intent.EXTRA_TEXT, EhUrl.getGalleryDetailUrl(mGalleryInfo.gid, mGalleryInfo.token));
intent.setDataAndType(uri, mimeType);
try {
@@ -985,7 +991,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private void showPageDialog(final int page) {
Resources resources = GalleryActivity.this.getResources();
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
BaseDialogBuilder builder = new BaseDialogBuilder(GalleryActivity.this);
builder.setTitle(resources.getString(R.string.page_menu_title, page + 1));
final CharSequence[] items;
@@ -1281,7 +1287,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
private void onTapMenuArea() {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
BaseDialogBuilder builder = new BaseDialogBuilder(GalleryActivity.this);
GalleryMenuHelper helper = new GalleryMenuHelper(builder.getContext());
builder.setTitle(R.string.gallery_menu_title)
.setView(helper.getView())
@@ -1370,4 +1376,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
@@ -42,9 +42,9 @@ import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.R;
@@ -77,7 +77,7 @@ import com.hippo.scene.SceneFragment;
import com.hippo.scene.StageActivity;
import com.hippo.unifile.UniFile;
import com.hippo.util.BitmapUtils;
import com.hippo.util.ClipboardUtil;
import com.hippo.util.ClipboardUtilKt;
import com.hippo.widget.DrawerView;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.SimpleHandler;
@@ -259,7 +259,7 @@ public final class MainActivity extends StageActivity
String url = intent.getData().toString();
new EditTextDialogBuilder(this, url, "")
.setTitle(R.string.error_cannot_parse_the_url)
.setPositiveButton(android.R.string.copy, (dialog, which) -> ClipboardUtil.addTextToClipboard(url))
.setPositiveButton(android.R.string.copy, (dialog, which) -> ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(this), url, false, () -> null))
.show();
}
}
@@ -288,7 +288,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);
@@ -338,7 +337,7 @@ public final class MainActivity extends StageActivity
break;
}
if (hasUnverified) {
new MaterialAlertDialogBuilder(this)
new BaseDialogBuilder(this)
.setTitle(R.string.app_link_not_verified_title)
.setMessage(R.string.app_link_not_verified_message)
.setPositiveButton(R.string.open_settings, (dialogInterface, i) -> {
@@ -364,7 +363,7 @@ public final class MainActivity extends StageActivity
if (null == uniFile || uniFile.ensureDir()) {
return;
}
new MaterialAlertDialogBuilder(this)
new BaseDialogBuilder(this)
.setTitle(R.string.waring)
.setMessage(R.string.invalid_download_location)
.setPositiveButton(R.string.get_it, null)
@@ -457,7 +456,7 @@ public final class MainActivity extends StageActivity
}
private void checkClipboardUrlInternal() {
String text = ClipboardUtil.getUrlFromClipboard();
String text = ClipboardUtilKt.getUrlFromClipboard(ClipboardUtilKt.getClipboardManager(this), this);
int hashCode = text != null ? text.hashCode() : 0;
if (text != null && hashCode != 0 && Settings.getClipboardTextHashCode() != hashCode) {
@@ -19,7 +19,6 @@
package com.hippo.ehviewer.ui
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
@@ -32,8 +31,8 @@ import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
class SecurityActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
override fun onResume() {
super.onResume()
if (isAuthenticationSupported(this))
startAuthentication(getString(R.string.settings_privacy_require_unlock))
else
@@ -1,68 +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 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;
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()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
.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,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.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,11 +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.util.Log;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@@ -31,13 +33,18 @@ import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.os.LocaleListCompat;
import androidx.preference.Preference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
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;
@@ -52,6 +59,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -63,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(),
@@ -76,7 +85,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
e.printStackTrace();
}
try {
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
AlertDialog alertDialog = new BaseDialogBuilder(requireActivity())
.setCancelable(false)
.setView(R.layout.preference_dialog_task)
.show();
@@ -193,7 +202,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
e.printStackTrace();
}
try {
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
AlertDialog alertDialog = new BaseDialogBuilder(requireActivity())
.setCancelable(false)
.setView(R.layout.preference_dialog_task)
.show();
@@ -227,9 +236,12 @@ public class AdvancedFragment extends BasePreferenceFragment {
Preference dumpLogcat = findPreference(KEY_DUMP_LOGCAT);
Preference clearMemoryCache = findPreference(KEY_CLEAR_MEMORY_CACHE);
Preference appLanguage = findPreference(KEY_APP_LANGUAGE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
appLanguage.setVisible(false);
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);
@@ -241,6 +253,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
clearMemoryCache.setOnPreferenceClickListener(this);
importData.setOnPreferenceClickListener(this);
exportData.setOnPreferenceClickListener(this);
backupFavorite.setOnPreferenceClickListener(this);
appLanguage.setOnPreferenceChangeListener(this);
}
@@ -287,14 +300,66 @@ public class AdvancedFragment extends BasePreferenceFragment {
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.setIndex(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.setIndex(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();
if (KEY_APP_LANGUAGE.equals(key)) {
if (KEY_APP_LANGUAGE.equals(key) && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
if ("system".equals(newValue)) {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.wrap(Resources.getSystem().getConfiguration().getLocales()));
} else {
@@ -70,7 +70,8 @@ public class BasePreferenceFragment extends PreferenceFragmentCompat
.replace(R.id.fragment, classObj)
.addToBackStack(null)
.commitAllowingStateLoss();
} catch (ClassNotFoundException | IllegalAccessException | java.lang.InstantiationException ignored) {
} catch (ClassNotFoundException | IllegalAccessException |
java.lang.InstantiationException ignored) {
}
return true;
}
@@ -25,7 +25,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
@@ -99,7 +99,7 @@ public class DownloadFragment extends BasePreferenceFragment {
if (KEY_DOWNLOAD_LOCATION.equals(key)) {
UniFile file = Settings.getDownloadLocation();
if (file != null && !UniFile.isFileUri(Settings.getDownloadLocation().getUri())) {
new MaterialAlertDialogBuilder(requireContext())
new BaseDialogBuilder(requireContext())
.setTitle(R.string.settings_download_download_location)
.setMessage(file.getUri().toString())
.setPositiveButton(R.string.pick_new_download_location, (dialogInterface, i) -> openDirPickerL())
@@ -1,94 +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.fragment;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.Preference;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhTagDatabase;
public class EhFragment extends BasePreferenceFragment {
@Override
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 detailSize = findPreference(Settings.KEY_DETAIL_SIZE);
Preference thumbSize = findPreference(Settings.KEY_THUMB_SIZE);
Preference showTagTranslations = findPreference(Settings.KEY_SHOW_TAG_TRANSLATIONS);
Preference tagTranslationsSource = findPreference("tag_translations_source");
theme.setOnPreferenceChangeListener(this);
gallerySite.setOnPreferenceChangeListener(this);
listMode.setOnPreferenceChangeListener(this);
detailSize.setOnPreferenceChangeListener(this);
thumbSize.setOnPreferenceChangeListener(this);
showTagTranslations.setOnPreferenceChangeListener(this);
blackDarkTheme.setOnPreferenceChangeListener(this);
if (!EhTagDatabase.isPossible(requireActivity())) {
getPreferenceScreen().removePreference(showTagTranslations);
getPreferenceScreen().removePreference(tagTranslationsSource);
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
if (Settings.KEY_THEME.equals(key)) {
AppCompatDelegate.setDefaultNightMode(Integer.parseInt((String) newValue));
requireActivity().recreate();
return true;
} else if (Settings.KEY_GALLERY_SITE.equals(key)) {
requireActivity().setResult(Activity.RESULT_OK);
return true;
} else if (Settings.KEY_LIST_MODE.equals(key)) {
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)) {
requireActivity().setResult(Activity.RESULT_OK);
} else if (Settings.KEY_SHOW_TAG_TRANSLATIONS.equals(key)) {
if (Boolean.TRUE.equals(newValue)) {
EhTagDatabase.update(requireActivity());
}
} else if (Settings.KEY_BLACK_DARK_THEME.equals(key)) {
if ((requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) > 0) {
requireActivity().recreate();
}
return true;
}
return true;
}
@Override
public int getFragmentTitle() {
return R.string.settings_eh;
}
}
@@ -0,0 +1,91 @@
/*
* 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.fragment
import android.app.Activity
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.Preference
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhTagDatabase
class EhFragment : BasePreferenceFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.eh_settings)
val account = findPreference<Preference>(Settings.KEY_ACCOUNT)
val theme = findPreference<Preference>(Settings.KEY_THEME)
val blackDarkTheme = findPreference<Preference>(Settings.KEY_BLACK_DARK_THEME)
val gallerySite = findPreference<Preference>(Settings.KEY_GALLERY_SITE)
val listMode = findPreference<Preference>(Settings.KEY_LIST_MODE)
val listThumbSize = findPreference<Preference>(Settings.KEY_LIST_THUMB_SIZE)
val detailSize = findPreference<Preference>(Settings.KEY_DETAIL_SIZE)
val thumbSize = findPreference<Preference>(Settings.KEY_THUMB_SIZE)
val showTagTranslations = findPreference<Preference>(Settings.KEY_SHOW_TAG_TRANSLATIONS)
val tagTranslationsSource = findPreference<Preference>("tag_translations_source")
Settings.getDisplayName()?.let { account?.summary = it }
theme!!.onPreferenceChangeListener = this
gallerySite!!.onPreferenceChangeListener = this
listMode!!.onPreferenceChangeListener = this
listThumbSize!!.onPreferenceChangeListener = this
detailSize!!.onPreferenceChangeListener = this
thumbSize!!.onPreferenceChangeListener = this
showTagTranslations!!.onPreferenceChangeListener = this
blackDarkTheme!!.onPreferenceChangeListener = this
if (!EhTagDatabase.isPossible(requireActivity())) {
preferenceScreen.removePreference(showTagTranslations)
preferenceScreen.removePreference(tagTranslationsSource!!)
}
}
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
val key = preference.key
if (Settings.KEY_THEME == key) {
AppCompatDelegate.setDefaultNightMode((newValue as String).toInt())
requireActivity().recreate()
return true
} else if (Settings.KEY_GALLERY_SITE == key) {
requireActivity().setResult(Activity.RESULT_OK)
return true
} else if (Settings.KEY_LIST_MODE == key) {
requireActivity().setResult(Activity.RESULT_OK)
return true
} else if (Settings.KEY_LIST_THUMB_SIZE == key) {
Settings.LIST_THUMB_SIZE_INITED = false
requireActivity().setResult(Activity.RESULT_OK)
return true
} else if (Settings.KEY_DETAIL_SIZE == key) {
requireActivity().setResult(Activity.RESULT_OK)
} else if (Settings.KEY_THUMB_SIZE == key) {
requireActivity().setResult(Activity.RESULT_OK)
} else if (Settings.KEY_SHOW_TAG_TRANSLATIONS == key) {
if (java.lang.Boolean.TRUE == newValue) {
EhTagDatabase.update(requireActivity())
}
} else if (Settings.KEY_BLACK_DARK_THEME == key) {
if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) {
requireActivity().recreate()
}
return true
}
return true
}
override fun getFragmentTitle(): Int {
return R.string.settings_eh
}
}
@@ -31,9 +31,9 @@ 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.app.BaseDialogBuilder
import com.hippo.easyrecyclerview.EasyRecyclerView
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhFilter
@@ -112,7 +112,7 @@ class FilterFragment : BaseFragment() {
}
private fun showTipDialog() {
MaterialAlertDialogBuilder(requireActivity())
BaseDialogBuilder(requireActivity())
.setTitle(R.string.filter)
.setMessage(R.string.filter_tip)
.setPositiveButton(android.R.string.ok, null)
@@ -120,7 +120,7 @@ class FilterFragment : BaseFragment() {
}
private fun showAddFilterDialog() {
val dialog = MaterialAlertDialogBuilder(requireActivity())
val dialog = BaseDialogBuilder(requireActivity())
.setTitle(R.string.add_filter)
.setView(R.layout.dialog_add_filter)
.setPositiveButton(R.string.add, null)
@@ -131,7 +131,7 @@ class FilterFragment : BaseFragment() {
private fun showDeleteFilterDialog(filter: Filter) {
val message = getString(R.string.delete_filter, filter.text)
MaterialAlertDialogBuilder(requireActivity())
BaseDialogBuilder(requireActivity())
.setMessage(message)
.setPositiveButton(R.string.delete) { _: DialogInterface?, which: Int ->
if (DialogInterface.BUTTON_POSITIVE != which) {
@@ -34,9 +34,9 @@ import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
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.app.BaseDialogBuilder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.Hosts;
import com.hippo.ehviewer.R;
@@ -149,7 +149,8 @@ public class HostsFragment extends BaseFragment
ip.setText(arguments.getString(KEY_IP));
}
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()).setView(view);
BaseDialogBuilder builder = new BaseDialogBuilder(requireContext());
builder.setView(view);
onCreateDialogBuilder(builder);
AlertDialog dialog = builder.create();
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
@@ -19,6 +19,7 @@
package com.hippo.ehviewer.ui.fragment
import android.os.Bundle
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
@@ -28,20 +29,45 @@ import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupporte
* Created by Mo10 on 2018/2/10.
*/
class PrivacyFragment : BasePreferenceFragment() {
private lateinit var requireUnlock: SwitchPreferenceCompat
private lateinit var unlockDelay: SeekBarPreference
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.privacy_settings)
requireUnlock = findPreference("require_unlock")!!
unlockDelay = findPreference("require_unlock_delay")!!
requireUnlock.setOnPreferenceChangeListener { _, newValue ->
unlockDelay.isEnabled = newValue as Boolean
setUnlockDelaySummary(unlockDelay.value)
true
}
unlockDelay.setOnPreferenceChangeListener { _, newValue ->
setUnlockDelaySummary(newValue as Int)
true
}
}
override fun onStart() {
super.onStart()
if (!isAuthenticationSupported(requireContext())) {
Settings.putSecurity(false)
findPreference<SwitchPreferenceCompat>("require_unlock")?.isEnabled = false
findPreference<SwitchPreferenceCompat>("require_unlock")?.isChecked = false
requireUnlock.isEnabled = false
requireUnlock.isChecked = false
unlockDelay.isEnabled = false
} else {
unlockDelay.isEnabled = requireUnlock.isChecked == true
setUnlockDelaySummary(unlockDelay.value)
}
}
override fun getFragmentTitle(): Int {
return R.string.settings_privacy
}
private fun setUnlockDelaySummary(value: Int) {
unlockDelay.summary = if (value == 0) {
getString(R.string.settings_privacy_require_unlock_delay_summary_immediately)
} else {
getString(R.string.settings_privacy_require_unlock_delay_summary, value.toString())
}
}
}
@@ -42,6 +42,8 @@ import androidx.fragment.app.FragmentActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import kotlin.Unit;
public abstract class BaseScene extends SceneFragment {
public static final int LENGTH_SHORT = 0;
@@ -56,7 +58,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();
@@ -107,21 +108,18 @@ public abstract class BaseScene extends SceneFragment {
}
}
public void showTip(@StringRes int id, int length) {
public Unit showTip(@StringRes int id, int length) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).showTip(id, length);
}
return null;
}
public boolean needShowLeftDrawer() {
return true;
}
public boolean needWhiteStatusBar() {
return true;
}
public int getNavCheckedItem() {
return 0;
}
@@ -176,14 +174,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 +203,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
@@ -31,14 +31,14 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.util.ClipboardUtil;
import com.hippo.util.ClipboardUtilKt;
import com.hippo.util.ExceptionUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.ViewUtils;
@@ -217,7 +217,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
hideSoftInput();
if (!checkIpbMemberId(ipbMemberId) || !(checkIpbPassHash(ipbPassHash))) {
new MaterialAlertDialogBuilder(context).setTitle(R.string.waring)
new BaseDialogBuilder(context).setTitle(R.string.waring)
.setMessage(R.string.wrong_cookie_warning)
.setPositiveButton(R.string.i_will_check_it, null)
.setNegativeButton(R.string.i_dont_think_so, (dialog, which) -> {
@@ -253,7 +253,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
private void fillCookiesFromClipboard() {
hideSoftInput();
String text = ClipboardUtil.getTextFromClipboard();
String text = ClipboardUtilKt.getTextFromClipboard(ClipboardUtilKt.getClipboardManager(getContext()), getContext());
if (text == null) {
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
return;
@@ -51,12 +51,10 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.app.BaseDialogBuilder;
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;
@@ -74,9 +72,7 @@ import com.hippo.ehviewer.download.DownloadService;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.io.UniFileInputStreamPipe;
import com.hippo.scene.Announcer;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.unifile.UniFile;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.view.ViewTransition;
@@ -85,14 +81,10 @@ import com.hippo.widget.LoadImageView;
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.ObjectUtils;
import com.hippo.yorozuya.ViewUtils;
import com.hippo.yorozuya.collect.LongList;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -425,7 +417,7 @@ public class DownloadsScene extends ToolbarScene
int id = item.getItemId();
if (id == R.id.action_filter) {
new MaterialAlertDialogBuilder(requireActivity())
new BaseDialogBuilder(requireActivity())
.setSingleChoiceItems(R.array.download_state, mType + 1, (dialog, which) -> {
mType = which - 1;
updateForLabel();
@@ -448,7 +440,7 @@ public class DownloadsScene extends ToolbarScene
openDrawer(Gravity.RIGHT);
return true;
} else if (id == R.id.action_reset_reading_progress) {
new MaterialAlertDialogBuilder(getContext())
new BaseDialogBuilder(getContext())
.setMessage(R.string.reset_reading_progress_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
@@ -521,7 +513,7 @@ public class DownloadsScene extends ToolbarScene
for (int i = 0, n = list.size(); i < n; i++) {
items[i + 2] = list.get(i).getLabel();
}
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.default_download_label)
.setItems(items, (dialog1, which) -> {
if (which == 0) {
@@ -708,7 +700,7 @@ public class DownloadsScene extends ToolbarScene
MoveDialogHelper helper = new MoveDialogHelper(labels, downloadInfoList);
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.download_move_dialog_title)
.setItems(labels, helper)
.show();
@@ -885,73 +877,6 @@ 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> {
private final LayoutInflater mInflater;
@@ -1230,8 +1155,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);
@@ -21,13 +21,10 @@ import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.text.InputType;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -45,9 +42,9 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.annotation.Implemented;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
@@ -77,7 +74,6 @@ import com.hippo.widget.ContentLayout;
import com.hippo.widget.FabLayout;
import com.hippo.widget.SearchBarMover;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ObjectUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.ViewUtils;
@@ -402,7 +398,7 @@ public class FavoritesScene extends BaseScene implements
items[1] = getString(R.string.local_favorites);
String[] favCat = Settings.getFavCat();
System.arraycopy(favCat, 0, items, 2, 10);
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.default_favorites_collection)
.setItems(items, (dialog, which) -> Settings.putDefaultFavSlot(which - 2)).show();
return true;
@@ -731,7 +727,7 @@ public class FavoritesScene extends BaseScene implements
}
case 4: { // Delete
DeleteDialogHelper helper = new DeleteDialogHelper();
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.delete_favorites_dialog_title)
.setMessage(getString(R.string.delete_favorites_dialog_message, mModifyGiList.size()))
.setPositiveButton(android.R.string.ok, helper)
@@ -745,7 +741,7 @@ public class FavoritesScene extends BaseScene implements
String[] array = new String[11];
array[0] = getString(R.string.local_favorites);
System.arraycopy(Settings.getFavCat(), 0, array, 1, 10);
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.move_favorites_dialog_title)
.setItems(array, helper)
.setOnCancelListener(helper)
@@ -54,8 +54,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
@@ -72,7 +72,7 @@ import com.hippo.ehviewer.client.parser.VoteCommentParser;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.text.URLImageGetter;
import com.hippo.util.ClipboardUtil;
import com.hippo.util.ClipboardUtilKt;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.ReadableTime;
import com.hippo.util.TextUrl;
@@ -184,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();
@@ -297,7 +290,7 @@ public final class GalleryCommentsScene extends ToolbarScene
}
}
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
BaseDialogBuilder builder = new BaseDialogBuilder(context);
context = builder.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
EasyRecyclerView rv = (EasyRecyclerView) inflater.inflate(R.layout.dialog_recycler_view, null);
@@ -354,15 +347,14 @@ public final class GalleryCommentsScene extends ToolbarScene
menuId.add(R.id.check_vote_status);
}
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
if (which < 0 || which >= menuId.size()) {
return;
}
int id = menuId.get(which);
if (id == R.id.copy) {
ClipboardUtil.addTextToClipboard(comment.comment);
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), comment.comment, false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
} else if (id == R.id.vote_up) {
voteComment(comment.id, 1);
} else if (id == R.id.vote_down) {
@@ -62,9 +62,9 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.beerbelly.BeerBelly;
import com.hippo.ehviewer.AppConfig;
@@ -103,7 +103,7 @@ import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.text.URLImageGetter;
import com.hippo.util.AppHelper;
import com.hippo.util.ClipboardUtil;
import com.hippo.util.ClipboardUtilKt;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.ReadableTime;
import com.hippo.view.ViewTransition;
@@ -260,7 +260,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
result -> {
if (result && mGalleryDetail != null) {
TorrentListDialogHelper helper = new TorrentListDialogHelper();
Dialog dialog = new MaterialAlertDialogBuilder(requireActivity())
Dialog dialog = new BaseDialogBuilder(requireActivity())
.setTitle(R.string.torrents)
.setView(R.layout.dialog_torrent_list)
.setOnDismissListener(helper)
@@ -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;
@@ -1218,7 +1203,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
if (EhApplication.getDownloadManager(context).getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
CommonOperations.startDownload(activity, galleryInfo, false);
} else {
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.download_remove_dialog_title)
.setMessage(getString(R.string.download_remove_dialog_message, galleryInfo.title))
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> EhApplication.getDownloadManager(context).deleteDownload(galleryInfo.gid))
@@ -1244,7 +1229,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
for (GalleryInfo newerVersion : mGalleryDetail.newerVersions) {
titles.add(getString(R.string.newer_version_title, newerVersion.title, newerVersion.posted));
}
new MaterialAlertDialogBuilder(requireContext())
new BaseDialogBuilder(requireContext())
.setItems(titles.toArray(new CharSequence[0]), (dialog, which) -> {
GalleryInfo newerVersion = mGalleryDetail.newerVersions.get(which);
Bundle args = new Bundle();
@@ -1290,7 +1275,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
requestStoragePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
} else {
TorrentListDialogHelper helper = new TorrentListDialogHelper();
Dialog dialog = new MaterialAlertDialogBuilder(context)
Dialog dialog = new BaseDialogBuilder(context)
.setTitle(R.string.torrents)
.setView(R.layout.dialog_torrent_list)
.setOnDismissListener(helper)
@@ -1307,7 +1292,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
return;
}
ArchiveListDialogHelper helper = new ArchiveListDialogHelper();
Dialog dialog = new MaterialAlertDialogBuilder(context)
Dialog dialog = new BaseDialogBuilder(context)
.setTitle(R.string.dialog_archive_title)
.setView(R.layout.dialog_archive_list)
.setOnDismissListener(helper)
@@ -1322,7 +1307,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
return;
}
RateDialogHelper helper = new RateDialogHelper();
Dialog dialog = new MaterialAlertDialogBuilder(context)
Dialog dialog = new BaseDialogBuilder(context)
.setTitle(R.string.rate)
.setView(R.layout.dialog_rate)
.setNegativeButton(android.R.string.cancel, null)
@@ -1384,7 +1369,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
return;
}
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setMessage(getString(R.string.filter_the_uploader, uploader))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Filter filter = new Filter();
@@ -1404,7 +1389,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
return;
}
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setMessage(getString(R.string.filter_the_tag, tag))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Filter filter = new Filter();
@@ -1453,7 +1438,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
menuId.add(R.id.vote_down);
}
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(tag)
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
if (which < 0 || which >= menuId.size()) {
@@ -1469,11 +1454,9 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
} else if (id == R.id.add_filter) {
showFilterTagDialog(tag);
} else if (id == R.id.copy) {
ClipboardUtil.addTextToClipboard(tag);
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), tag, false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
} else if (id == R.id.copy_trans) {
ClipboardUtil.addTextToClipboard(tv.getText().toString());
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), tv.getText().toString(), false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
}
}).show();
}
@@ -35,7 +35,7 @@ import com.hippo.ehviewer.UrlOpener;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.util.ClipboardUtil;
import com.hippo.util.ClipboardUtilKt;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.ViewUtils;
@@ -188,14 +188,11 @@ public final class GalleryInfoScene extends ToolbarScene {
if (position == INDEX_PARENT) {
UrlOpener.openUrl(context, mValues.get(position), true);
} else {
ClipboardUtil.addTextToClipboard(mValues.get(position));
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), mValues.get(position), false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
if (position == INDEX_URL) {
// Save it to avoid detect the gallery
Settings.putClipboardTextHashCode(mValues.get(position).hashCode());
}
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
}
return true;
} else {
@@ -40,6 +40,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.PickVisualMediaRequest;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -54,8 +55,8 @@ import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
@@ -149,8 +150,8 @@ public final class GalleryListScene extends BaseScene
private EasyRecyclerView mRecyclerView;
@Nullable
private SearchLayout mSearchLayout;
ActivityResultLauncher<String[]> selectImageLauncher = registerForActivityResult(
new ActivityResultContracts.OpenDocument(),
ActivityResultLauncher<PickVisualMediaRequest> selectImageLauncher = registerForActivityResult(
new ActivityResultContracts.PickVisualMedia(),
result -> mSearchLayout.setImageUri(result));
@Nullable
private SearchBar mSearchBar;
@@ -214,7 +215,7 @@ public final class GalleryListScene extends BaseScene
}
};
// Double click back exit
private long mPressBackTime = 0;
private final long mPressBackTime = 0;
private boolean mHasFirstRefresh = false;
private int mNavCheckedId = 0;
private DownloadManager mDownloadManager;
@@ -662,7 +663,7 @@ public final class GalleryListScene extends BaseScene
if (null == context) {
return;
}
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
final BaseDialogBuilder builder = new BaseDialogBuilder(context);
builder.setMessage(R.string.add_quick_search_tip);
builder.setTitle(R.string.readme);
builder.show();
@@ -949,7 +950,7 @@ public final class GalleryListScene extends BaseScene
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
};
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(EhUtils.getSuitableTitle(gi))
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
switch (which) {
@@ -961,7 +962,7 @@ public final class GalleryListScene extends BaseScene
break;
case 1: // Download
if (downloaded) {
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.download_remove_dialog_title)
.setMessage(getString(R.string.download_remove_dialog_message, gi.title))
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> mDownloadManager.deleteDownload(gi.gid))
@@ -988,7 +989,7 @@ public final class GalleryListScene extends BaseScene
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.download_move_dialog_title)
.setItems(labels, helper)
.show();
@@ -1334,7 +1335,9 @@ public final class GalleryListScene extends BaseScene
@Override
public void onSelectImage() {
try {
selectImageLauncher.launch(new String[]{"image/*"});
PickVisualMediaRequest.Builder builder = new PickVisualMediaRequest.Builder();
builder.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE);
selectImageLauncher.launch(builder.build());
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
@@ -35,8 +35,8 @@ import androidx.annotation.Nullable;
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.app.BaseDialogBuilder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
@@ -211,7 +211,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
int pages = mHelper.getPages();
if (pages > 1 && mHelper.canGoTo()) {
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setTitle(R.string.go_to)
AlertDialog dialog = new BaseDialogBuilder(context).setTitle(R.string.go_to)
.setView(R.layout.dialog_go_to)
.setPositiveButton(android.R.string.ok, null)
.create();
@@ -35,7 +35,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.app.BaseDialogBuilder
import com.hippo.easyrecyclerview.EasyRecyclerView
import com.hippo.easyrecyclerview.FastScroller
import com.hippo.easyrecyclerview.HandlerDrawable
@@ -200,7 +200,7 @@ class HistoryScene : ToolbarScene() {
@SuppressLint("NotifyDataSetChanged")
private fun showClearAllDialog() {
MaterialAlertDialogBuilder(requireContext())
BaseDialogBuilder(requireContext())
.setMessage(R.string.clear_all_history)
.setPositiveButton(R.string.clear_all) { _: DialogInterface?, which: Int ->
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
@@ -260,7 +260,7 @@ class HistoryScene : ToolbarScene() {
R.drawable.v_download_x24,
if (favourited) R.drawable.v_heart_broken_x24 else R.drawable.v_heart_x24,
)
MaterialAlertDialogBuilder(context)
BaseDialogBuilder(context)
.setTitle(EhUtils.getSuitableTitle(gi))
.setAdapter(
SelectItemWithIconAdapter(
@@ -277,7 +277,7 @@ class HistoryScene : ToolbarScene() {
startActivity(intent)
}
1 -> if (downloaded) {
MaterialAlertDialogBuilder(context)
BaseDialogBuilder(context)
.setTitle(R.string.download_remove_dialog_title)
.setMessage(
getString(
@@ -319,7 +319,7 @@ class HistoryScene : ToolbarScene() {
}
val labels = labelList.toTypedArray()
val helper = MoveDialogHelper(labels, gi)
MaterialAlertDialogBuilder(context)
BaseDialogBuilder(context)
.setTitle(R.string.download_move_dialog_title)
.setItems(labels, helper)
.show()
@@ -30,8 +30,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
@@ -333,7 +333,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
return;
}
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setTitle(R.string.sign_in_failed)
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.sign_in_failed_tip))
.setPositiveButton(R.string.get_it, null)
@@ -20,7 +20,6 @@ import android.os.Bundle;
import android.util.Log;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.scene.Announcer;
/**
@@ -87,12 +87,6 @@ public abstract class ToolbarScene extends BaseScene {
}
}
@Override
public boolean needWhiteStatusBar() {
return false;
}
public int getMenuResId() {
return 0;
}
@@ -26,7 +26,7 @@ import android.webkit.WebView;
import android.widget.EditText;
import android.widget.TextView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.R;
public class DialogWebChromeClient extends WebChromeClient {
@@ -39,7 +39,7 @@ public class DialogWebChromeClient extends WebChromeClient {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
new MaterialAlertDialogBuilder(view.getContext())
new BaseDialogBuilder(view.getContext())
.setMessage(message)
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
.setOnCancelListener(dialog -> result.cancel())
@@ -49,7 +49,7 @@ public class DialogWebChromeClient extends WebChromeClient {
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
new MaterialAlertDialogBuilder(view.getContext())
new BaseDialogBuilder(view.getContext())
.setMessage(message)
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel())
@@ -67,7 +67,7 @@ public class DialogWebChromeClient extends WebChromeClient {
final EditText valueView = promptView.findViewById(R.id.value);
valueView.setText(defaultValue);
new MaterialAlertDialogBuilder(context)
new BaseDialogBuilder(context)
.setView(promptView)
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm(valueView.getText().toString()))
.setOnCancelListener(dialog -> result.cancel())
@@ -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)
}
}
@@ -46,7 +46,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhTagDatabase;
@@ -668,7 +668,7 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
@Override
public boolean onLongClick() {
new MaterialAlertDialogBuilder(getContext())
new BaseDialogBuilder(getContext())
.setMessage(getContext().getString(R.string.delete_search_history, mKeyword))
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.delete, (dialog, which) -> {
@@ -34,9 +34,9 @@ import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.hippo.app.BaseDialogBuilder
import com.hippo.easyrecyclerview.EasyRecyclerView
import com.hippo.easyrecyclerview.MarginItemDecoration
import com.hippo.easyrecyclerview.SimpleHolder
@@ -217,7 +217,7 @@ class SearchLayout @JvmOverloads constructor(
override fun onClick(v: View) {
if (mNormalSearchModeHelp === v) {
MaterialAlertDialogBuilder(context)
BaseDialogBuilder(context)
.setMessage(R.string.search_tip)
.show()
}
+45 -18
View File
@@ -20,6 +20,8 @@ import android.graphics.Bitmap;
import androidx.annotation.Nullable;
import com.hippo.Native;
import java.io.FileDescriptor;
import java.util.ArrayList;
@@ -33,6 +35,10 @@ public final class Image {
private static boolean needEvictAll = false;
private long mNativePtr;
private boolean needRecycle = false;
private Integer nativeProcessCnt = 0;
private Image(long nativePtr) {
if (needEvictAll)
evictAll();
@@ -60,7 +66,7 @@ public final class Image {
*/
@Nullable
public static Image decode(FileDescriptor fd, boolean partially) {
return newFromAddr(nativeDecode(fd));
return newFromAddr(nativeDecode(Native.getFd(fd)));
}
/**
@@ -68,7 +74,7 @@ public final class Image {
*/
@Nullable
public static Image decode(Integer fd, boolean partially) {
return newFromAddr(nativeDecodeFdInt(fd));
return newFromAddr(nativeDecode(fd));
}
/**
@@ -95,9 +101,7 @@ public final class Image {
return imageList.size();
}
private static native long nativeDecode(FileDescriptor fd);
private static native long nativeDecodeFdInt(int fd);
private static native long nativeDecode(int fd);
private static native long nativeDecodeAddr(long addr);
@@ -127,30 +131,43 @@ public final class Image {
* Return the format of the image
*/
public int getFormat() {
checkRecycled();
return nativeGetFormat(mNativePtr);
checkRecycledAndLock();
int ret = nativeGetFormat(mNativePtr);
releaseNativeLock();
return ret;
}
/**
* Return the width of the image
*/
public int getWidth() {
checkRecycled();
return nativeGetWidth(mNativePtr);
checkRecycledAndLock();
int ret = nativeGetWidth(mNativePtr);
releaseNativeLock();
return ret;
}
/**
* Return the height of the image
*/
public int getHeight() {
checkRecycled();
return nativeGetHeight(mNativePtr);
checkRecycledAndLock();
int ret = nativeGetHeight(mNativePtr);
releaseNativeLock();
return ret;
}
private void checkRecycled() {
private synchronized void checkRecycledAndLock() {
if (mNativePtr == 0) {
throw new IllegalStateException("The image is recycled.");
}
nativeProcessCnt++;
}
private synchronized void releaseNativeLock() {
nativeProcessCnt--;
if (needRecycle)
recycle();
}
/**
@@ -158,8 +175,9 @@ public final class Image {
*/
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
int width, int height, boolean fillBlank, int defaultColor) {
checkRecycled();
checkRecycledAndLock();
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height);
releaseNativeLock();
}
/**
@@ -168,24 +186,27 @@ public final class Image {
* width * height must <= 512 * 512 or do nothing
*/
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
checkRecycled();
checkRecycledAndLock();
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height);
releaseNativeLock();
}
/**
* Move to next frame. Do nothing for non-animation image
*/
public void advance() {
checkRecycled();
checkRecycledAndLock();
nativeAdvance(mNativePtr);
releaseNativeLock();
}
/**
* Return current frame delay. 0 for non-animation image
*/
public int getDelay() {
checkRecycled();
checkRecycledAndLock();
int delay = nativeGetDelay(mNativePtr);
releaseNativeLock();
return delay <= 10 ? 100 : delay;
}
@@ -193,8 +214,10 @@ public final class Image {
* Return is the image opaque
*/
public boolean isOpaque() {
checkRecycled();
return nativeIsOpaque(mNativePtr);
checkRecycledAndLock();
boolean ret = nativeIsOpaque(mNativePtr);
releaseNativeLock();
return ret;
}
/**
@@ -204,6 +227,10 @@ public final class Image {
*/
public synchronized void recycle() {
if (mNativePtr != 0) {
if (nativeProcessCnt != 0) {
needRecycle = true;
return;
}
nativeRecycle(mNativePtr);
mNativePtr = 0;
imageList.remove(this);
@@ -37,7 +37,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.R;
/**
@@ -255,7 +255,7 @@ public abstract class DialogPreference extends Preference implements
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
mBuilder = new MaterialAlertDialogBuilder(context)
mBuilder = new BaseDialogBuilder(context)
.setTitle(mDialogTitle)
.setIcon(mDialogIcon)
.setPositiveButton(mPositiveButtonText, this)
@@ -1,43 +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.text;
import android.graphics.drawable.Drawable;
import android.text.Html;
import com.hippo.conaco.Conaco;
import com.hippo.drawable.UnikeryDrawable;
import com.hippo.image.ImageBitmap;
import com.hippo.widget.ObservedTextView;
public class URLImageGetter implements Html.ImageGetter {
private final ObservedTextView mTextView;
private final Conaco<ImageBitmap> mConaco;
public URLImageGetter(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
mTextView = textView;
mConaco = conaco;
}
@Override
public Drawable getDrawable(String source) {
UnikeryDrawable drawable = new UnikeryDrawable(mTextView, mConaco);
drawable.load(source);
return drawable;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 Hippo Seven
* 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.
@@ -13,15 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.text
package com.hippo.scene;
import android.graphics.drawable.Drawable
import android.text.Html.ImageGetter
import com.hippo.conaco.Conaco
import com.hippo.drawable.UnikeryDrawable
import com.hippo.image.ImageBitmap
import com.hippo.widget.ObservedTextView
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);
}
class URLImageGetter(
private val mTextView: ObservedTextView,
private val mConaco: Conaco<ImageBitmap>
) : ImageGetter {
override fun getDrawable(source: String): Drawable {
return UnikeryDrawable(mTextView, mConaco).apply { load(source) }
}
}
@@ -1,126 +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.util;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.textclassifier.TextClassifier;
import androidx.annotation.Nullable;
public class ClipboardUtil {
private static final String TAG = "ClipboardUtil";
@Nullable
private static ClipboardManager clipboardManager;
@SuppressLint("StaticFieldLeak")
private static Context sContext;
public static void initialize(Context context) {
sContext = context;
clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboardManager == null) {
Log.e(TAG, "This device has no clipboard!");
}
}
public static void addTextToClipboard(String text) {
if (clipboardManager != null) {
try {
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static String getTextFromClipboard() {
if (clipboardManager != null) {
try {
if (!clipboardManager.hasPrimaryClip()) {
return null;
}
ClipData primaryClip = clipboardManager.getPrimaryClip();
if (primaryClip == null) {
return null;
}
ClipData.Item item = primaryClip.getItemAt(0);
if (item == null) {
return null;
}
String string = String.valueOf(item.coerceToText(sContext));
if (!TextUtils.isEmpty(string)) {
return string;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
public static String getUrlFromClipboard() {
if (clipboardManager != null) {
try {
if (!clipboardManager.hasPrimaryClip()) {
return null;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ClipDescription primaryClipDescription = clipboardManager.getPrimaryClipDescription();
if (primaryClipDescription != null) {
if (primaryClipDescription.getClassificationStatus() == ClipDescription.CLASSIFICATION_COMPLETE) {
if (primaryClipDescription.getConfidenceScore(TextClassifier.TYPE_URL) <= 0) {
return null;
}
}
}
}
ClipData primaryClip = clipboardManager.getPrimaryClip();
if (primaryClip == null) {
return null;
}
ClipData.Item item = primaryClip.getItemAt(0);
if (item == null) {
return null;
}
String string = String.valueOf(item.coerceToText(sContext));
if (!TextUtils.isEmpty(string)) {
return string;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
}
@@ -0,0 +1,61 @@
/*
* 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.util
import android.content.ClipData
import android.content.ClipDescription
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.PersistableBundle
import android.text.TextUtils
import android.view.textclassifier.TextClassifier
fun Context.getClipboardManager(): ClipboardManager {
return getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
}
fun ClipboardManager.addTextToClipboard(text: String?, isSensitive: Boolean, showTip: () -> Unit) {
setPrimaryClip(ClipData.newPlainText(null, text).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && isSensitive)
description.extras = PersistableBundle().apply {
putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
}
})
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
showTip()
}
fun ClipboardManager.getTextFromClipboard(context: Context): String? {
val item = primaryClip?.getItemAt(0)
val string = item?.coerceToText(context).toString()
return if (!TextUtils.isEmpty(string)) string else null
}
fun ClipboardManager.getUrlFromClipboard(context: Context): String? {
if (primaryClipDescription?.classificationStatus == ClipDescription.CLASSIFICATION_COMPLETE) {
if ((primaryClipDescription?.getConfidenceScore(TextClassifier.TYPE_URL)
?.let { it <= 0 }) == true
) {
return null
}
}
val item = primaryClip?.getItemAt(0)
val string = item?.coerceToText(context).toString()
return if (!TextUtils.isEmpty(string)) string else null
}
@@ -102,27 +102,11 @@ public class ContentLayout extends FrameLayout {
mFastScroller = mContentView.findViewById(R.id.fast_scroller);
mRecyclerView = mRefreshLayout.findViewById(R.id.recycler_view);
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(context.getTheme(), androidx.appcompat.R.attr.colorPrimary));
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable drawable = new HandlerDrawable();
drawable.setColor(ResourcesKt.resolveColor(context.getTheme(), androidx.appcompat.R.attr.colorPrimary));
mFastScroller.setHandlerDrawable(drawable);
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);
Resources resources = getResources();
mBottomProgress.setIndicatorColor(
resources.getColor(R.color.loading_indicator_red),
resources.getColor(R.color.loading_indicator_blue),
resources.getColor(R.color.loading_indicator_green),
resources.getColor(R.color.loading_indicator_orange));
mBottomProgress.setIndeterminateAnimationType(LinearProgressIndicator.INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS);
mRecyclerViewOriginBottom = mRecyclerView.getPaddingBottom();
mFastScrollerOriginBottom = mFastScroller.getPaddingBottom();
}
@@ -34,7 +34,6 @@ import androidx.core.content.ContextCompat;
import com.hippo.conaco.Conaco;
import com.hippo.conaco.ConacoTask;
import com.hippo.conaco.DataContainer;
import com.hippo.conaco.Unikery;
import com.hippo.drawable.PreciselyClipDrawable;
import com.hippo.ehviewer.EhApplication;
@@ -56,7 +55,6 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
private Conaco<ImageBitmap> mConaco;
private String mKey;
private String mUrl;
private DataContainer mContainer;
private boolean mUseNetwork;
private int mOffsetX = Integer.MIN_VALUE;
private int mOffsetY = Integer.MIN_VALUE;
@@ -109,7 +107,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
if (mFailed) {
onFailure();
} else if (mConaco.isLoading(this)) {
load(mKey, mUrl, mContainer, mUseNetwork);
load(mKey, mUrl, mUseNetwork);
}
}
}
@@ -209,15 +207,11 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
}
public void load(String key, String url) {
load(key, url, null, true);
load(key, url, true);
}
public void load(String key, String url, boolean useNetwork) {
load(key, url, null, useNetwork);
}
public void load(String key, String url, DataContainer container, boolean useNetwork) {
if (url == null || (key == null && container == null)) {
if (url == null || (key == null)) {
return;
}
@@ -226,14 +220,12 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
mKey = key;
mUrl = url;
mContainer = container;
mUseNetwork = useNetwork;
ConacoTask.Builder<ImageBitmap> builder = new ConacoTask.Builder<ImageBitmap>()
.setUnikery(this)
.setKey(key)
.setUrl(url)
.setDataContainer(container)
.setUseNetwork(useNetwork);
mConaco.load(builder);
}
@@ -255,7 +247,6 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
mConaco.cancel(this);
mKey = null;
mUrl = null;
mContainer = null;
clearDrawable();
}
@@ -326,7 +317,6 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
// Can't retry, so release
mKey = null;
mUrl = null;
mContainer = null;
}
}
@@ -363,12 +353,12 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
@Override
public void onClick(@NonNull View v) {
load(mKey, mUrl, mContainer, true);
load(mKey, mUrl, true);
}
@Override
public boolean onLongClick(@NonNull View v) {
load(mKey, mUrl, mContainer, true);
load(mKey, mUrl, true);
return true;
}
@@ -1,3 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 GeneralPublic 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
@@ -1,3 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 GeneralPublic 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
@@ -105,7 +105,7 @@
<TextView
android:id="@+id/language"
style="@style/TextAppearance.Material3.BodySmall"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
@@ -115,7 +115,7 @@
<TextView
android:id="@+id/size"
style="@style/TextAppearance.Material3.BodySmall"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@@ -129,7 +129,7 @@
<TextView
android:id="@+id/favoredTimes"
style="@style/TextAppearance.Material3.BodySmall"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -140,7 +140,7 @@
<TextView
android:id="@+id/pages"
style="@style/TextAppearance.Material3.BodySmall"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@@ -154,7 +154,7 @@
<TextView
android:id="@+id/posted"
style="@style/TextAppearance.Material3.BodySmall"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
+17 -9
View File
@@ -29,12 +29,20 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height"
app:alwaysCutAndScale="true"
app:retryType="longClick" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.ResizeableFixedThumb
android:id="@+id/thumb"
android:layout_width="0dp"
android:layout_height="0dp"
app:alwaysCutAndScale="true"
app:retryType="longClick"/>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/title"
@@ -45,7 +53,7 @@
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginRight="8dp"
android:layout_toEndOf="@id/thumb" />
android:layout_toEndOf="@id/thumb_card" />
<TextView
android:id="@+id/uploader"
@@ -70,7 +78,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@id/title"
android:layout_alignBottom="@id/thumb"
android:layout_alignBottom="@id/thumb_card"
android:layout_marginBottom="8dp"
android:paddingLeft="8dp"
android:paddingTop="2dp"
@@ -83,7 +91,7 @@
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/thumb"
android:layout_alignBottom="@+id/thumb_card"
android:layout_alignParentEnd="true"
android:paddingEnd="8dp">
@@ -35,12 +35,14 @@
<TextView
android:id="@+id/user"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackground" />
<TextView
android:id="@+id/time"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
@@ -35,10 +35,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.FixedThumb
<com.hippo.ehviewer.widget.ResizeableFixedThumb
android:id="@+id/thumb"
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height"
android:layout_width="0dp"
android:layout_height="0dp"
app:alwaysCutAndScale="true"
app:retryType="click"/>
@@ -60,6 +60,7 @@
android:layout_gravity="bottom"
android:indeterminate="true"
android:visibility="gone"
app:fitsSystemWindowsInsets="end"
app:hideAnimationBehavior="inward"
app:showAnimationBehavior="outward" />
-4
View File
@@ -15,10 +15,6 @@
-->
<resources>
<!-- divider -->
<color name="divider">#20ffffff</color>
<color name="navigation_bar_divider">#1fffffff</color>
<!-- Lock pattern view -->
</resources>
+13 -3
View File
@@ -391,7 +391,7 @@
<string name="hosts">自定义 hosts.txt</string>
<string name="hosts_tip">hosts.txt</string>
<string name="hosts_host">Host</string>
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
<string name="add_host">添加 host</string>
<string name="add_host_add">添加</string>
<string name="edit_host">编辑 host</string>
@@ -421,8 +421,8 @@
<string name="settings_eh_identity_cookies">身份 Cookie</string>
<string name="settings_eh_identity_cookies_summary">可用于登录</string>
<string name="settings_eh_identity_cookies_signed">身份 Cookie 可用于登录该账号。&lt;br&gt;&lt;b&gt;注意数据安全&lt;/b&gt;&lt;br&gt;&lt;br&gt;%s</string>
<string name="settings_eh_identity_cookies_tourist">未登录</string>
<string name="settings_eh_identity_cookies_copy">复制</string>
<string name="settings_eh_identity_cookies_tourist">游客模式</string>
<string name="settings_eh_identity_cookies_copy">复制 Cookies</string>
<string name="settings_u_config">EHentai 设置</string>
<string name="settings_u_config_summary">EHentai 网站上的设置</string>
<string name="settings_my_tags">我的标签</string>
@@ -761,6 +761,9 @@
<string name="show_library_version">依赖库之版本</string>
<string name="settings_privacy_require_unlock">需要解锁</string>
<string name="settings_privacy_require_unlock_delay">锁定延迟</string>
<string name="settings_privacy_require_unlock_delay_summary">离开应用并在 %s 分钟内返回时不需要解锁</string>
<string name="settings_privacy_require_unlock_delay_summary_immediately">无论何时回到应用均要求解锁</string>
<string name="proxy_type_name">代理类型</string>
<string name="filter_label">屏蔽项类型</string>
@@ -768,4 +771,11 @@
<string name="archive_need_passwd">归档文件需要密码</string>
<string name="passwd_wrong">密码错误</string>
<string name="passwd_cannot_be_empty">密码不能为空</string>
<string name="list_tile_thumb_size">详情模式下缩略图大小</string>
<string name="backup_favorite">备份收藏列表</string>
<string name="backup_favorite_summary">备份云端收藏列表到本地</string>
<string name="backup_favorite_failed">备份收藏列表失败</string>
<string name="backup_favorite_success">备份收藏列表成功</string>
<string name="backup_favorite_start">正在备份收藏列表</string>
<string name="account_name">账户</string>
</resources>
+18 -10
View File
@@ -95,7 +95,7 @@
<string name="kokomade_tip">設定->Eh->圖庫站台->e-hentai</string>
<string name="app_waring">本應用程式中內容來自網際網路,部分內容可能對您的生理及心理造成難以恢復的傷害。本應用程式的作者不會對由其所造成的任何影響負責。\n\n未成年人應在監護人指導下使用本應用程式。</string>
<string name="app_waring">本應用程式中內容來自網際網路,部分內容可能對您的生理及心理造成難以恢復的傷害。本應用程式的作者不會對由其所造成的任何影響負責。\n\n未成年人應在監護人指導下使用本應用程式。\n\n繼續使用即代表您同意上述條款。</string>
<string name="error_username_cannot_empty">使用者名稱欄位不能為空</string>
<string name="error_password_cannot_empty">密碼欄位不能為空</string>
<string name="skip_signing_in">略過登入</string>
@@ -391,7 +391,7 @@
<string name="hosts">自訂hosts.txt</string>
<string name="hosts_tip">hosts.txt</string>
<string name="hosts_host">Host</string>
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
<string name="add_host">加入 host</string>
<string name="add_host_add">加入</string>
<string name="edit_host">編輯 host</string>
@@ -421,8 +421,8 @@
<string name="settings_eh_identity_cookies">身份識別 Cookie</string>
<string name="settings_eh_identity_cookies_summary">可用於登入</string>
<string name="settings_eh_identity_cookies_signed">身份 Cookie 可用於登入該帳號。&lt;br&gt;&lt;b&gt;請注意資料安全&lt;/b&gt;&lt;br&gt;&lt;br&gt;%s</string>
<string name="settings_eh_identity_cookies_tourist">未登入</string>
<string name="settings_eh_identity_cookies_copy">複製</string>
<string name="settings_eh_identity_cookies_tourist">訪客模式</string>
<string name="settings_eh_identity_cookies_copy">複製 Cookies</string>
<string name="settings_u_config">EHentai 設定</string>
<string name="settings_u_config_summary">EHentai 網站上的設定</string>
<string name="settings_my_tags">我的標籤</string>
@@ -735,7 +735,7 @@
<string name="delete_search_history">從搜索記錄中刪除“%s”?</string>
<string name="settings_advanced_dns_over_http_title">安全 DNS(DoH)</string>
<string name="settings_advanced_dns_over_http_summary">解決某些地區的 DNS 汙染問題</string>
<string name="settings_advanced_domain_fronting_title">域名前置</string>
<string name="settings_advanced_domain_fronting_title">域名前綴</string>
<string name="settings_advanced_domain_fronting_summary">繞過 SNI 封鎖</string>
<string name="read_theme">色彩主題</string>
<string name="follow_app">跟隨應用</string>
@@ -754,19 +754,27 @@
<string name="reset_download_location">恢復預設路徑</string>
<string name="pick_new_download_location">選擇新路徑</string>
<string name="dont_show_again">不再顯示</string>
<string name="open_settings">打開設置</string>
<string name="open_settings">打開設定</string>
<string name="app_link_not_verified_message">對於 Android 12 及更新的版本,您需要手動增加連結到已驗證連結才能在 EhViewer 中打開 E-Hentai 連結。</string>
<string name="app_link_not_verified_title">應用連結未驗證</string>
<string name="open_by_default">預設打開</string>
<string name="show_library_version">依賴庫之版本</string>
<string name="settings_privacy_require_unlock">需要解鎖</string>
<string name="settings_privacy_require_unlock_delay">鎖定延遲</string>
<string name="settings_privacy_require_unlock_delay_summary">離開程式並在 %s 分鐘內返回時不需要解鎖</string>
<string name="settings_privacy_require_unlock_delay_summary_immediately">無論何時回到程式均要求解鎖</string>
<string name="proxy_type_name">代理類型</string>
<string name="filter_label">屏蔽項類型</string>
<string name="filter_label">隱藏項類型</string>
<string name="archive_passwd">歸檔文件密碼</string>
<string name="archive_need_passwd">歸檔文件需要密碼</string>
<string name="archive_passwd">封存文件密碼</string>
<string name="archive_need_passwd">封存文件需要密碼</string>
<string name="passwd_wrong">密碼錯誤</string>
<string name="passwd_cannot_be_empty">密碼不能為空</string>
<string name="list_tile_thumb_size">詳情模式下縮圖大小</string>
<string name="backup_favorite">備份收藏列表</string>
<string name="backup_favorite_summary">備份雲端收藏列表到本機</string>
<string name="backup_favorite_failed">備份收藏列表失敗</string>
<string name="backup_favorite_success">備份收藏列表成功</string>
<string name="backup_favorite_start">正在備份收藏列表</string>
</resources>
-4
View File
@@ -134,10 +134,6 @@
<attr name="url" format="string" />
</declare-styleable>
<declare-styleable name="SeekBarPreference">
<attr name="max" format="integer" />
</declare-styleable>
<declare-styleable name="FastScroller">
<attr name="handler" format="reference" />
<attr name="draggable" format="boolean" />
-33
View File
@@ -18,43 +18,10 @@
<color name="primary_drawable_black">#ccffffff</color>
<color name="secondary_drawable_black">#3dffffff</color>
<color name="red_500">#fff44336</color>
<color name="purple_a700">#ffaa00ff</color>
<color name="cyan_600">#ff00acc1</color>
<color name="light_green_600">#ff7cb342</color>
<color name="yellow_800">#fff9a825</color>
<color name="deep_orange_400">#ffff7043</color>
<color name="brown_500">#ff795548</color>
<color name="loading_indicator_red">#ffe53935</color>
<color name="loading_indicator_purple">@color/purple_a700</color>
<color name="loading_indicator_blue">#ff1d87e4</color>
<color name="loading_indicator_cyan">@color/cyan_600</color>
<color name="loading_indicator_green">@color/light_green_600</color>
<color name="loading_indicator_yellow">@color/yellow_800</color>
<color name="loading_indicator_orange">@color/deep_orange_400</color>
<color name="navigation_bar_divider">#1f000000</color>
<!-- Red -->
<!-- Orange -->
<!-- Yellow 700 -->
<!-- Green -->
<!-- Light Green -->
<!-- Blue -->
<!-- Indigo -->
<!-- Purple -->
<!-- Deep Purple 300 -->
<!-- Pink 300 -->
<!-- divider -->
<color name="divider">#20000000</color>
<color name="divider_black">#20ffffff</color>
<!-- shadow -->
<!-- Gallery Activity -->
<!-- Lock pattern view -->
<!--Shortcut background-->
<color name="shortcut_bg">#F5F5F5</color>
</resources>
-2
View File
@@ -31,8 +31,6 @@
<dimen name="rating_size">16dp</dimen>
<dimen name="rating_interval">1dp</dimen>
<dimen name="gallery_list_thumb_height">120dp</dimen>
<dimen name="gallery_list_thumb_width">80dp</dimen>
<!-- 4dp - 4dp, 4dp for card padding -->
<dimen name="gallery_list_interval">0dp</dimen>
<!-- 8dp - 4dp, 4dp for card padding -->
-3
View File
@@ -60,13 +60,10 @@
<string name="pd_file_image" translatable="false">M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6,20H15L18,20V12L14,16L12,14L6,20M8,9A2,2 0 0,0 6,11A2,2 0 0,0 8,13A2,2 0 0,0 10,11A2,2 0 0,0 8,9Z</string>
<string name="pd_eh_subscription" translatable="false">M20,8H4V6h16V8 M18,2H6v2h12V2 M20,10H4c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2v-8C22,10.9,21.1,10,20,10zM11,14H7v1h4v2H7v1h4v2H5v-8h6V14z M19,20h-2v-3h-2v3h-2v-8h2v3h2v-3h2V20z</string>
<string name="pd_pencil" translatable="false">M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z</string>
<string name="pd_big_history" translatable="false">M5.5,2H22V0H5C3.3,0,2,1.3,2,3V21C2,22.7,3.3,24,5,24H22V4H5.5C4.1,4,4.1,2,5.5,2ZM20,6V22H6V6H20M12.5,11.8V14.4L14.9,15.8L15.3,15.2L13.3,14V11.9M13.3,9.3C11.5,9.3,10,10.3,9.2,11.7L8,10.5V13.8H11.3L9.9,12.4C10.5,11.1,11.8,10.3,13.3,10.3C15.4,10.3,17.1,12,17.1,14.1S15.4,17.9,13.3,17.9C11.7,17.9,10.3,16.9,9.8,15.4H8.7C9.3,17.4,11.1,18.9,13.3,18.9C15.9,18.9,18.1,16.8,18.1,14.1C18,11.4,15.9,9.3,13.3,9.3Z</string>
<string name="pd_big_download" translatable="false">M0,19V24H24V19H0ZM16,22H15V21H16V22ZM19,22H18V21H19V22ZM22,22H21V21H22V22M15,11L15,0L9,0L9,11L5,11L12,18L19,11Z</string>
<string name="pd_big_filter" translatable="false">M22.9,2.84C24,0.44,15.08,0,12,0C9.06,0,0,0.43,1.1,2.84C1.6,3.94,10,15.76,10,15.76V21L14,24V15.76S22.4,3.96,22.9,2.84ZM12,1.94C16.25,1.94,20.15,2.51,20.15,3.17S16.25,4.34,12,4.34S3.97,3.8,3.97,3.17S7.76,1.94,12,1.94Z</string>
<string name="pd_slider_bubble" translatable="false">M13,0C20.2,0,26,5.9,26,13.2,26,16.9,24,20.8,18.7,26.1L13,32,7.2,26.2C2,20.8,0,16.9,0,13.3,0,5.9,5.8,0,13,0Z</string>
<string name="pd_sad_panda" translatable="false">M21.1,7.8C22.2,7.4,23,6.3,23,5C23,3.3,21.7,2,20,2C18.9,2,17.9,2.6,17.4,3.6L21.1,7.8M6.6,3.6C6.1,2.6,5.1,2,4,2C2.3,2,1,3.3,1,5C1,6.3,1.8,7.4,2.9,7.8L6.6,3.6M10.1,13L8,15.1C7.4,15.7,6.5,15.7,5.9,15.1L5.9,15.1C5.3,14.5,5.3,13.6,5.9,13L8,10.9C8.6,10.3,9.5,10.3,10.1,10.9L10.1,10.9C10.7,11.5,10.7,12.4,10.1,13M16,15.1L13.9,13C13.3,12.4,13.3,11.5,13.9,10.9L13.9,10.9C14.5,10.3,15.4,10.3,16,10.9L18.1,13C18.7,13.6,18.7,14.5,18.1,15.1L18.1,15.1C17.5,15.7,16.6,15.7,16,15.1M12,17C12.8,17,13.5,17.4,13.5,18S12.8,19,12,19S10.5,18.6,10.5,18S11.2,17,12,17Z</string>
<string name="pd_archive" translatable="false">M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM10,17l-3.5,-3.5 1.41,-1.41L10,14.17 15.18,9l1.41,1.41L10,17z</string>
+12 -2
View File
@@ -466,8 +466,8 @@
<string name="settings_eh_identity_cookies">Identity cookies</string>
<string name="settings_eh_identity_cookies_summary">Cookies can be used to sign in</string>
<string name="settings_eh_identity_cookies_signed">Identity cookies can be used to sign in to this account.&lt;br&gt;&lt;b&gt;KEEP IT SAFE&lt;/b&gt;&lt;br&gt;&lt;br&gt;%s</string>
<string name="settings_eh_identity_cookies_tourist">Not signed in.</string>
<string name="settings_eh_identity_cookies_copy">Copy</string>
<string name="settings_eh_identity_cookies_tourist">Tourist mode</string>
<string name="settings_eh_identity_cookies_copy">Copy Cookies</string>
<string name="settings_u_config">EHentai settings</string>
<string name="settings_u_config_summary">Settings on EHentai website</string>
<string name="settings_my_tags">My tags</string>
@@ -825,6 +825,9 @@
<string name="show_library_version">Library Version</string>
<string name="settings_privacy_require_unlock">Require Unlock</string>
<string name="settings_privacy_require_unlock_delay">Lock Delay</string>
<string name="settings_privacy_require_unlock_delay_summary">No unlock is required when leaving App and returning within %s minute(s)</string>
<string name="settings_privacy_require_unlock_delay_summary_immediately">Unlock is required whenever you return to this App</string>
<string name="proxy_type_name">Proxy Type</string>
<string name="filter_label">Blocker Type</string>
@@ -832,4 +835,11 @@
<string name="archive_need_passwd">Archive need password</string>
<string name="passwd_wrong">Password Wrong</string>
<string name="passwd_cannot_be_empty">Password can\'t be empty</string>
<string name="list_tile_thumb_size">Thumb size in thumb mode</string>
<string name="backup_favorite">Backup favorite list</string>
<string name="backup_favorite_summary">Backup remote favorite list to local</string>
<string name="backup_favorite_failed">Backup favorite list failed</string>
<string name="backup_favorite_success">backup favorite list success</string>
<string name="backup_favorite_start">Now starting backup favorite list</string>
<string name="account_name">Account</string>
</resources>
+1 -3
View File
@@ -18,8 +18,6 @@
<style name="AppTheme" parent="Base.AppTheme" />
<!-- TextAppearance -->
<!-- Progress View -->
<style name="ProgressView">
<item name="android:indeterminate">true</item>
@@ -34,7 +32,7 @@
</style>
<style name="CardMessage">
<item name="android:textAppearance">@style/TextAppearance.Material3.BodyMedium</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.LabelLarge</item>
<item name="android:ellipsize">end</item>
<item name="android:singleLine">true</item>
<item name="android:maxLines">1</item>
@@ -109,4 +109,10 @@
android:title="@string/open_by_default"
app:iconSpaceReserved="false" />
<Preference
android:key="backup_favorite"
android:summary="@string/backup_favorite_summary"
android:title="@string/backup_favorite"
app:iconSpaceReserved="false" />
</PreferenceScreen>
+13 -10
View File
@@ -17,16 +17,10 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.hippo.ehviewer.preference.SignOutPreference
android:key="sign_out"
android:summary="@string/settings_eh_sign_out_summary"
android:title="@string/settings_eh_sign_out"
app:iconSpaceReserved="false" />
<com.hippo.ehviewer.preference.IdentityCookiePreference
android:key="identity_cookie"
android:summary="@string/settings_eh_identity_cookies_summary"
android:title="@string/settings_eh_identity_cookies"
<com.hippo.ehviewer.preference.AccountPreference
android:key="account"
android:summary="@string/settings_eh_identity_cookies_tourist"
android:title="@string/account_name"
app:iconSpaceReserved="false" />
<rikka.preference.SimpleMenuPreference
@@ -82,6 +76,15 @@
app:entryValues="@array/list_mode_entry_values"
app:iconSpaceReserved="false" />
<SeekBarPreference
android:defaultValue="40"
android:key="list_tile_size"
android:title="@string/list_tile_thumb_size"
android:max="60"
app:iconSpaceReserved="false"
app:min="20"
app:showSeekBarValue="true" />
<rikka.preference.SimpleMenuPreference
android:defaultValue="0"
android:key="detail_size"
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 GeneralPublic 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/>.
-->
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en" />
<locale android:name="de" />
<locale android:name="es" />
<locale android:name="fr" />
<locale android:name="ja" />
<locale android:name="ko" />
<locale android:name="th" />
<locale android:name="zh-CN" />
<locale android:name="zh-HK" />
<locale android:name="zh-TW" />
</locale-config>
+10
View File
@@ -24,6 +24,16 @@
android:title="@string/settings_privacy_require_unlock"
app:iconSpaceReserved="false" />
<SeekBarPreference
android:defaultValue="0"
android:key="require_unlock_delay"
android:max="30"
android:progress="0"
android:summary=""
android:title="@string/settings_privacy_require_unlock_delay"
app:iconSpaceReserved="false"
app:showSeekBarValue="true" />
<rikka.material.preference.MaterialSwitchPreference
android:defaultValue="false"
android:key="enable_secure"
+1 -1
View File
@@ -122,8 +122,8 @@
android:dependency="custom_screen_lightness"
android:key="screen_lightness"
android:title="@string/settings_read_screen_lightness"
android:max="200"
app:iconSpaceReserved="false"
app:max="200"
app:showSeekBarValue="true" />
</PreferenceScreen>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

After

Width:  |  Height:  |  Size: 8.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 MiB

After

Width:  |  Height:  |  Size: 6.5 MiB

+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.application' version '8.0.0-alpha02' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
id 'com.google.devtools.ksp' version '1.7.10-1.0.6' apply false
}
+1 -1
View File
@@ -79,7 +79,7 @@ App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 a
Here is the libraries
- [AOSP&AndroidX](http://source.android.com/)
- [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)
+1 -1
View File
@@ -79,7 +79,7 @@ App 分为 1.8 与 1.7 两个版本。1.8 版本适配 Android 12 及以上系
本项目受到了诸多开源项目的帮助
- [AOSP&AndroidX](http://source.android.com/)
- [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)
+1 -1
View File
@@ -79,7 +79,7 @@ App 分為 1.8 與 1.7 兩個版本。1.8 版本適配 Android 12 及以上系
本項目受到了諸多開源項目的幫助
- [AOSP&AndroidX](http://source.android.com/)
- [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)