Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f699973b9 | ||
|
|
3f27c9139c | ||
|
|
9eee6fc4af | ||
|
|
a343dbe8f8 | ||
|
|
bc08a09621 | ||
|
|
586ad66bff | ||
|
|
1a426e1f5f | ||
|
|
e9a9f1070c | ||
|
|
7d3dd61bc4 | ||
|
|
f37894af88 | ||
|
|
14129545b8 | ||
|
|
f06346b6c7 | ||
|
|
13e3e8c534 | ||
|
|
4be282813c | ||
|
|
74e66a407d | ||
|
|
b19da4c82e | ||
|
|
2f49764e1b | ||
|
|
08dfb0f72d | ||
|
|
9251ed238c | ||
|
|
3b96328765 | ||
|
|
108928e524 | ||
|
|
b1191bbf9b | ||
|
|
3424260cfd | ||
|
|
e1fe56160e | ||
|
|
5838ea3e3f | ||
|
|
0906b17ed1 | ||
|
|
49b9cb72d3 | ||
|
|
01508cc3c1 | ||
|
|
79eba83812 | ||
|
|
a455dee24c | ||
|
|
f58b4959e4 | ||
|
|
a6b4182ecd | ||
|
|
9010591ffc | ||
|
|
e0dde2c982 | ||
|
|
6109d97bf7 | ||
|
|
95936e01ce | ||
|
|
beadc7b185 | ||
|
|
0bc2e45137 | ||
|
|
b8e649e995 | ||
|
|
bdcd21a535 | ||
|
|
1b779a8b77 | ||
|
|
6c59a341ee | ||
|
|
1a6e03c8f8 | ||
|
|
653c0a2ac6 | ||
|
|
e4189f367a | ||
|
|
48b7355ba4 | ||
|
|
d7d3a903ff | ||
|
|
bab3067273 | ||
|
|
fdd70a6908 | ||
|
|
3699a8fe30 | ||
|
|
32f98f8a15 | ||
|
|
c5366760a2 | ||
|
|
c98ec2a7a4 | ||
|
|
e4c729e51b | ||
|
|
f36726d8fb | ||
|
|
d2fc2ee136 | ||
|
|
6edc2164af | ||
|
|
e34dc30f1f | ||
|
|
c03081d3fa | ||
|
|
97287e4538 | ||
|
|
fc44507a34 | ||
|
|
2597004750 | ||
|
|
d1bd9c5fae | ||
|
|
54542f9246 | ||
|
|
377b23458f | ||
|
|
b7973cdb76 | ||
|
|
5f1a70886c | ||
|
|
5fbc8b0b66 | ||
|
|
780b2b6e1d | ||
|
|
039ad2032c | ||
|
|
a24222b693 | ||
|
|
6047d91d7e | ||
|
|
f0158f6946 | ||
|
|
00ecdc58e6 | ||
|
|
0a075a97af | ||
|
|
34ee70ca7d | ||
|
|
338751ef2b | ||
|
|
d4465ba864 | ||
|
|
9a8fdd4edb | ||
|
|
338848f1eb | ||
|
|
9b918ec036 | ||
|
|
24439c7853 | ||
|
|
a7f63a5b57 | ||
|
|
1e68782056 | ||
|
|
e0f7dab34f | ||
|
|
5513d47b6d | ||
|
|
e12abb66f7 | ||
|
|
106be8c0ca | ||
|
|
2e0d065e4d | ||
|
|
073298cfee | ||
|
|
ebd0f4b4f8 | ||
|
|
0ba71b060c | ||
|
|
144e384441 | ||
|
|
9bb694171b | ||
|
|
647362cd82 | ||
|
|
56996558df | ||
|
|
956b7c719e | ||
|
|
56e13559a0 | ||
|
|
261ed86047 | ||
|
|
d9b18358d2 | ||
|
|
a5161bba6f | ||
|
|
62b00c758c | ||
|
|
8663b4fa40 | ||
|
|
6304c12bd0 | ||
|
|
efe2473ecf | ||
|
|
6ba494a4f1 | ||
|
|
b81572ffff | ||
|
|
d2ae1567be | ||
|
|
d00055a5f8 | ||
|
|
896a2684d9 | ||
|
|
b98ecb17d3 | ||
|
|
cd9af7feee | ||
|
|
1c38f77b5c | ||
|
|
c5977e358e | ||
|
|
78e58e6894 | ||
|
|
30433c5cbf | ||
|
|
fe8e3766c0 | ||
|
|
90c95d9788 | ||
|
|
3930a79a47 | ||
|
|
fc852780f8 | ||
|
|
35315c38cd | ||
|
|
0b40f37418 | ||
|
|
5a5f0421d8 | ||
|
|
86c5c5bc59 | ||
|
|
776a088de9 | ||
|
|
ba54655877 | ||
|
|
9f47ae438c | ||
|
|
1717233f5a | ||
|
|
cf71feb89d | ||
|
|
21e393246a | ||
|
|
c368f234f0 | ||
|
|
c3d8610e2a | ||
|
|
1a10e330aa | ||
|
|
d992897506 | ||
|
|
a0fe6f0d7d | ||
|
|
f4826ea727 | ||
|
|
a17b254494 | ||
|
|
0190fc07bd | ||
|
|
c8dab6a88f | ||
|
|
fbafadfd98 |
No files matched your search
@@ -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
|
||||
@@ -1,3 +1,4 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 所有其他问题 / All other questions
|
||||
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
|
||||
|
||||
@@ -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
|
||||
@@ -1,6 +1,11 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags-ignore:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
@@ -20,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,21 +23,49 @@ 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: |
|
||||
mkdir app/build/outputs/apk/release/fdroid
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME-armv7.apk
|
||||
|
||||
- name: Push Apks
|
||||
uses: cpina/github-action-push-to-another-repository@v1.5
|
||||
env:
|
||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||
with:
|
||||
source-directory: app/build/outputs/apk/release/fdroid
|
||||
destination-github-username: Ehviewer-Overhauled
|
||||
destination-repository-name: fdroid-repo
|
||||
user-name: 'github-actions[bot]'
|
||||
user-email: 'github-actions[bot]@users.noreply.github.com'
|
||||
target-branch: deploy
|
||||
commit-message: Add apks from $GITHUB_REF
|
||||
target-directory: fdroid/tmp-apks
|
||||
@@ -11,7 +11,7 @@
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="art/launcher_icon-web.png" width="200">
|
||||
<img src="art/launcher_icon-web.svg" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 180006
|
||||
versionName "1.8.1.0-alpha02"
|
||||
versionCode 180013
|
||||
versionName "1.8.2.4"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -86,45 +81,63 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.0"
|
||||
}
|
||||
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.0-alpha05"
|
||||
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-alpha01"
|
||||
implementation "androidx.annotation:annotation-experimental:1.3.0-alpha01"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0-alpha05"
|
||||
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"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.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"
|
||||
implementation "androidx.compose.runtime:runtime:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
|
||||
implementation "androidx.compose.ui:ui:$compose_version"
|
||||
|
||||
implementation "androidx.compose.material3:material3:1.0.0-beta03"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
|
||||
def core_version = "1.9.0-alpha05"
|
||||
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"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
|
||||
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"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
@@ -133,18 +146,21 @@ 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"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta01'
|
||||
implementation "androidx.paging:paging-runtime:3.2.0-alpha02"
|
||||
|
||||
def room_version = "2.5.0-alpha02"
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
|
||||
|
||||
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"
|
||||
|
||||
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
@@ -153,20 +169,26 @@ 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-beta01'
|
||||
implementation 'androidx.webkit:webkit:1.5.0'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.7.0-alpha03'
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
|
||||
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'
|
||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||
|
||||
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
|
||||
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
|
||||
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
||||
implementation 'org.jsoup:jsoup:1.15.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.3'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
|
||||
exclude group: 'dev.rikka.rikkax.material', module: 'material'
|
||||
exclude group: 'androidx.compose.material'
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
@@ -17,15 +17,19 @@
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#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>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define LOG_TAG "libarchive_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
|
||||
@@ -35,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;
|
||||
@@ -55,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;
|
||||
@@ -66,52 +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);
|
||||
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, jlong addr, jlong size) {
|
||||
archiveAddr = (void *) addr;
|
||||
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 = _;
|
||||
madvise(archiveAddr, archiveSize, MADV_SEQUENTIAL | MADV_WILLNEED);
|
||||
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;
|
||||
@@ -120,77 +230,125 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jlong add
|
||||
default:
|
||||
need_encrypt = false;
|
||||
}
|
||||
LOGI("%s%d", "archive_read_has_encrypted_entries returns ", encryptRet);
|
||||
|
||||
// madvise for readahead optimization
|
||||
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:;
|
||||
}
|
||||
}
|
||||
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 = 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();
|
||||
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
|
||||
archive_alloc_ctx(&ctx);
|
||||
void *tmpBuf = alloca(4096);
|
||||
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;
|
||||
void *tmpBuf = malloc(4096);
|
||||
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;
|
||||
}
|
||||
free(tmpBuf);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -149,7 +149,7 @@
|
||||
#define PACKAGE_NAME "nettle"
|
||||
|
||||
/* Define to the full name and version of this package. */
|
||||
#define PACKAGE_STRING "nettle 3.8"
|
||||
#define PACKAGE_STRING "nettle 3.8.1"
|
||||
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#define PACKAGE_TARNAME "nettle"
|
||||
@@ -158,7 +158,7 @@
|
||||
#define PACKAGE_URL ""
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#define PACKAGE_VERSION "3.8"
|
||||
#define PACKAGE_VERSION "3.8.1"
|
||||
|
||||
/* The size of `long', as computed by sizeof. */
|
||||
#define SIZEOF_LONG __SIZEOF_LONG__
|
||||
|
||||
@@ -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,15 +21,9 @@ 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;
|
||||
FileDescriptor fd;
|
||||
long archiveAddr;
|
||||
Context ctx;
|
||||
Uri uri;
|
||||
|
||||
@@ -40,14 +34,16 @@ public class UriArchiveAccessor {
|
||||
|
||||
public int open() throws Exception {
|
||||
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
|
||||
fd = pfd.getFileDescriptor();
|
||||
archiveAddr = Os.mmap(0, pfd.getStatSize(), OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, 0);
|
||||
return openArchive(archiveAddr, pfd.getStatSize());
|
||||
return openArchive(pfd.getFd(), pfd.getStatSize());
|
||||
}
|
||||
|
||||
private native int openArchive(long addr, long size);
|
||||
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();
|
||||
|
||||
@@ -57,8 +53,6 @@ public class UriArchiveAccessor {
|
||||
|
||||
public void close() throws Exception {
|
||||
closeArchive();
|
||||
Os.munmap(archiveAddr, pfd.getStatSize());
|
||||
Os.close(fd);
|
||||
pfd.close();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.paging.PagingSource;
|
||||
import androidx.room.Room;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
@@ -57,8 +58,6 @@ import java.util.List;
|
||||
public class EhDB {
|
||||
private static final int CUR_DB_VER = 4;
|
||||
|
||||
private static final int MAX_HISTORY_COUNT = 200;
|
||||
|
||||
private static EhDatabase db;
|
||||
|
||||
private static void upgradeDB(SQLiteDatabase db, int oldVersion) {
|
||||
@@ -328,10 +327,14 @@ public class EhDB {
|
||||
dao.update(list);
|
||||
}
|
||||
|
||||
public static synchronized List<HistoryInfo> getHistoryLazyList() {
|
||||
public static synchronized List<HistoryInfo> getHistoryList() {
|
||||
return db.historyDao().list();
|
||||
}
|
||||
|
||||
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
|
||||
return db.historyDao().listLazy();
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryInfo info = dao.load(galleryInfo.gid);
|
||||
@@ -344,8 +347,6 @@ public class EhDB {
|
||||
info = new HistoryInfo(galleryInfo);
|
||||
info.time = System.currentTimeMillis();
|
||||
dao.insert(info);
|
||||
List<HistoryInfo> list = dao.list(MAX_HISTORY_COUNT, -1);
|
||||
dao.delete(list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,9 +357,6 @@ public class EhDB {
|
||||
dao.insert(info);
|
||||
}
|
||||
}
|
||||
|
||||
List<HistoryInfo> list = dao.list(MAX_HISTORY_COUNT, -1);
|
||||
dao.delete(list);
|
||||
}
|
||||
|
||||
public static synchronized void deleteHistoryInfo(HistoryInfo info) {
|
||||
@@ -492,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,5 +1,6 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.paging.PagingSource;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
@@ -19,6 +20,9 @@ public interface HistoryDao extends BasicDao<HistoryInfo> {
|
||||
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC LIMIT :limit OFFSET :offset")
|
||||
List<HistoryInfo> list(int offset, int limit);
|
||||
|
||||
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC")
|
||||
PagingSource<Integer, HistoryInfo> listLazy();
|
||||
|
||||
@Update
|
||||
void update(HistoryInfo historyInfo);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,647 +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.scene;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.FastScroller;
|
||||
import com.hippo.easyrecyclerview.HandlerDrawable;
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.FavouriteStatusRouter;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.dao.HistoryInfo;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.widget.LoadImageView;
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
public class HistoryScene extends ToolbarScene {
|
||||
|
||||
/*---------------
|
||||
View life cycle
|
||||
---------------*/
|
||||
@Nullable
|
||||
private TextView mTip;
|
||||
@Nullable
|
||||
private FastScroller mFastScroller;
|
||||
@Nullable
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
private ViewTransition mViewTransition;
|
||||
@Nullable
|
||||
private RecyclerView.Adapter<?> mAdapter;
|
||||
@Nullable
|
||||
private List<HistoryInfo> mLazyList;
|
||||
|
||||
private DownloadManager mDownloadManager;
|
||||
private DownloadManager.DownloadInfoListener mDownloadInfoListener;
|
||||
private FavouriteStatusRouter mFavouriteStatusRouter;
|
||||
private FavouriteStatusRouter.Listener mFavouriteStatusRouterListener;
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
mDownloadManager.removeDownloadInfoListener(mDownloadInfoListener);
|
||||
mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNavCheckedItem() {
|
||||
return R.id.nav_history;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Context context = getContext();
|
||||
assert context != null;
|
||||
mDownloadManager = EhApplication.getDownloadManager(context);
|
||||
mDownloadManager = EhApplication.getDownloadManager(context);
|
||||
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context);
|
||||
|
||||
mDownloadInfoListener = new DownloadManager.DownloadInfoListener() {
|
||||
@Override
|
||||
public void onAdd(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateAll() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReload() {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange() {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenameLabel(String from, String to) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateLabels() {
|
||||
}
|
||||
};
|
||||
mDownloadManager.addDownloadInfoListener(mDownloadInfoListener);
|
||||
|
||||
mFavouriteStatusRouterListener = (gid, slot) -> {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
mFavouriteStatusRouter.addListener(mFavouriteStatusRouterListener);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateViewWithToolbar(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_history, container, false);
|
||||
View content = ViewUtils.$$(view, R.id.content);
|
||||
mRecyclerView = (EasyRecyclerView) ViewUtils.$$(content, R.id.recycler_view);
|
||||
mFastScroller = (FastScroller) ViewUtils.$$(content, R.id.fast_scroller);
|
||||
mTip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
mViewTransition = new ViewTransition(content, mTip);
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
Resources resources = context.getResources();
|
||||
|
||||
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.big_history);
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
mTip.setCompoundDrawables(null, drawable, null, null);
|
||||
|
||||
mAdapter = new HistoryAdapter();
|
||||
mAdapter.setHasStableIds(true);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
AutoStaggeredGridLayoutManager layoutManager = new AutoStaggeredGridLayoutManager(
|
||||
0, StaggeredGridLayoutManager.VERTICAL);
|
||||
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()));
|
||||
layoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE);
|
||||
mRecyclerView.setLayoutManager(layoutManager);
|
||||
mRecyclerView.setClipToPadding(false);
|
||||
mRecyclerView.setClipChildren(false);
|
||||
int interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval);
|
||||
int paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h);
|
||||
int paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v);
|
||||
MarginItemDecoration decoration = new MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV);
|
||||
mRecyclerView.addItemDecoration(decoration);
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new HistoryItemTouchHelperCallback());
|
||||
itemTouchHelper.attachToRecyclerView(mRecyclerView);
|
||||
mFastScroller.attachToRecyclerView(mRecyclerView);
|
||||
HandlerDrawable handlerDrawable = new HandlerDrawable();
|
||||
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorPrimary));
|
||||
mFastScroller.setHandlerDrawable(handlerDrawable);
|
||||
|
||||
updateLazyList();
|
||||
updateView(false);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setTitle(R.string.history);
|
||||
setNavigationIcon(R.drawable.ic_baseline_menu_24);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
if (null != mLazyList) {
|
||||
mLazyList = null;
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView.stopScroll();
|
||||
mRecyclerView = null;
|
||||
}
|
||||
|
||||
mViewTransition = null;
|
||||
mAdapter = null;
|
||||
}
|
||||
|
||||
// Remember to notify
|
||||
private void updateLazyList() {
|
||||
mLazyList = EhDB.getHistoryLazyList();
|
||||
}
|
||||
|
||||
private void updateView(boolean animation) {
|
||||
if (null == mAdapter || null == mViewTransition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAdapter.getItemCount() == 0) {
|
||||
mViewTransition.showView(1, animation);
|
||||
} else {
|
||||
mViewTransition.showView(0, animation);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
@Override
|
||||
public void onNavigationClick() {
|
||||
toggleDrawer(Gravity.LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuResId() {
|
||||
return R.menu.scene_history;
|
||||
}
|
||||
|
||||
private void showClearAllDialog() {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(R.string.clear_all_history)
|
||||
.setPositiveButton(R.string.clear_all, (dialog, which) -> {
|
||||
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
EhDB.clearHistoryInfo();
|
||||
updateLazyList();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateView(true);
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
// Skip when in choice mode
|
||||
Context context = getContext();
|
||||
if (null == context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_clear_all) {
|
||||
showClearAllDialog();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onItemClick(View view, int position) {
|
||||
if (null == mLazyList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO);
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, mLazyList.get(position));
|
||||
Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args);
|
||||
startScene(announcer);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onItemLongClick(int position) {
|
||||
final Context context = getContext();
|
||||
final MainActivity activity = getMainActivity();
|
||||
if (null == context || null == activity || null == mLazyList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GalleryInfo gi = mLazyList.get(position);
|
||||
|
||||
if (gi == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean downloaded = mDownloadManager.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID;
|
||||
boolean favourited = gi.favoriteSlot != -2;
|
||||
|
||||
CharSequence[] items = downloaded ? new CharSequence[]{
|
||||
context.getString(R.string.read),
|
||||
context.getString(R.string.delete_downloads),
|
||||
context.getString(favourited ? R.string.remove_from_favourites : R.string.add_to_favourites),
|
||||
context.getString(R.string.delete),
|
||||
context.getString(R.string.download_move_dialog_title),
|
||||
} : new CharSequence[]{
|
||||
context.getString(R.string.read),
|
||||
context.getString(R.string.download),
|
||||
context.getString(favourited ? R.string.remove_from_favourites : R.string.add_to_favourites),
|
||||
context.getString(R.string.delete),
|
||||
};
|
||||
|
||||
int[] icons = downloaded ? new int[]{
|
||||
R.drawable.v_book_open_x24,
|
||||
R.drawable.v_delete_x24,
|
||||
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
|
||||
R.drawable.v_delete_x24,
|
||||
R.drawable.v_folder_move_x24,
|
||||
} : new int[]{
|
||||
R.drawable.v_book_open_x24,
|
||||
R.drawable.v_download_x24,
|
||||
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
|
||||
R.drawable.v_delete_x24,
|
||||
};
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(EhUtils.getSuitableTitle(gi))
|
||||
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
|
||||
switch (which) {
|
||||
case 0: // Read
|
||||
Intent intent = new Intent(activity, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case 1: // Download
|
||||
if (downloaded) {
|
||||
new MaterialAlertDialogBuilder(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))
|
||||
.show();
|
||||
} else {
|
||||
CommonOperations.startDownload(activity, gi, false);
|
||||
}
|
||||
break;
|
||||
case 2: // Favorites
|
||||
if (favourited) {
|
||||
CommonOperations.removeFromFavorites(activity, gi, new RemoveFromFavoriteListener(context, activity.getStageId(), getTag()));
|
||||
} else {
|
||||
CommonOperations.addToFavorites(activity, gi, new AddToFavoriteListener(context, activity.getStageId(), getTag()));
|
||||
}
|
||||
break;
|
||||
case 3: // Delete
|
||||
if (null == mLazyList || null == mAdapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
HistoryInfo info = mLazyList.get(position);
|
||||
EhDB.deleteHistoryInfo(info);
|
||||
updateLazyList();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateView(true);
|
||||
break;
|
||||
case 4: // Move
|
||||
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager(context).getLabelList();
|
||||
List<String> labelList = new ArrayList<>(labelRawList.size() + 1);
|
||||
labelList.add(getString(R.string.default_download_label_name));
|
||||
for (int i = 0, n = labelRawList.size(); i < n; i++) {
|
||||
labelList.add(labelRawList.get(i).getLabel());
|
||||
}
|
||||
String[] labels = labelList.toArray(new String[0]);
|
||||
|
||||
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class AddToFavoriteListener extends EhCallback<GalleryListScene, Void> {
|
||||
|
||||
public AddToFavoriteListener(Context context, int stageId, String sceneTag) {
|
||||
super(context, stageId, sceneTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
showTip(R.string.add_to_favorite_success, LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
showTip(R.string.add_to_favorite_failure, LENGTH_LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstance(SceneFragment scene) {
|
||||
return scene instanceof GalleryListScene;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveFromFavoriteListener extends EhCallback<GalleryListScene, Void> {
|
||||
|
||||
public RemoveFromFavoriteListener(Context context, int stageId, String sceneTag) {
|
||||
super(context, stageId, sceneTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
showTip(R.string.remove_from_favorite_success, LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
showTip(R.string.remove_from_favorite_failure, LENGTH_LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstance(SceneFragment scene) {
|
||||
return scene instanceof GalleryListScene;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HistoryHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final View card;
|
||||
public final LoadImageView thumb;
|
||||
public final TextView title;
|
||||
public final TextView uploader;
|
||||
public final SimpleRatingView rating;
|
||||
public final TextView category;
|
||||
public final TextView posted;
|
||||
public final TextView simpleLanguage;
|
||||
public final TextView pages;
|
||||
public final ImageView downloaded;
|
||||
|
||||
public HistoryHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
card = itemView.findViewById(R.id.card);
|
||||
thumb = itemView.findViewById(R.id.thumb);
|
||||
title = itemView.findViewById(R.id.title);
|
||||
uploader = itemView.findViewById(R.id.uploader);
|
||||
rating = itemView.findViewById(R.id.rating);
|
||||
category = itemView.findViewById(R.id.category);
|
||||
posted = itemView.findViewById(R.id.posted);
|
||||
simpleLanguage = itemView.findViewById(R.id.simple_language);
|
||||
pages = itemView.findViewById(R.id.pages);
|
||||
downloaded = itemView.findViewById(R.id.downloaded);
|
||||
}
|
||||
}
|
||||
|
||||
private class MoveDialogHelper implements DialogInterface.OnClickListener {
|
||||
|
||||
private final String[] mLabels;
|
||||
private final GalleryInfo mGi;
|
||||
|
||||
public MoveDialogHelper(String[] labels, GalleryInfo gi) {
|
||||
mLabels = labels;
|
||||
mGi = gi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Cancel check mode
|
||||
Context context = getContext();
|
||||
if (null == context) {
|
||||
return;
|
||||
}
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView.outOfCustomChoiceMode();
|
||||
}
|
||||
|
||||
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
|
||||
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
|
||||
if (downloadInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String label = which == 0 ? null : mLabels[which];
|
||||
|
||||
downloadManager.changeLabel(Collections.singletonList(downloadInfo), label);
|
||||
}
|
||||
}
|
||||
|
||||
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private final int mListThumbWidth;
|
||||
private final int mListThumbHeight;
|
||||
|
||||
public HistoryAdapter() {
|
||||
mInflater = getLayoutInflater();
|
||||
|
||||
@SuppressLint("InflateParams") View calculator = mInflater.inflate(R.layout.item_gallery_list_thumb_height, null);
|
||||
ViewUtils.measureView(calculator, 1024, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mListThumbHeight = calculator.getMeasuredHeight();
|
||||
mListThumbWidth = mListThumbHeight * 2 / 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (null == mLazyList) {
|
||||
return super.getItemId(position);
|
||||
} else {
|
||||
return mLazyList.get(position).gid;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public HistoryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
HistoryHolder holder = new HistoryHolder(mInflater.inflate(R.layout.item_history, parent, false));
|
||||
|
||||
ViewGroup.LayoutParams lp = holder.thumb.getLayoutParams();
|
||||
lp.width = mListThumbWidth;
|
||||
lp.height = mListThumbHeight;
|
||||
holder.thumb.setLayoutParams(lp);
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull HistoryHolder holder, int position) {
|
||||
if (null == mLazyList) {
|
||||
return;
|
||||
}
|
||||
|
||||
GalleryInfo gi = mLazyList.get(position);
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
|
||||
holder.title.setText(EhUtils.getSuitableTitle(gi));
|
||||
holder.uploader.setText(gi.uploader);
|
||||
holder.rating.setRating(gi.rating);
|
||||
TextView category = holder.category;
|
||||
String newCategoryText = EhUtils.getCategory(gi.category);
|
||||
if (!newCategoryText.contentEquals(category.getText())) {
|
||||
category.setText(newCategoryText);
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category));
|
||||
}
|
||||
holder.posted.setText(gi.posted);
|
||||
holder.pages.setText(null);
|
||||
holder.pages.setVisibility(View.GONE);
|
||||
if (TextUtils.isEmpty(gi.simpleLanguage)) {
|
||||
holder.simpleLanguage.setText(null);
|
||||
holder.simpleLanguage.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.simpleLanguage.setText(gi.simpleLanguage);
|
||||
holder.simpleLanguage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
holder.downloaded.setVisibility(mDownloadManager.containDownloadInfo(gi.gid) ? View.VISIBLE : View.GONE);
|
||||
|
||||
// Update transition name
|
||||
long gid = gi.gid;
|
||||
ViewCompat.setTransitionName(holder.thumb, TransitionNameFactory.getThumbTransitionName(gid));
|
||||
|
||||
holder.card.setOnClickListener(v -> onItemClick(holder.itemView, position));
|
||||
holder.card.setOnLongClickListener(v -> onItemLongClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return null != mLazyList ? mLazyList.size() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
private class HistoryItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return makeMovementFlags(0, ItemTouchHelper.LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
int mPosition = viewHolder.getBindingAdapterPosition();
|
||||
if (null == mLazyList || mAdapter == null) {
|
||||
return;
|
||||
}
|
||||
HistoryInfo info = mLazyList.get(mPosition);
|
||||
EhDB.deleteHistoryInfo(info);
|
||||
updateLazyList();
|
||||
mAdapter.notifyItemRemoved(mPosition);
|
||||
updateView(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
* 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.scene
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.*
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.easyrecyclerview.FastScroller
|
||||
import com.hippo.easyrecyclerview.HandlerDrawable
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration
|
||||
import com.hippo.ehviewer.*
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.HistoryInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
|
||||
import com.hippo.ehviewer.ui.CommonOperations
|
||||
import com.hippo.ehviewer.ui.GalleryActivity
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView
|
||||
import com.hippo.scene.Announcer
|
||||
import com.hippo.scene.SceneFragment
|
||||
import com.hippo.view.ViewTransition
|
||||
import com.hippo.widget.LoadImageView
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import rikka.core.res.resolveColor
|
||||
|
||||
class HistoryScene : ToolbarScene() {
|
||||
private var mRecyclerView: EasyRecyclerView? = null
|
||||
private var mViewTransition: ViewTransition? = null
|
||||
private var mAdapter: HistoryAdapter? = null
|
||||
private var mDownloadManager: DownloadManager? = null
|
||||
private var mDownloadInfoListener: DownloadInfoListener? = null
|
||||
private var mFavouriteStatusRouter: FavouriteStatusRouter? = null
|
||||
private var mFavouriteStatusRouterListener: FavouriteStatusRouter.Listener? = null
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mDownloadManager!!.removeDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouter!!.removeListener(mFavouriteStatusRouterListener)
|
||||
}
|
||||
|
||||
override fun getNavCheckedItem(): Int {
|
||||
return R.id.nav_history
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val context = requireContext()
|
||||
mDownloadManager = EhApplication.getDownloadManager(context)
|
||||
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context)
|
||||
mDownloadInfoListener = object : DownloadInfoListener {
|
||||
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>) {}
|
||||
override fun onUpdateAll() {}
|
||||
override fun onReload() {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onChange() {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onRenameLabel(from: String, to: String) {}
|
||||
override fun onRemove(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onUpdateLabels() {}
|
||||
}
|
||||
mDownloadManager!!.addDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouterListener = FavouriteStatusRouter.Listener { _: Long, _: Int ->
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
mFavouriteStatusRouter!!.addListener(mFavouriteStatusRouterListener)
|
||||
}
|
||||
|
||||
override fun onCreateViewWithToolbar(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.scene_history, container, false)
|
||||
val content = ViewUtils.`$$`(view, R.id.content)
|
||||
val recyclerView = ViewUtils.`$$`(content, R.id.recycler_view) as EasyRecyclerView
|
||||
val mFastScroller = ViewUtils.`$$`(content, R.id.fast_scroller) as FastScroller
|
||||
val mTip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
mViewTransition = ViewTransition(content, mTip)
|
||||
val resources = requireContext().resources
|
||||
val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.big_history)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
mTip.setCompoundDrawables(null, drawable, null, null)
|
||||
val historyData = Pager(
|
||||
PagingConfig(20)
|
||||
) {
|
||||
EhDB.getHistoryLazyList()
|
||||
}.flow.cachedIn(viewLifecycleOwner.lifecycleScope)
|
||||
mAdapter = HistoryAdapter(object : DiffUtil.ItemCallback<HistoryInfo>() {
|
||||
override fun areItemsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
|
||||
return oldItem.gid == newItem.gid
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
|
||||
return oldItem.gid == newItem.gid
|
||||
}
|
||||
})
|
||||
recyclerView.adapter = mAdapter
|
||||
val layoutManager = AutoStaggeredGridLayoutManager(
|
||||
0, StaggeredGridLayoutManager.VERTICAL
|
||||
)
|
||||
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()))
|
||||
layoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.clipChildren = false
|
||||
val interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval)
|
||||
val paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h)
|
||||
val paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v)
|
||||
val decoration = MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV)
|
||||
recyclerView.addItemDecoration(decoration)
|
||||
val itemTouchHelper = ItemTouchHelper(HistoryItemTouchHelperCallback())
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
mFastScroller.attachToRecyclerView(recyclerView)
|
||||
mRecyclerView = recyclerView
|
||||
val handlerDrawable = HandlerDrawable()
|
||||
handlerDrawable.setColor(theme.resolveColor(com.google.android.material.R.attr.colorPrimary))
|
||||
mFastScroller.setHandlerDrawable(handlerDrawable)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
historyData.collectLatest { value ->
|
||||
mAdapter!!.submitData(
|
||||
value
|
||||
)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setTitle(R.string.history)
|
||||
setNavigationIcon(R.drawable.ic_baseline_menu_24)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mRecyclerView?.stopScroll()
|
||||
mRecyclerView = null
|
||||
mViewTransition = null
|
||||
mAdapter = null
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
override fun onNavigationClick() {
|
||||
toggleDrawer(Gravity.LEFT)
|
||||
}
|
||||
|
||||
override fun getMenuResId(): Int {
|
||||
return R.menu.scene_history
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun showClearAllDialog() {
|
||||
BaseDialogBuilder(requireContext())
|
||||
.setMessage(R.string.clear_all_history)
|
||||
.setPositiveButton(R.string.clear_all) { _: DialogInterface?, which: Int ->
|
||||
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
|
||||
return@setPositiveButton
|
||||
}
|
||||
EhDB.clearHistoryInfo()
|
||||
mAdapter!!.refresh()
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
// Skip when in choice mode
|
||||
context ?: return false
|
||||
val id = item.itemId
|
||||
if (id == R.id.action_clear_all) {
|
||||
showClearAllDialog()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
val args = Bundle()
|
||||
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO)
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, mAdapter?.peek(position))
|
||||
val announcer = Announcer(GalleryDetailScene::class.java).setArgs(args)
|
||||
startScene(announcer)
|
||||
return true
|
||||
}
|
||||
|
||||
fun onItemLongClick(position: Int): Boolean {
|
||||
val context = requireContext()
|
||||
val activity = mainActivity ?: return false
|
||||
val gi: GalleryInfo = mAdapter?.peek(position) ?: return true
|
||||
val downloaded = mDownloadManager!!.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID
|
||||
val favourited = gi.favoriteSlot != -2
|
||||
val items = if (downloaded) arrayOf<CharSequence>(
|
||||
context.getString(R.string.read),
|
||||
context.getString(R.string.delete_downloads),
|
||||
context.getString(if (favourited) R.string.remove_from_favourites else R.string.add_to_favourites),
|
||||
context.getString(R.string.download_move_dialog_title)
|
||||
) else arrayOf<CharSequence>(
|
||||
context.getString(R.string.read),
|
||||
context.getString(R.string.download),
|
||||
context.getString(if (favourited) R.string.remove_from_favourites else R.string.add_to_favourites),
|
||||
)
|
||||
val icons = if (downloaded) intArrayOf(
|
||||
R.drawable.v_book_open_x24,
|
||||
R.drawable.v_delete_x24,
|
||||
if (favourited) R.drawable.v_heart_broken_x24 else R.drawable.v_heart_x24,
|
||||
R.drawable.v_folder_move_x24
|
||||
) else intArrayOf(
|
||||
R.drawable.v_book_open_x24,
|
||||
R.drawable.v_download_x24,
|
||||
if (favourited) R.drawable.v_heart_broken_x24 else R.drawable.v_heart_x24,
|
||||
)
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(EhUtils.getSuitableTitle(gi))
|
||||
.setAdapter(
|
||||
SelectItemWithIconAdapter(
|
||||
context,
|
||||
items,
|
||||
icons
|
||||
)
|
||||
) { _: DialogInterface?, which: Int ->
|
||||
when (which) {
|
||||
0 -> {
|
||||
val intent = Intent(activity, GalleryActivity::class.java)
|
||||
intent.action = GalleryActivity.ACTION_EH
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
|
||||
startActivity(intent)
|
||||
}
|
||||
1 -> if (downloaded) {
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
.setMessage(
|
||||
getString(
|
||||
R.string.download_remove_dialog_message,
|
||||
gi.title
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
mDownloadManager!!.deleteDownload(
|
||||
gi.gid
|
||||
)
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
CommonOperations.startDownload(activity, gi, false)
|
||||
}
|
||||
2 -> if (favourited) {
|
||||
CommonOperations.removeFromFavorites(
|
||||
activity,
|
||||
gi,
|
||||
RemoveFromFavoriteListener(context, activity.stageId, tag)
|
||||
)
|
||||
} else {
|
||||
CommonOperations.addToFavorites(
|
||||
activity,
|
||||
gi,
|
||||
AddToFavoriteListener(context, activity.stageId, tag)
|
||||
)
|
||||
}
|
||||
3 -> {
|
||||
val labelRawList = EhApplication.getDownloadManager(context).labelList
|
||||
val labelList: MutableList<String> = ArrayList(labelRawList.size + 1)
|
||||
labelList.add(getString(R.string.default_download_label_name))
|
||||
var i = 0
|
||||
val n = labelRawList.size
|
||||
while (i < n) {
|
||||
labelList.add(labelRawList[i].label)
|
||||
i++
|
||||
}
|
||||
val labels = labelList.toTypedArray()
|
||||
val helper = MoveDialogHelper(labels, gi)
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
return true
|
||||
}
|
||||
|
||||
private class AddToFavoriteListener(context: Context?, stageId: Int, sceneTag: String?) :
|
||||
EhCallback<GalleryListScene?, Void?>(context, stageId, sceneTag) {
|
||||
override fun onSuccess(result: Void?) {
|
||||
showTip(R.string.add_to_favorite_success, LENGTH_SHORT)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
showTip(R.string.add_to_favorite_failure, LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
override fun isInstance(scene: SceneFragment): Boolean {
|
||||
return scene is GalleryListScene
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoveFromFavoriteListener(context: Context?, stageId: Int, sceneTag: String?) :
|
||||
EhCallback<GalleryListScene?, Void?>(context, stageId, sceneTag) {
|
||||
override fun onSuccess(result: Void?) {
|
||||
showTip(R.string.remove_from_favorite_success, LENGTH_SHORT)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
showTip(R.string.remove_from_favorite_failure, LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
override fun isInstance(scene: SceneFragment): Boolean {
|
||||
return scene is GalleryListScene
|
||||
}
|
||||
}
|
||||
|
||||
private class HistoryHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val card: View
|
||||
val thumb: LoadImageView
|
||||
val title: TextView
|
||||
val uploader: TextView
|
||||
val rating: SimpleRatingView
|
||||
val category: TextView
|
||||
val posted: TextView
|
||||
val simpleLanguage: TextView
|
||||
val pages: TextView
|
||||
val downloaded: ImageView
|
||||
|
||||
init {
|
||||
card = itemView.findViewById(R.id.card)
|
||||
thumb = itemView.findViewById(R.id.thumb)
|
||||
title = itemView.findViewById(R.id.title)
|
||||
uploader = itemView.findViewById(R.id.uploader)
|
||||
rating = itemView.findViewById(R.id.rating)
|
||||
category = itemView.findViewById(R.id.category)
|
||||
posted = itemView.findViewById(R.id.posted)
|
||||
simpleLanguage = itemView.findViewById(R.id.simple_language)
|
||||
pages = itemView.findViewById(R.id.pages)
|
||||
downloaded = itemView.findViewById(R.id.downloaded)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MoveDialogHelper(
|
||||
private val mLabels: Array<String>,
|
||||
private val mGi: GalleryInfo
|
||||
) : DialogInterface.OnClickListener {
|
||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||
mRecyclerView?.outOfCustomChoiceMode()
|
||||
val downloadManager = EhApplication.getDownloadManager(requireContext())
|
||||
val downloadInfo = downloadManager.getDownloadInfo(mGi.gid) ?: return
|
||||
val label = if (which == 0) null else mLabels[which]
|
||||
downloadManager.changeLabel(listOf(downloadInfo), label)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class HistoryAdapter(diffCallback: DiffUtil.ItemCallback<HistoryInfo>) :
|
||||
PagingDataAdapter<HistoryInfo, HistoryHolder>(diffCallback) {
|
||||
private val mInflater: LayoutInflater = layoutInflater
|
||||
private val mListThumbWidth: Int
|
||||
private val mListThumbHeight: Int
|
||||
|
||||
init {
|
||||
@SuppressLint("InflateParams") val calculator =
|
||||
mInflater.inflate(R.layout.item_gallery_list_thumb_height, null)
|
||||
ViewUtils.measureView(calculator, 1024, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
mListThumbHeight = calculator.measuredHeight
|
||||
mListThumbWidth = mListThumbHeight * 2 / 3
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryHolder {
|
||||
val holder = HistoryHolder(mInflater.inflate(R.layout.item_history, parent, false))
|
||||
val lp = holder.thumb.layoutParams
|
||||
lp.width = mListThumbWidth
|
||||
lp.height = mListThumbHeight
|
||||
holder.thumb.layoutParams = lp
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: HistoryHolder, position: Int) {
|
||||
val gi: GalleryInfo? = getItem(position)
|
||||
gi ?: return
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb)
|
||||
holder.title.text = EhUtils.getSuitableTitle(gi)
|
||||
holder.uploader.text = gi.uploader
|
||||
holder.rating.rating = gi.rating
|
||||
val category = holder.category
|
||||
val newCategoryText = EhUtils.getCategory(gi.category)
|
||||
if (!newCategoryText!!.contentEquals(category.text)) {
|
||||
category.text = newCategoryText
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category))
|
||||
}
|
||||
holder.posted.text = gi.posted
|
||||
holder.pages.text = null
|
||||
holder.pages.visibility = View.GONE
|
||||
if (TextUtils.isEmpty(gi.simpleLanguage)) {
|
||||
holder.simpleLanguage.text = null
|
||||
holder.simpleLanguage.visibility = View.GONE
|
||||
} else {
|
||||
holder.simpleLanguage.text = gi.simpleLanguage
|
||||
holder.simpleLanguage.visibility = View.VISIBLE
|
||||
}
|
||||
holder.downloaded.visibility =
|
||||
if (mDownloadManager!!.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
|
||||
|
||||
// Update transition name
|
||||
val gid = gi.gid
|
||||
ViewCompat.setTransitionName(
|
||||
holder.thumb,
|
||||
TransitionNameFactory.getThumbTransitionName(gid)
|
||||
)
|
||||
holder.card.setOnClickListener { onItemClick(position) }
|
||||
holder.card.setOnLongClickListener { onItemLongClick(position) }
|
||||
}
|
||||
}
|
||||
|
||||
private inner class HistoryItemTouchHelperCallback : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
return makeMovementFlags(0, ItemTouchHelper.LEFT)
|
||||
}
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val mPosition = viewHolder.bindingAdapterPosition
|
||||
if (mAdapter == null) {
|
||||
return
|
||||
}
|
||||
val info: HistoryInfo = mAdapter!!.peek(mPosition)!!
|
||||
EhDB.deleteHistoryInfo(info)
|
||||
mAdapter!!.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -53,11 +53,6 @@ public class FixedThumb extends LoadImageView {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public void setFix(float minAspect, float maxAspect) {
|
||||
this.minAspect = minAspect;
|
||||
this.maxAspect = maxAspect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreSetImageDrawable(Drawable drawable, boolean isTarget) {
|
||||
if (alwaysCutAndScale) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,24 +15,54 @@
|
||||
~ 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"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="0.37947997"
|
||||
android:scaleY="0.37947997"
|
||||
android:translateX="35.1"
|
||||
android:translateY="39.584126">
|
||||
<path
|
||||
android:fillColor="@color/ic_launcher_foreground"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M39.893,68.066L39.893,75.977L0,75.977L0,4.59L39.893,4.59L39.893,12.5L8.984,12.5L8.984,34.766L38.086,34.766L38.086,42.578L8.984,42.578L8.984,68.066L39.893,68.066Z" />
|
||||
<path
|
||||
android:fillColor="@color/ic_launcher_foreground"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M99.609,75.977L90.918,75.977L90.918,41.699A23.436,23.436 0,0 0,90.662 38.109Q90.055,34.207 88.013,31.958Q85.629,29.334 81.028,28.863A20.84,20.84 0,0 0,78.906 28.76A23.134,23.134 0,0 0,74.45 29.16Q71.926,29.656 69.994,30.767A11.122,11.122 0,0 0,66.968 33.301A13.32,13.32 0,0 0,64.756 37.276Q63.399,40.971 63.213,46.495A53.517,53.517 0,0 0,63.184 48.291L63.184,75.977L54.395,75.977L54.395,0L63.184,0L63.184,22.266A66.935,66.935 0,0 1,63.115 25.383Q63.043,26.914 62.897,28.251A33.547,33.547 0,0 1,62.695 29.785L63.281,29.785A16.11,16.11 0,0 1,69.394 24.035A19.448,19.448 0,0 1,70.068 23.682A20.88,20.88 0,0 1,77.292 21.622A25.763,25.763 0,0 1,79.98 21.484A32.643,32.643 0,0 1,85.483 21.917Q91.249,22.905 94.678,26.147Q98.472,29.735 99.347,36.631A35.274,35.274 0,0 1,99.609 41.064L99.609,75.977Z" />
|
||||
</group>
|
||||
<group android:scaleX="0.7425"
|
||||
android:scaleY="0.7425"
|
||||
android:translateX="30.68"
|
||||
android:translateY="30.68">
|
||||
<path
|
||||
android:pathData="M11,9H34"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,54H23"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,9L11,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#35AA53"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,31L52,31"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M52,31L52,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#4586F3"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M34,21L34,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#EB4334"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z"
|
||||
android:fillColor="#FBBD06"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -15,24 +15,58 @@
|
||||
~ 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="44dp"
|
||||
android:height="44dp"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="0.37947997"
|
||||
android:scaleY="0.37947997"
|
||||
android:translateX="35.1"
|
||||
android:translateY="39.584126">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M39.893,68.066L39.893,75.977L0,75.977L0,4.59L39.893,4.59L39.893,12.5L8.984,12.5L8.984,34.766L38.086,34.766L38.086,42.578L8.984,42.578L8.984,68.066L39.893,68.066Z" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M99.609,75.977L90.918,75.977L90.918,41.699A23.436,23.436 0,0 0,90.662 38.109Q90.055,34.207 88.013,31.958Q85.629,29.334 81.028,28.863A20.84,20.84 0,0 0,78.906 28.76A23.134,23.134 0,0 0,74.45 29.16Q71.926,29.656 69.994,30.767A11.122,11.122 0,0 0,66.968 33.301A13.32,13.32 0,0 0,64.756 37.276Q63.399,40.971 63.213,46.495A53.517,53.517 0,0 0,63.184 48.291L63.184,75.977L54.395,75.977L54.395,0L63.184,0L63.184,22.266A66.935,66.935 0,0 1,63.115 25.383Q63.043,26.914 62.897,28.251A33.547,33.547 0,0 1,62.695 29.785L63.281,29.785A16.11,16.11 0,0 1,69.394 24.035A19.448,19.448 0,0 1,70.068 23.682A20.88,20.88 0,0 1,77.292 21.622A25.763,25.763 0,0 1,79.98 21.484A32.643,32.643 0,0 1,85.483 21.917Q91.249,22.905 94.678,26.147Q98.472,29.735 99.347,36.631A35.274,35.274 0,0 1,99.609 41.064L99.609,75.977Z" />
|
||||
</group>
|
||||
<group android:scaleX="0.7425"
|
||||
android:scaleY="0.7425"
|
||||
android:translateX="30.68"
|
||||
android:translateY="30.68">
|
||||
<path
|
||||
android:pathData="M11,9H34"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,54H23"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,9L11,54"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,31L52,31"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M52,31L52,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M34,21L34,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -42,7 +42,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="@dimen/keyline_margin"
|
||||
app:srcCompat="@mipmap/ic_launcher" />
|
||||
app:srcCompat="@drawable/ic_launcher_foreground" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -102,6 +102,15 @@
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingTop="24dp"
|
||||
android:maxWidth="@dimen/single_max_width"
|
||||
android:text="@string/app_waring"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp" />
|
||||
|
||||
@@ -77,10 +77,11 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.hippo.widget.LoadImageView
|
||||
<com.hippo.ehviewer.widget.FixedThumb
|
||||
android:id="@+id/thumb"
|
||||
android:layout_width="@dimen/gallery_detail_thumb_width"
|
||||
android:layout_height="@dimen/gallery_detail_thumb_height" />
|
||||
android:layout_height="@dimen/gallery_detail_thumb_height"
|
||||
app:alwaysCutAndScale="true"/>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
@@ -104,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"
|
||||
@@ -114,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"
|
||||
@@ -128,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"
|
||||
@@ -139,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"
|
||||
@@ -153,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"
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card"
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
@@ -31,15 +32,15 @@
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/thumb_card"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="@dimen/gallery_list_thumb_width"
|
||||
android:layout_height="@dimen/gallery_list_thumb_height">
|
||||
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="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:alwaysCutAndScale="true"
|
||||
app:retryType="click" />
|
||||
app:retryType="click"/>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="@dimen/keyline_margin"
|
||||
app:srcCompat="@mipmap/ic_launcher" />
|
||||
app:srcCompat="@drawable/ic_launcher_foreground" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/username_layout"
|
||||
@@ -83,6 +83,15 @@
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingTop="24dp"
|
||||
android:maxWidth="@dimen/single_max_width"
|
||||
android:text="@string/app_waring"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
android:layout_gravity="bottom"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:fitsSystemWindowsInsets="end"
|
||||
app:hideAnimationBehavior="inward"
|
||||
app:showAnimationBehavior="outward" />
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
~ You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
<background android:drawable="@android:color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -15,8 +15,9 @@
|
||||
~ You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
<background android:drawable="@android:color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 838 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 666 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -15,10 +15,6 @@
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<!-- divider -->
|
||||
<color name="divider">#20ffffff</color>
|
||||
|
||||
<color name="navigation_bar_divider">#1fffffff</color>
|
||||
|
||||
<!-- Lock pattern view -->
|
||||
</resources>
|
||||