Compare commits
104
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f3476895e | ||
|
|
98af874e83 | ||
|
|
02bd5badb9 | ||
|
|
605f86483b | ||
|
|
f0d6ad9848 | ||
|
|
7677498e0c | ||
|
|
d115af60b3 | ||
|
|
b07ab230e9 | ||
|
|
760ca8d6fb | ||
|
|
4f0acb465a | ||
|
|
f1ddc6c3aa | ||
|
|
3b955cc9a9 | ||
|
|
652f503635 | ||
|
|
5f6d2e8dbd | ||
|
|
4925312c4a | ||
|
|
c58bad59e2 | ||
|
|
7aa318890d | ||
|
|
066500f2f8 | ||
|
|
d37752a6db | ||
|
|
cd81a10d28 | ||
|
|
812018af06 | ||
|
|
ee1ddeec12 | ||
|
|
8a3ce0e5ef | ||
|
|
8b9a3a0274 | ||
|
|
08f8f6cc75 | ||
|
|
1052ac5fc5 | ||
|
|
b62b6f95a8 | ||
|
|
ffd719a104 | ||
|
|
2f787ca94b | ||
|
|
990c8ed9c9 | ||
|
|
3492593a06 | ||
|
|
2bea46b15b | ||
|
|
f0903fa8b2 | ||
|
|
60caa099e2 | ||
|
|
586fba4221 | ||
|
|
80faa9a2db | ||
|
|
54fea2929f | ||
|
|
4599ed88ee | ||
|
|
60d75fd780 | ||
|
|
896d71b867 | ||
|
|
6d440730fd | ||
|
|
77a6960e90 | ||
|
|
215eb267fe | ||
|
|
2c86e8b898 | ||
|
|
d7daa47c73 | ||
|
|
3c488916bf | ||
|
|
ba09fcdc3c | ||
|
|
385101a729 | ||
|
|
002c3cf1d5 | ||
|
|
84064549ac | ||
|
|
8c52ad212e | ||
|
|
23e6f78067 | ||
|
|
d5fcb06e8f | ||
|
|
562d1f9a0d | ||
|
|
03f29288ad | ||
|
|
eb84320225 | ||
|
|
10d73ab639 | ||
|
|
cac8f6361a | ||
|
|
32d0758fdc | ||
|
|
c382c9c896 | ||
|
|
e772a52bf2 | ||
|
|
edda52159e | ||
|
|
00c2e683bd | ||
|
|
a88766db56 | ||
|
|
f6c1092cba | ||
|
|
a15b92333a | ||
|
|
21fc20a0f4 | ||
|
|
bf72d772b5 | ||
|
|
76d8575398 | ||
|
|
029dc7ac61 | ||
|
|
0b6f8d1d6f | ||
|
|
0b283bb333 | ||
|
|
c74ee2b5cb | ||
|
|
8263dce3ca | ||
|
|
a37e5e1ab3 | ||
|
|
0baad6c042 | ||
|
|
a2da9ffea6 | ||
|
|
4f3668dae4 | ||
|
|
2598b68b97 | ||
|
|
7d065fb5c5 | ||
|
|
c4cc089eca | ||
|
|
85c377713d | ||
|
|
3e7a1d9d35 | ||
|
|
b0e9d8ec66 | ||
|
|
c25163b3c4 | ||
|
|
12d5aa9e08 | ||
|
|
9fe1743dd9 | ||
|
|
e178880eb4 | ||
|
|
0abab346ab | ||
|
|
63d355ad95 | ||
|
|
17ab6c7532 | ||
|
|
8d82fc39f6 | ||
|
|
d396cae207 | ||
|
|
ec96ca2d7c | ||
|
|
fa2279e480 | ||
|
|
de5b77cc96 | ||
|
|
41a54b76bf | ||
|
|
a09940eb86 | ||
|
|
159b8c13b0 | ||
|
|
498d51ec22 | ||
|
|
24e50da7c9 | ||
|
|
fcc595d9a3 | ||
|
|
d2aecb5eb5 | ||
|
|
e2cfa30052 |
No files matched your search
@@ -17,15 +17,15 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 19
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 19
|
||||
java-version: 11
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
@@ -12,15 +12,15 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 19
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 19
|
||||
java-version: 11
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
@@ -15,15 +15,15 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 19
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 19
|
||||
java-version: 11
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
@@ -32,11 +32,11 @@ jobs:
|
||||
|
||||
- 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
|
||||
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal-A11-.apk
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a-A11-.apk
|
||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64-A11-.apk
|
||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86-A11-.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a-A11-.apk
|
||||
|
||||
- name: Releases
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -44,28 +44,8 @@ jobs:
|
||||
body: Bump Version
|
||||
discussion_category_name: Announcements
|
||||
files: |
|
||||
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
|
||||
app/build/outputs/apk/release/EhViewer-universal-A11-.apk
|
||||
app/build/outputs/apk/release/EhViewer-arm64-v8a-A11-.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86_64-A11-.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86-A11-.apk
|
||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a-A11-.apk
|
||||
+15
-6
@@ -1,9 +1,18 @@
|
||||
[submodule "app/src/main/cpp/libarchive"]
|
||||
path = app/src/main/cpp/libarchive
|
||||
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
|
||||
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
|
||||
url = https://github.com/Ehviewer-Overhauled/libjpeg-turbo
|
||||
[submodule "native/src/main/jni/image/giflib/giflib"]
|
||||
path = native/src/main/jni/image/giflib/giflib
|
||||
url = https://github.com/Ehviewer-Overhauled/giflib
|
||||
[submodule "native/src/main/jni/image/libpng/libpng"]
|
||||
path = native/src/main/jni/image/libpng/libpng
|
||||
url = https://github.com/Ehviewer-Overhauled/libpng
|
||||
[submodule "native/src/main/jni/libarchive"]
|
||||
path = native/src/main/jni/libarchive
|
||||
url = https://github.com/Ehviewer-Overhauled/libarchive
|
||||
[submodule "app/src/main/cpp/liblzma"]
|
||||
path = app/src/main/cpp/liblzma
|
||||
[submodule "native/src/main/jni/liblzma"]
|
||||
path = native/src/main/jni/liblzma
|
||||
url = https://github.com/Ehviewer-Overhauled/xz
|
||||
[submodule "app/src/main/cpp/nettle/nettle"]
|
||||
path = app/src/main/cpp/nettle/nettle
|
||||
[submodule "native/src/main/jni/nettle/nettle"]
|
||||
path = native/src/main/jni/nettle/nettle
|
||||
url = https://github.com/Ehviewer-Overhauled/nettle
|
||||
@@ -1,11 +1,11 @@
|
||||
<p align="right">
|
||||
<strong>English<strong>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-cn.md">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md">
|
||||
简体中文
|
||||
</a>
|
||||
<span> | <span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-tw.md">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md">
|
||||
正體中文
|
||||
</a>
|
||||
</p>
|
||||
@@ -19,7 +19,7 @@
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/LICENSE">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
|
||||
@@ -35,23 +35,23 @@
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#description">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#description">
|
||||
Description
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#download">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#download">
|
||||
Download
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#screenshot">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#screenshot">
|
||||
Screenshot
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#thanks">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#thanks">
|
||||
Thanks
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#license">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#license">
|
||||
License
|
||||
</a>
|
||||
</h3>
|
||||
@@ -66,7 +66,7 @@ with Material Design 3 and Dynamic Color Support
|
||||
|
||||
# Download
|
||||
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="art/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Git it on F-Droid" src="art/get-it-on-fdroid.svg" width="200px"/></a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="art/get-it-on-github.svg" width="200px"/></a>
|
||||
|
||||
App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 and above, also the develop mainline. Version 1.7 support Android 11 and below, drop down some spicetify functions for Android 12 to make app useable on lower Android System
|
||||
|
||||
@@ -84,9 +84,15 @@ Here is the libraries
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
- [material-design-icons](https://github.com/google/material-design-icons)
|
||||
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
|
||||
- [apng](http://apng.sourceforge.net/)
|
||||
- [giflib](http://giflib.sourceforge.net)
|
||||
- [greenDAO](https://github.com/greenrobot/greenDAO)
|
||||
- [jsoup](https://github.com/jhy/jsoup)
|
||||
- [libjpeg-turbo](https://libjpeg-turbo.org/)
|
||||
- [libpng](http://www.libpng.org/pub/png/libpng.html)
|
||||
- [okhttp](https://github.com/square/okhttp)
|
||||
- [RikkaX](https://github.com/RikkaApps/RikkaX)
|
||||
- [roaster](https://github.com/forge/roaster)
|
||||
- [libarchive](http://www.libarchive.org/)
|
||||
- [XZ Utils](https://tukaani.org/xz/)
|
||||
|
||||
|
||||
+1
-2
@@ -4,5 +4,4 @@ manifest-merger-release-report.txt
|
||||
/src/main/libs
|
||||
/src/main/obj
|
||||
# Intellij
|
||||
*.iml
|
||||
/.cxx
|
||||
*.iml
|
||||
+24
-47
@@ -1,13 +1,17 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.devtools.ksp'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
ndkVersion "25.1.8937393"
|
||||
|
||||
Properties localProperties = new Properties()
|
||||
if (project.rootProject.file('local.properties').exists()) {
|
||||
localProperties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
@@ -29,26 +33,20 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
versionCode 180018
|
||||
versionName "1.8.3.0-alpha04"
|
||||
versionCode 114537
|
||||
versionName "1.7.31.0-alpha02"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path 'src/main/cpp/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
jvmTarget = '11'
|
||||
}
|
||||
|
||||
lint {
|
||||
@@ -81,17 +79,19 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.2"
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += 'src/main/java-gen'
|
||||
}
|
||||
}
|
||||
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
task -> task.dependsOn ":daogenerator:executeDaoGenerator"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.1"
|
||||
implementation "androidx.activity:activity:$activity_version"
|
||||
@@ -112,16 +112,6 @@ dependencies {
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||
|
||||
def compose_version = "1.3.0"
|
||||
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"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
|
||||
@@ -130,9 +120,10 @@ dependencies {
|
||||
implementation "androidx.core:core-ktx:$core_version"
|
||||
|
||||
implementation "androidx.customview:customview:1.2.0-alpha02"
|
||||
implementation "androidx.documentfile:documentfile:1.1.0-alpha01"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.6.0-alpha03"
|
||||
def fragment_version = "1.5.4"
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
@@ -145,23 +136,8 @@ dependencies {
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||
|
||||
implementation "androidx.loader:loader:1.1.0"
|
||||
|
||||
def nav_version = "2.5.3"
|
||||
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
implementation "androidx.paging:paging-runtime:3.2.0-alpha03"
|
||||
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
|
||||
|
||||
def room_version = "2.5.0-beta01"
|
||||
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'
|
||||
implementation "androidx.transition:transition-ktx:1.4.1"
|
||||
@@ -183,12 +159,13 @@ dependencies {
|
||||
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.greenrobot:greendao:3.3.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.3'
|
||||
implementation projects.native
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
|
||||
exclude group: 'dev.rikka.rikkax.material', module: 'material'
|
||||
exclude group: 'androidx.compose.material'
|
||||
}
|
||||
Vendored
+17
-8
@@ -7,19 +7,28 @@
|
||||
public static void throw*(...);
|
||||
}
|
||||
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
-keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
|
||||
-keepnames class com.hippo.ehviewer.ui.scene.* { }
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep class com.hippo.ehviewer.dao.* { *; }
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
-mergeinterfacesaggressively
|
||||
-overloadaggressively
|
||||
|
||||
-dontwarn net.sqlcipher.database.**
|
||||
-dontwarn rx.**
|
||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||
-dontwarn org.conscrypt.Conscrypt$Version
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn net.sqlcipher.Cursor
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.conscrypt.Conscrypt
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
@@ -23,11 +23,14 @@
|
||||
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" />
|
||||
@@ -37,13 +40,11 @@
|
||||
android:allowBackup="true"
|
||||
android:appCategory="image"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_scheme"
|
||||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/AppTheme">
|
||||
@@ -143,12 +144,6 @@
|
||||
android:label="@string/settings"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="com.hippo.ehviewer.ui.SecurityActivity"
|
||||
android:configChanges="screenSize"
|
||||
android:label="@string/settings"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
|
||||
android:excludeFromRecents="true"
|
||||
|
||||
@@ -1,354 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <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>
|
||||
|
||||
#define LOG_TAG "libarchive_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
|
||||
|
||||
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;
|
||||
static jlong archiveSize = 0;
|
||||
|
||||
const char supportExt[9][6] = {
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
"gif",
|
||||
"webp",
|
||||
"bmp",
|
||||
"ico",
|
||||
"wbmp",
|
||||
"heif"
|
||||
};
|
||||
|
||||
static inline int filename_is_playable_file(const char *name) {
|
||||
const char *dotptr = strrchr(name, '.');
|
||||
if (!dotptr++)
|
||||
return false;
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
if (strcmp(dotptr, supportExt[i]) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static long archive_list_all_entries(archive_ctx *ctx) {
|
||||
long count = 0;
|
||||
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(ctx->arc));
|
||||
return count;
|
||||
}
|
||||
|
||||
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_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(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 *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;
|
||||
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(ctx->arc));
|
||||
} else {
|
||||
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(ctx->arc);
|
||||
switch (encryptRet) {
|
||||
case 1: // At lease 1 encrypted entry
|
||||
need_encrypt = true;
|
||||
break;
|
||||
case 0: // format supports but no encrypted entry found
|
||||
default:
|
||||
need_encrypt = false;
|
||||
}
|
||||
|
||||
// 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_ctx(ctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToAddr(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 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;
|
||||
}
|
||||
*(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 *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 *env, jobject thiz) {
|
||||
(void) env; /* UNUSED */
|
||||
(void) thiz; /* UNUSED */
|
||||
return need_encrypt;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
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);
|
||||
passwd = realloc(passwd, len * sizeof(char));
|
||||
if (!passwd) {
|
||||
LOGE("Allocate passwd buffer failed");
|
||||
return false;
|
||||
}
|
||||
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
|
||||
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;
|
||||
if (archive_read_data(ctx->arc, tmpBuf, 4096) < ARCHIVE_OK) {
|
||||
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
||||
ret = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
archive_release_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;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/data_space.h>
|
||||
#include <android/imagedecoder.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#define TAG "ImageDecoder_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG ,__VA_ARGS__)
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
|
||||
|
||||
typedef struct {
|
||||
AImageDecoder *decoder;
|
||||
void *srcBuffer;
|
||||
size_t stride;
|
||||
int fd;
|
||||
} SOURCE;
|
||||
|
||||
typedef struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
void *buffer;
|
||||
int bufferLen;
|
||||
bool isAnimated;
|
||||
bool alpha;
|
||||
int delay;
|
||||
SOURCE *src;
|
||||
} IMAGE;
|
||||
|
||||
static void recycleSource(SOURCE *src) {
|
||||
if (!src)
|
||||
return;
|
||||
AImageDecoder_delete(src->decoder);
|
||||
free(src->srcBuffer);
|
||||
free(src);
|
||||
}
|
||||
|
||||
static void recycle(IMAGE *image) {
|
||||
if (!image)
|
||||
return;
|
||||
recycleSource(image->src);
|
||||
free(image->buffer);
|
||||
free(image);
|
||||
}
|
||||
|
||||
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
int left;
|
||||
int line;
|
||||
size_t line_stride;
|
||||
int src_stride;
|
||||
int src_pos;
|
||||
int dst_pos;
|
||||
size_t dst_blank_length;
|
||||
|
||||
// Sanitize
|
||||
if (src_x < 0) {
|
||||
width -= src_x;
|
||||
dst_x -= src_x;
|
||||
src_x = 0;
|
||||
}
|
||||
if (dst_x < 0) {
|
||||
width -= dst_x;
|
||||
src_x -= dst_x;
|
||||
dst_x = 0;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (src_y < 0) {
|
||||
height -= src_y;
|
||||
dst_y -= src_y;
|
||||
src_y = 0;
|
||||
}
|
||||
if (dst_y < 0) {
|
||||
height -= dst_y;
|
||||
src_y -= dst_y;
|
||||
dst_y = 0;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_x + width - src_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
left = dst_x + width - dst_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_y + height - src_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
left = dst_y + height - dst_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init
|
||||
line_stride = (size_t) (width * 4);
|
||||
src_stride = src_w * 4;
|
||||
src_pos = src_y * src_stride + src_x * 4;
|
||||
dst_pos = 0;
|
||||
|
||||
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
|
||||
|
||||
// First line
|
||||
dst_pos += (int) dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int) line_stride;
|
||||
src_pos += src_stride;
|
||||
|
||||
// Other lines
|
||||
dst_blank_length = (size_t) ((dst_w - width) * 4);
|
||||
for (line = 1; line < height; line++) {
|
||||
dst_pos += (int) dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int) line_stride;
|
||||
src_pos += src_stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void decodeCommon(IMAGE *image, SOURCE *src) {
|
||||
image->src = src;
|
||||
AImageDecoder *decoder = src->decoder;
|
||||
image->isAnimated = AImageDecoder_isAnimated(decoder);
|
||||
AImageDecoder_setDataSpace(decoder, ADATASPACE_SRGB);
|
||||
AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888);
|
||||
const AImageDecoderHeaderInfo *headerInfo = AImageDecoder_getHeaderInfo(decoder);
|
||||
image->height = AImageDecoderHeaderInfo_getHeight(headerInfo);
|
||||
image->width = AImageDecoderHeaderInfo_getWidth(headerInfo);
|
||||
image->alpha =
|
||||
AImageDecoderHeaderInfo_getAlphaFlags(headerInfo) == ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
|
||||
src->stride = AImageDecoder_getMinimumStride(decoder);
|
||||
image->bufferLen = (int) (image->height * src->stride);
|
||||
image->buffer = malloc(image->bufferLen);
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_setInternallyHandleDisposePrevious(decoder, false);
|
||||
AImageDecoderFrameInfo *frameInfo = AImageDecoderFrameInfo_create();
|
||||
AImageDecoder_getFrameInfo(decoder, frameInfo);
|
||||
image->delay = (int) AImageDecoderFrameInfo_getDuration(frameInfo) / 1000000;
|
||||
AImageDecoderFrameInfo_delete(frameInfo);
|
||||
}
|
||||
AImageDecoder_decodeImage(decoder, image->buffer, src->stride, image->bufferLen);
|
||||
if (!image->isAnimated) {
|
||||
recycleSource(image->src);
|
||||
image->src = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
IMAGE *createFromFd(int fd) {
|
||||
AImageDecoder *decoder = NULL;
|
||||
if (AImageDecoder_createFromFd(fd, &decoder) != ANDROID_IMAGE_DECODER_SUCCESS)
|
||||
return NULL;
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
||||
src->decoder = decoder;
|
||||
src->fd = fd;
|
||||
decodeCommon(image, src);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *createFromAddr(void *addr, long long size) {
|
||||
AImageDecoder *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;
|
||||
src->srcBuffer = addr;
|
||||
decodeCommon(image, src);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *create(int32_t width, int32_t height, const void *data) {
|
||||
IMAGE *plain = calloc(1, sizeof(IMAGE));
|
||||
void *buffer = NULL;
|
||||
size_t length = width * height * 4;
|
||||
|
||||
buffer = malloc(length);
|
||||
memcpy(buffer, data, length);
|
||||
|
||||
// plain->isAnimated = false; this is unnecessary since we use calloc
|
||||
plain->width = width;
|
||||
plain->height = height;
|
||||
plain->buffer = buffer;
|
||||
plain->bufferLen = (int) length;
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
void render(IMAGE *image, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
copy_pixels(image->buffer, image->width, image->height, src_x, src_y, dst, dst_w, dst_h, dst_x,
|
||||
dst_y, width, height);
|
||||
}
|
||||
|
||||
void advance(IMAGE *image) {
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_advanceFrame(image->src->decoder);
|
||||
if (AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
||||
image->bufferLen) == ANDROID_IMAGE_DECODER_FINISHED) {
|
||||
AImageDecoder_rewind(image->src->decoder);
|
||||
AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
||||
image->bufferLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
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_nativeCreate(JNIEnv *env, jclass clazz, jobject bitmap) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
void *image = NULL;
|
||||
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
|
||||
image = create((int) info.width, (int) info.height, pixels);
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jlong ptr, jint src_x,
|
||||
jint src_y, jobject dst, jint dst_x, jint dst_y, jint width,
|
||||
jint height) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
|
||||
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,
|
||||
height);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, dst);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jlong ptr, jboolean init,
|
||||
jint src_x, jint src_y, jint width, jint height) {
|
||||
if (width * height > IMAGE_TILE_MAX_SIZE)
|
||||
return;
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, tile_buffer, width, height, 0, 0, width, height);
|
||||
|
||||
if (init) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeAdvance(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
advance((IMAGE *) ptr);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetDelay(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
return ((IMAGE *) ptr)->delay;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_image_Image_nativeIsOpaque(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
return ((IMAGE *) ptr)->alpha;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRecycle(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
recycle((IMAGE *) ptr);
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecodeAddr(JNIEnv *env, jclass clazz, jlong addr) {
|
||||
IMAGE *image = createFromAddr((void *) addr, *(long long *) addr);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetFormat(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->isAnimated;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetWidth(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->width;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetHeight(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->height;
|
||||
}
|
||||
Submodule app/src/main/cpp/nettle/nettle deleted from 990abad16c.
@@ -1,40 +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
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
||||
class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
val pfd by lazy { ctx.contentResolver.openFileDescriptor(uri, "r")!! }
|
||||
fun open(): Int {
|
||||
return openArchive(pfd.fd, pfd.statSize)
|
||||
}
|
||||
|
||||
private external fun openArchive(fd: Int, size: Long): Int
|
||||
external fun extractToAddr(index: Int): Long
|
||||
external fun extractToFd(index: Int, fd: Int)
|
||||
external fun getFilename(index: Int): String
|
||||
external fun needPassword(): Boolean
|
||||
external fun providePassword(str: String): Boolean
|
||||
private external fun closeArchive()
|
||||
fun close() {
|
||||
closeArchive()
|
||||
pfd.close()
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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,6 +18,7 @@ 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(
|
||||
@@ -25,7 +26,7 @@ class CheckBoxDialogBuilder constructor(
|
||||
message: String?,
|
||||
checkText: String?,
|
||||
checked: Boolean
|
||||
) : BaseDialogBuilder(
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
|
||||
@@ -30,7 +31,7 @@ class EditTextDialogBuilder constructor(
|
||||
context: Context,
|
||||
text: String?,
|
||||
hint: String?
|
||||
) : BaseDialogBuilder(
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
), OnEditorActionListener {
|
||||
private val mTextInputLayout: TextInputLayout
|
||||
|
||||
@@ -23,13 +23,14 @@ 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
|
||||
) : BaseDialogBuilder(
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
|
||||
@@ -167,7 +167,6 @@ public final class DiskLruCache implements Closeable {
|
||||
* its sequence number is not equal to its entry's sequence number.
|
||||
*/
|
||||
private long nextSequenceNumber = 0;
|
||||
|
||||
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
|
||||
this.directory = directory;
|
||||
this.appVersion = appVersion;
|
||||
@@ -176,7 +175,21 @@ public final class DiskLruCache implements Closeable {
|
||||
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
this.valueCount = valueCount;
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the cache in {@code directory}, creating a cache if none exists
|
||||
@@ -231,21 +244,7 @@ public final class DiskLruCache implements Closeable {
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
|
||||
@@ -156,6 +156,7 @@ public final class ConacoTask<V> {
|
||||
} else if (mNetworkLoadTask != null) { // Getting from network
|
||||
mNetworkLoadTask.cancel(false);
|
||||
if (mCall != null) {
|
||||
mCall.cancel();
|
||||
mCall = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ 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;
|
||||
@@ -44,8 +43,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;
|
||||
@@ -61,13 +60,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,8 +107,7 @@ public class EhApplication extends SceneApplication {
|
||||
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
|
||||
private static final long DEBUG_PRINT_INTERVAL = 3000L;
|
||||
|
||||
public static boolean locked = true;
|
||||
public static long locked_last_leave_time = 0;
|
||||
public static boolean locked = false;
|
||||
|
||||
private static EhApplication instance;
|
||||
|
||||
@@ -328,6 +326,7 @@ public class EhApplication extends SceneApplication {
|
||||
super.onCreate();
|
||||
|
||||
Native.initialize();
|
||||
ClipboardUtil.initialize(this);
|
||||
GetText.initialize(this);
|
||||
StatusCodeException.initialize(this);
|
||||
Settings.initialize(this);
|
||||
@@ -338,10 +337,11 @@ public class EhApplication extends SceneApplication {
|
||||
EhEngine.initialize();
|
||||
BitmapUtils.initialize(this);
|
||||
|
||||
// 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()));
|
||||
if (EhDB.needMerge()) {
|
||||
EhDB.mergeOldDB(this);
|
||||
}
|
||||
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
|
||||
|
||||
// Do io tasks in new thread
|
||||
@@ -429,7 +429,7 @@ public class EhApplication extends SceneApplication {
|
||||
Activity activity = getTopActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
AlertDialog dialog = new BaseDialogBuilder(activity)
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(activity)
|
||||
.setMessage(Html.fromHtml(html))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
@@ -532,9 +532,9 @@ public class EhApplication extends SceneApplication {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public EhActivity getTopActivity() {
|
||||
public Activity getTopActivity() {
|
||||
if (!mActivityList.isEmpty()) {
|
||||
return (EhActivity) mActivityList.get(mActivityList.size() - 1);
|
||||
return mActivityList.get(mActivityList.size() - 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -590,8 +590,6 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,24 +17,27 @@
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.paging.PagingSource;
|
||||
import androidx.room.Room;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.dao.BasicDao;
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder;
|
||||
import com.hippo.ehviewer.dao.DaoMaster;
|
||||
import com.hippo.ehviewer.dao.DaoSession;
|
||||
import com.hippo.ehviewer.dao.DownloadDirname;
|
||||
import com.hippo.ehviewer.dao.DownloadDirnameDao;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.dao.DownloadLabelDao;
|
||||
import com.hippo.ehviewer.dao.DownloadsDao;
|
||||
import com.hippo.ehviewer.dao.EhDatabase;
|
||||
import com.hippo.ehviewer.dao.Filter;
|
||||
import com.hippo.ehviewer.dao.FilterDao;
|
||||
import com.hippo.ehviewer.dao.HistoryDao;
|
||||
import com.hippo.ehviewer.dao.HistoryInfo;
|
||||
import com.hippo.ehviewer.dao.LocalFavoriteInfo;
|
||||
@@ -43,8 +46,14 @@ import com.hippo.ehviewer.dao.QuickSearch;
|
||||
import com.hippo.ehviewer.dao.QuickSearchDao;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.SqlUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
|
||||
import org.greenrobot.greendao.AbstractDao;
|
||||
import org.greenrobot.greendao.query.CloseableListIterator;
|
||||
import org.greenrobot.greendao.query.LazyList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -56,9 +65,15 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EhDB {
|
||||
private static final int CUR_DB_VER = 4;
|
||||
|
||||
private static EhDatabase db;
|
||||
private static final String TAG = EhDB.class.getSimpleName();
|
||||
|
||||
private static final int MAX_HISTORY_COUNT = 200;
|
||||
|
||||
private static DaoSession sDaoSession;
|
||||
|
||||
private static boolean sHasOldDB;
|
||||
private static boolean sNewDB;
|
||||
|
||||
private static void upgradeDB(SQLiteDatabase db, int oldVersion) {
|
||||
switch (oldVersion) {
|
||||
@@ -100,12 +115,219 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static void initialize(Context context) {
|
||||
db = Room.databaseBuilder(context, EhDatabase.class, "eh.db").allowMainThreadQueries().build();
|
||||
sHasOldDB = context.getDatabasePath("data").exists();
|
||||
|
||||
DBOpenHelper helper = new DBOpenHelper(
|
||||
context.getApplicationContext(), "eh.db", null);
|
||||
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
|
||||
sDaoSession = daoMaster.newSession();
|
||||
}
|
||||
|
||||
public static boolean needMerge() {
|
||||
return sNewDB && sHasOldDB;
|
||||
}
|
||||
|
||||
public static void mergeOldDB(Context context) {
|
||||
sNewDB = false;
|
||||
|
||||
OldDBHelper oldDBHelper = new OldDBHelper(context);
|
||||
SQLiteDatabase oldDB;
|
||||
try {
|
||||
oldDB = oldDBHelper.getReadableDatabase();
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get GalleryInfo list
|
||||
SparseJLArray<GalleryInfo> map = new SparseJLArray<>();
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_GALLERY, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
GalleryInfo gi = new GalleryInfo();
|
||||
gi.gid = cursor.getInt(0);
|
||||
gi.token = cursor.getString(1);
|
||||
gi.title = cursor.getString(2);
|
||||
gi.posted = cursor.getString(3);
|
||||
gi.category = cursor.getInt(4);
|
||||
gi.thumb = cursor.getString(5);
|
||||
gi.uploader = cursor.getString(6);
|
||||
try {
|
||||
// In 0.6.x version, NaN is stored
|
||||
gi.rating = cursor.getFloat(7);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
gi.rating = -1.0f;
|
||||
}
|
||||
|
||||
map.put(gi.gid, gi);
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Merge local favorites
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_LOCAL_FAVOURITE, null);
|
||||
if (cursor != null) {
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
long i = 0L;
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalFavoriteInfo info = new LocalFavoriteInfo(gi);
|
||||
info.setTime(i);
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
|
||||
// Merge quick search
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_TAG, null);
|
||||
if (cursor != null) {
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
QuickSearch quickSearch = new QuickSearch();
|
||||
|
||||
int mode = cursor.getInt(2);
|
||||
String search = cursor.getString(4);
|
||||
String tag = cursor.getString(7);
|
||||
if (mode == ListUrlBuilder.MODE_UPLOADER && search != null &&
|
||||
search.startsWith("uploader:")) {
|
||||
search = search.substring("uploader:".length());
|
||||
}
|
||||
|
||||
quickSearch.setTime(cursor.getInt(0));
|
||||
quickSearch.setName(cursor.getString(1));
|
||||
quickSearch.setMode(mode);
|
||||
quickSearch.setCategory(cursor.getInt(3));
|
||||
quickSearch.setKeyword(mode == ListUrlBuilder.MODE_TAG ? tag : search);
|
||||
quickSearch.setAdvanceSearch(cursor.getInt(5));
|
||||
quickSearch.setMinRating(cursor.getInt(6));
|
||||
|
||||
dao.insert(quickSearch);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Merge download info
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_DOWNLOAD, null);
|
||||
if (cursor != null) {
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
long i = 0L;
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
DownloadInfo info = new DownloadInfo(gi);
|
||||
int state = cursor.getInt(2);
|
||||
int legacy = cursor.getInt(3);
|
||||
if (state == DownloadInfo.STATE_FINISH && legacy > 0) {
|
||||
state = DownloadInfo.STATE_FAILED;
|
||||
}
|
||||
info.setState(state);
|
||||
info.setLegacy(legacy);
|
||||
if (cursor.getColumnCount() == 5) {
|
||||
info.setTime(cursor.getLong(4));
|
||||
} else {
|
||||
info.setTime(i);
|
||||
}
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
// Merge history info
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_HISTORY, null);
|
||||
if (cursor != null) {
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
HistoryInfo info = new HistoryInfo(gi);
|
||||
info.setMode(cursor.getInt(1));
|
||||
info.setTime(cursor.getLong(2));
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
oldDBHelper.close();
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized List<DownloadInfo> getAllDownloadInfo() {
|
||||
DownloadsDao dao = db.downloadsDao();
|
||||
List<DownloadInfo> list = dao.list();
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
List<DownloadInfo> list = dao.queryBuilder().orderDesc(DownloadsDao.Properties.Time).list();
|
||||
// Fix state
|
||||
for (DownloadInfo info : list) {
|
||||
if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) {
|
||||
@@ -117,7 +339,7 @@ public class EhDB {
|
||||
|
||||
// Insert or update
|
||||
public static synchronized void putDownloadInfo(DownloadInfo downloadInfo) {
|
||||
DownloadsDao dao = db.downloadsDao();
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
if (null != dao.load(downloadInfo.gid)) {
|
||||
// Update
|
||||
dao.update(downloadInfo);
|
||||
@@ -127,13 +349,13 @@ public class EhDB {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadInfo(DownloadInfo downloadInfo) {
|
||||
db.downloadsDao().delete(downloadInfo);
|
||||
public static synchronized void removeDownloadInfo(long gid) {
|
||||
sDaoSession.getDownloadsDao().deleteByKey(gid);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static synchronized String getDownloadDirname(long gid) {
|
||||
DownloadDirnameDao dao = db.downloadDirnameDao();
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
DownloadDirname raw = dao.load(gid);
|
||||
if (raw != null) {
|
||||
return raw.getDirname();
|
||||
@@ -146,7 +368,7 @@ public class EhDB {
|
||||
* Insert or update
|
||||
*/
|
||||
public static synchronized void putDownloadDirname(long gid, String dirname) {
|
||||
DownloadDirnameDao dao = db.downloadDirnameDao();
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
DownloadDirname raw = dao.load(gid);
|
||||
if (raw != null) { // Update
|
||||
raw.setDirname(dirname);
|
||||
@@ -160,23 +382,23 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadDirname(long gid) {
|
||||
DownloadDirnameDao dao = db.downloadDirnameDao();
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
dao.deleteByKey(gid);
|
||||
}
|
||||
|
||||
public static synchronized void clearDownloadDirname() {
|
||||
DownloadDirnameDao dao = db.downloadDirnameDao();
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
dao.deleteAll();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static synchronized List<DownloadLabel> getAllDownloadLabelList() {
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
return dao.list();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
return dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time).list();
|
||||
}
|
||||
|
||||
public static synchronized DownloadLabel addDownloadLabel(String label) {
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
DownloadLabel raw = new DownloadLabel();
|
||||
raw.setLabel(label);
|
||||
raw.setTime(System.currentTimeMillis());
|
||||
@@ -187,13 +409,13 @@ public class EhDB {
|
||||
public static synchronized DownloadLabel addDownloadLabel(DownloadLabel raw) {
|
||||
// Reset id
|
||||
raw.setId(null);
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
raw.setId(dao.insert(raw));
|
||||
return raw;
|
||||
}
|
||||
|
||||
public static synchronized void updateDownloadLabel(DownloadLabel raw) {
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
dao.update(raw);
|
||||
}
|
||||
|
||||
@@ -206,8 +428,9 @@ public class EhDB {
|
||||
int offset = reverse ? toPosition : fromPosition;
|
||||
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
|
||||
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
List<DownloadLabel> list = dao.list(offset, limit);
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
List<DownloadLabel> list = dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time)
|
||||
.offset(offset).limit(limit).list();
|
||||
|
||||
int step = reverse ? 1 : -1;
|
||||
int start = reverse ? limit - 1 : 0;
|
||||
@@ -218,44 +441,50 @@ public class EhDB {
|
||||
}
|
||||
list.get(start).setTime(toTime);
|
||||
|
||||
dao.update(list);
|
||||
dao.updateInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadLabel(DownloadLabel raw) {
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
dao.delete(raw);
|
||||
}
|
||||
|
||||
public static synchronized List<GalleryInfo> getAllLocalFavorites() {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.list();
|
||||
return new ArrayList<>(list);
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time).list();
|
||||
List<GalleryInfo> result = new ArrayList<>();
|
||||
result.addAll(list);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.list();
|
||||
return new ArrayList<>(list);
|
||||
query = SqlUtils.sqlEscapeString("%" + query + "%");
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time)
|
||||
.where(LocalFavoritesDao.Properties.Title.like(query)).list();
|
||||
List<GalleryInfo> result = new ArrayList<>();
|
||||
result.addAll(list);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static synchronized void removeLocalFavorites(long gid) {
|
||||
db.localFavoritesDao().deleteByKey(gid);
|
||||
sDaoSession.getLocalFavoritesDao().deleteByKey(gid);
|
||||
}
|
||||
|
||||
public static synchronized void removeLocalFavorites(long[] gidArray) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
for (long gid : gidArray) {
|
||||
dao.deleteByKey(gid);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized boolean containLocalFavorites(long gid) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
return null != dao.load(gid);
|
||||
}
|
||||
|
||||
public static synchronized void putLocalFavorites(GalleryInfo galleryInfo) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
if (null == dao.load(galleryInfo.gid)) {
|
||||
LocalFavoriteInfo info;
|
||||
if (galleryInfo instanceof LocalFavoriteInfo) {
|
||||
@@ -275,31 +504,31 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static synchronized List<QuickSearch> getAllQuickSearch() {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
return dao.list();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
return dao.queryBuilder().orderAsc(QuickSearchDao.Properties.Time).list();
|
||||
}
|
||||
|
||||
public static synchronized void insertQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
quickSearch.id = null;
|
||||
quickSearch.time = System.currentTimeMillis();
|
||||
quickSearch.id = dao.insert(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void importQuickSearch(List<QuickSearch> quickSearchList) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
dao.insert(quickSearch);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
dao.update(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
dao.delete(quickSearch);
|
||||
}
|
||||
|
||||
@@ -312,8 +541,9 @@ public class EhDB {
|
||||
int offset = reverse ? toPosition : fromPosition;
|
||||
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
|
||||
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
List<QuickSearch> list = dao.list(offset, limit);
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
List<QuickSearch> list = dao.queryBuilder().orderAsc(QuickSearchDao.Properties.Time)
|
||||
.offset(offset).limit(limit).list();
|
||||
|
||||
int step = reverse ? 1 : -1;
|
||||
int start = reverse ? limit - 1 : 0;
|
||||
@@ -324,19 +554,15 @@ public class EhDB {
|
||||
}
|
||||
list.get(start).setTime(toTime);
|
||||
|
||||
dao.update(list);
|
||||
dao.updateInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized List<HistoryInfo> getHistoryList() {
|
||||
return db.historyDao().list();
|
||||
}
|
||||
|
||||
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
|
||||
return db.historyDao().listLazy();
|
||||
public static synchronized LazyList<HistoryInfo> getHistoryLazyList() {
|
||||
return sDaoSession.getHistoryDao().queryBuilder().orderDesc(HistoryDao.Properties.Time).listLazy();
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
HistoryInfo info = dao.load(galleryInfo.gid);
|
||||
if (null != info) {
|
||||
// Update time
|
||||
@@ -347,42 +573,49 @@ public class EhDB {
|
||||
info = new HistoryInfo(galleryInfo);
|
||||
info.time = System.currentTimeMillis();
|
||||
dao.insert(info);
|
||||
List<HistoryInfo> list = dao.queryBuilder().orderDesc(HistoryDao.Properties.Time)
|
||||
.limit(-1).offset(MAX_HISTORY_COUNT).list();
|
||||
dao.deleteInTx(list);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(List<HistoryInfo> historyInfoList) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
for (HistoryInfo info : historyInfoList) {
|
||||
if (null == dao.load(info.gid)) {
|
||||
dao.insert(info);
|
||||
}
|
||||
}
|
||||
|
||||
List<HistoryInfo> list = dao.queryBuilder().orderDesc(HistoryDao.Properties.Time)
|
||||
.limit(-1).offset(MAX_HISTORY_COUNT).list();
|
||||
dao.deleteInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized void deleteHistoryInfo(HistoryInfo info) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
dao.delete(info);
|
||||
}
|
||||
|
||||
public static synchronized void clearHistoryInfo() {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
dao.deleteAll();
|
||||
}
|
||||
|
||||
public static synchronized List<Filter> getAllFilter() {
|
||||
return db.filterDao().list();
|
||||
return sDaoSession.getFilterDao().queryBuilder().list();
|
||||
}
|
||||
|
||||
public static synchronized boolean addFilter(Filter filter) {
|
||||
Filter existFilter;
|
||||
try {
|
||||
existFilter = db.filterDao().load(filter.text, filter.mode);
|
||||
existFilter = sDaoSession.getFilterDao().queryBuilder().where(FilterDao.Properties.Text.eq(filter.text), FilterDao.Properties.Mode.eq(filter.mode)).unique();
|
||||
} catch (Exception e) {
|
||||
existFilter = null;
|
||||
}
|
||||
if (existFilter == null) {
|
||||
filter.setId(null);
|
||||
filter.setId(db.filterDao().insert(filter));
|
||||
filter.setId(sDaoSession.getFilterDao().insert(filter));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -390,19 +623,23 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static synchronized void deleteFilter(Filter filter) {
|
||||
db.filterDao().delete(filter);
|
||||
sDaoSession.getFilterDao().delete(filter);
|
||||
}
|
||||
|
||||
public static synchronized void triggerFilter(Filter filter) {
|
||||
filter.setEnable(!filter.enable);
|
||||
db.filterDao().update(filter);
|
||||
sDaoSession.getFilterDao().update(filter);
|
||||
}
|
||||
|
||||
private static <T> boolean copyDao(BasicDao<T> from, BasicDao<T> to) {
|
||||
List<T> list = from.list();
|
||||
for (T item : list)
|
||||
to.insert(item);
|
||||
return false;
|
||||
private static <T> boolean copyDao(AbstractDao<T, ?> from, AbstractDao<T, ?> to) {
|
||||
try (CloseableListIterator<T> iterator = from.queryBuilder().listIterator()) {
|
||||
while (iterator.hasNext()) {
|
||||
to.insert(iterator.next());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static synchronized boolean exportDB(Context context, Uri uri) {
|
||||
@@ -410,24 +647,31 @@ public class EhDB {
|
||||
|
||||
// Delete old export db
|
||||
context.deleteDatabase(ehExportName);
|
||||
EhDatabase newDb = Room.databaseBuilder(context, EhDatabase.class, ehExportName).allowMainThreadQueries().build();
|
||||
|
||||
DBOpenHelper helper = new DBOpenHelper(context.getApplicationContext(), ehExportName, null);
|
||||
|
||||
try {
|
||||
// Copy data to a export db
|
||||
if (copyDao(db.downloadsDao(), newDb.downloadsDao()))
|
||||
return false;
|
||||
if (copyDao(db.downloadLabelDao(), newDb.downloadLabelDao()))
|
||||
return false;
|
||||
if (copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao()))
|
||||
return false;
|
||||
if (copyDao(db.historyDao(), newDb.historyDao()))
|
||||
return false;
|
||||
if (copyDao(db.quickSearchDao(), newDb.quickSearchDao()))
|
||||
return false;
|
||||
if (copyDao(db.localFavoritesDao(), newDb.localFavoritesDao()))
|
||||
return false;
|
||||
if (copyDao(db.filterDao(), newDb.filterDao()))
|
||||
return false;
|
||||
try (SQLiteDatabase db = helper.getWritableDatabase()) {
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
DaoSession exportSession = daoMaster.newSession();
|
||||
if (!copyDao(sDaoSession.getDownloadsDao(), exportSession.getDownloadsDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getDownloadLabelDao(), exportSession.getDownloadLabelDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getDownloadDirnameDao(), exportSession.getDownloadDirnameDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getHistoryDao(), exportSession.getHistoryDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getQuickSearchDao(), exportSession.getQuickSearchDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getLocalFavoritesDao(), exportSession.getLocalFavoritesDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getBookmarksDao(), exportSession.getBookmarksDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getFilterDao(), exportSession.getFilterDao()))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy export db to data dir
|
||||
File dbFile = context.getDatabasePath(ehExportName);
|
||||
@@ -474,89 +718,76 @@ public class EhDB {
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
SQLiteDatabase oldDB = SQLiteDatabase.openDatabase(
|
||||
SQLiteDatabase db = SQLiteDatabase.openDatabase(
|
||||
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
|
||||
int newVersion = CUR_DB_VER;
|
||||
int oldVersion = oldDB.getVersion();
|
||||
int newVersion = DaoMaster.SCHEMA_VERSION;
|
||||
int oldVersion = db.getVersion();
|
||||
if (oldVersion < newVersion) {
|
||||
upgradeDB(oldDB, oldVersion);
|
||||
oldDB.setVersion(newVersion);
|
||||
upgradeDB(db, oldVersion);
|
||||
db.setVersion(newVersion);
|
||||
} else if (oldVersion > newVersion) {
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
}
|
||||
|
||||
String tmpDBName = "tmp.db";
|
||||
context.deleteDatabase(tmpDBName);
|
||||
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) {}
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
DaoSession session = daoMaster.newSession();
|
||||
|
||||
// Downloads
|
||||
try {
|
||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
} catch (Exception ignored) {}
|
||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
||||
List<DownloadInfo> downloadInfoList = session.getDownloadsDao().queryBuilder().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
|
||||
// Download label
|
||||
List<DownloadLabel> downloadLabelList = session.getDownloadLabelDao().queryBuilder().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
|
||||
// Download dirname
|
||||
try {
|
||||
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
List<DownloadDirname> downloadDirnameList = session.getDownloadDirnameDao().queryBuilder().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
|
||||
// History
|
||||
try {
|
||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
} catch (Exception ignored) {}
|
||||
List<HistoryInfo> historyInfoList = session.getHistoryDao().queryBuilder().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
|
||||
// QuickSearch
|
||||
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;
|
||||
}
|
||||
List<QuickSearch> quickSearchList = session.getQuickSearchDao().queryBuilder().list();
|
||||
List<QuickSearch> currentQuickSearchList = sDaoSession.getQuickSearchDao().queryBuilder().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);
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
} catch (Exception ignored) {}
|
||||
if (null == name) {
|
||||
continue;
|
||||
}
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
|
||||
// LocalFavorites
|
||||
try {
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = session.getLocalFavoritesDao().queryBuilder().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
|
||||
// Bookmarks
|
||||
// TODO
|
||||
|
||||
// Filter
|
||||
try {
|
||||
List<Filter> filterList = oldRoomDatabase.filterDao().list();
|
||||
List<Filter> currentFilterList = db.filterDao().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
}
|
||||
List<Filter> filterList = session.getFilterDao().queryBuilder().list();
|
||||
List<Filter> currentFilterList = sDaoSession.getFilterDao().queryBuilder().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
@@ -565,4 +796,46 @@ public class EhDB {
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DBOpenHelper extends DaoMaster.OpenHelper {
|
||||
|
||||
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
|
||||
super(context, name, factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
super.onCreate(db);
|
||||
sNewDB = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
upgradeDB(db, oldVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OldDBHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String DB_NAME = "data";
|
||||
private static final int VERSION = 4;
|
||||
|
||||
private static final String TABLE_GALLERY = "gallery";
|
||||
private static final String TABLE_LOCAL_FAVOURITE = "local_favourite";
|
||||
private static final String TABLE_TAG = "tag";
|
||||
private static final String TABLE_DOWNLOAD = "download";
|
||||
private static final String TABLE_HISTORY = "history";
|
||||
|
||||
public OldDBHelper(Context context) {
|
||||
super(context, DB_NAME, null, VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import com.hippo.conaco.ValueHelper;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
@@ -35,8 +34,7 @@ public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
FileInputStream is = (FileInputStream) isPipe.open();
|
||||
return ImageBitmap.decode(is.getFD());
|
||||
return ImageBitmap.decode(isPipe.open());
|
||||
} catch (OutOfMemoryError | IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
|
||||
@@ -50,8 +50,6 @@ 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;
|
||||
@@ -78,8 +76,6 @@ 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";
|
||||
@@ -92,7 +88,6 @@ 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";
|
||||
@@ -436,7 +431,6 @@ public class Settings {
|
||||
putInt(KEY_VERSION_CODE, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getDisplayName() {
|
||||
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
|
||||
}
|
||||
@@ -1067,9 +1061,6 @@ 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);
|
||||
@@ -1192,12 +1183,4 @@ public class Settings {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,6 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -111,52 +108,30 @@ public class GalleryInfo implements Parcelable {
|
||||
return new GalleryInfo[size];
|
||||
}
|
||||
};
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "GID")
|
||||
public long gid;
|
||||
@ColumnInfo(name = "TOKEN")
|
||||
public String token;
|
||||
@ColumnInfo(name = "TITLE")
|
||||
public String title;
|
||||
@ColumnInfo(name = "TITLE_JPN")
|
||||
public String titleJpn;
|
||||
@ColumnInfo(name = "THUMB")
|
||||
public String thumb;
|
||||
@ColumnInfo(name = "CATEGORY")
|
||||
public int category;
|
||||
@ColumnInfo(name = "POSTED")
|
||||
public String posted;
|
||||
@ColumnInfo(name = "UPLOADER")
|
||||
public String uploader;
|
||||
@Ignore
|
||||
public boolean disowned;
|
||||
@ColumnInfo(name = "RATING")
|
||||
public float rating;
|
||||
@Ignore
|
||||
public boolean rated;
|
||||
@Nullable
|
||||
@Ignore
|
||||
public String[] simpleTags;
|
||||
@Ignore
|
||||
public int pages;
|
||||
@Ignore
|
||||
public int thumbWidth;
|
||||
@Ignore
|
||||
public int thumbHeight;
|
||||
@Ignore
|
||||
public int spanSize;
|
||||
@Ignore
|
||||
public int spanIndex;
|
||||
@Ignore
|
||||
public int spanGroupIndex;
|
||||
/**
|
||||
* language from title
|
||||
*/
|
||||
@ColumnInfo(name = "SIMPLE_LANGUAGE")
|
||||
public String simpleLanguage;
|
||||
@Ignore
|
||||
public int favoriteSlot = -2;
|
||||
@Ignore
|
||||
public String favoriteName;
|
||||
|
||||
public GalleryInfo() {
|
||||
@@ -195,7 +170,6 @@ public class GalleryInfo implements Parcelable {
|
||||
}
|
||||
|
||||
private void generateSLangFromTags() {
|
||||
assert simpleTags != null;
|
||||
for (String tag : simpleTags) {
|
||||
for (int i = 0; i < S_LANGS.length; i++) {
|
||||
if (S_LANG_TAGS[i].equals(tag)) {
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FavoritesParser {
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+(-\\d+)?)");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+-\\d+)");
|
||||
|
||||
public static Result parse(String body) throws Exception {
|
||||
if (body.contains("This page requires you to log on.</p>")) {
|
||||
@@ -65,9 +65,9 @@ public class FavoritesParser {
|
||||
|
||||
try {
|
||||
var d = Jsoup.parse(body);
|
||||
var next = d.getElementById("unext");
|
||||
assert next != null;
|
||||
var href = next.attr("href");
|
||||
var snv = d.getElementsByClass("searchnav").first();
|
||||
assert snv != null;
|
||||
var href = snv.child(3).child(0).attr("href");
|
||||
var matcher = PATTERN_NEXT_PAGE.matcher(href);
|
||||
if (matcher.find()) {
|
||||
re.nextPage = matcher.group(1);
|
||||
|
||||
@@ -333,7 +333,7 @@ public class GalleryListParser {
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
if (es.size() < 2 || !NO_UNFILTERED_TEXT.equals(es.get(1).text())) {
|
||||
Log.d(TAG, "No gallery found");
|
||||
throw new ParseException("No gallery", body);
|
||||
}
|
||||
}
|
||||
result.galleryInfoList = list;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface BasicDao<T> {
|
||||
List<T> list();
|
||||
long insert(T t);
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
|
||||
@Entity(tableName = "BOOKMARKS")
|
||||
public class BookmarkInfo extends GalleryInfo {
|
||||
public static final Creator<BookmarkInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public BookmarkInfo createFromParcel(Parcel source) {
|
||||
return new BookmarkInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BookmarkInfo[] newArray(int size) {
|
||||
return new BookmarkInfo[size];
|
||||
}
|
||||
};
|
||||
@ColumnInfo(name = "PAGE")
|
||||
public int page;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
|
||||
protected BookmarkInfo(Parcel in) {
|
||||
super(in);
|
||||
this.page = in.readInt();
|
||||
this.time = in.readLong();
|
||||
}
|
||||
|
||||
public BookmarkInfo(GalleryInfo galleryInfo) {
|
||||
this.gid = galleryInfo.gid;
|
||||
this.token = galleryInfo.token;
|
||||
this.title = galleryInfo.title;
|
||||
this.titleJpn = galleryInfo.titleJpn;
|
||||
this.thumb = galleryInfo.thumb;
|
||||
this.category = galleryInfo.category;
|
||||
this.posted = galleryInfo.posted;
|
||||
this.uploader = galleryInfo.uploader;
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
public BookmarkInfo(int page, long time) {
|
||||
this.page = page;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitleJpn() {
|
||||
return titleJpn;
|
||||
}
|
||||
|
||||
public void setTitleJpn(String titleJpn) {
|
||||
this.titleJpn = titleJpn;
|
||||
}
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getPosted() {
|
||||
return posted;
|
||||
}
|
||||
|
||||
public void setPosted(String posted) {
|
||||
this.posted = posted;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public float getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public String getSimpleLanguage() {
|
||||
return simpleLanguage;
|
||||
}
|
||||
|
||||
public void setSimpleLanguage(String simpleLanguage) {
|
||||
this.simpleLanguage = simpleLanguage;
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(this.page);
|
||||
dest.writeLong(this.time);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface BookmarksDao extends BasicDao<BookmarkInfo> {
|
||||
@Insert
|
||||
long insert(BookmarkInfo bookmark);
|
||||
|
||||
@Delete
|
||||
void delete(BookmarkInfo bookmark);
|
||||
|
||||
@Query("SELECT * FROM BOOKMARKS ORDER BY TIME DESC")
|
||||
List<BookmarkInfo> list();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "DOWNLOAD_DIRNAME")
|
||||
public class DownloadDirname {
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "GID")
|
||||
private long gid;
|
||||
@ColumnInfo(name = "DIRNAME")
|
||||
private String dirname;
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getDirname() {
|
||||
return dirname;
|
||||
}
|
||||
|
||||
public void setDirname(String dirname) {
|
||||
this.dirname = dirname;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DownloadDirnameDao extends BasicDao<DownloadDirname> {
|
||||
@Query("SELECT * FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
|
||||
DownloadDirname load(long gid);
|
||||
|
||||
@Update
|
||||
void update(DownloadDirname downloadDirname);
|
||||
|
||||
@Insert
|
||||
long insert(DownloadDirname downloadDirname);
|
||||
|
||||
@Query("DELETE FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
|
||||
void deleteByKey(long gid);
|
||||
|
||||
@Query("DELETE FROM DOWNLOAD_DIRNAME")
|
||||
void deleteAll();
|
||||
|
||||
@Query("SELECT * FROM DOWNLOAD_DIRNAME")
|
||||
List<DownloadDirname> list();
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
|
||||
@Entity(tableName = "DOWNLOADS")
|
||||
public class DownloadInfo extends GalleryInfo {
|
||||
public static final Creator<DownloadInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public DownloadInfo createFromParcel(Parcel source) {
|
||||
return new DownloadInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadInfo[] newArray(int size) {
|
||||
return new DownloadInfo[size];
|
||||
}
|
||||
};
|
||||
public static final int STATE_INVALID = -1;
|
||||
public static final int STATE_NONE = 0;
|
||||
public static final int STATE_WAIT = 1;
|
||||
public static final int STATE_DOWNLOAD = 2;
|
||||
public static final int STATE_FINISH = 3;
|
||||
public static final int STATE_FAILED = 4;
|
||||
@ColumnInfo(name = "STATE")
|
||||
public int state;
|
||||
@ColumnInfo(name = "LEGACY")
|
||||
public int legacy;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
@ColumnInfo(name = "LABEL")
|
||||
public String label;
|
||||
@Ignore
|
||||
public long speed;
|
||||
@Ignore
|
||||
public long remaining;
|
||||
@Ignore
|
||||
public int finished;
|
||||
@Ignore
|
||||
public int downloaded;
|
||||
@Ignore
|
||||
public int total;
|
||||
|
||||
DownloadInfo() {
|
||||
}
|
||||
|
||||
protected DownloadInfo(Parcel in) {
|
||||
super(in);
|
||||
this.state = in.readInt();
|
||||
this.legacy = in.readInt();
|
||||
this.time = in.readLong();
|
||||
this.label = in.readString();
|
||||
}
|
||||
|
||||
public DownloadInfo(GalleryInfo galleryInfo) {
|
||||
this.gid = galleryInfo.gid;
|
||||
this.token = galleryInfo.token;
|
||||
this.title = galleryInfo.title;
|
||||
this.titleJpn = galleryInfo.titleJpn;
|
||||
this.thumb = galleryInfo.thumb;
|
||||
this.category = galleryInfo.category;
|
||||
this.posted = galleryInfo.posted;
|
||||
this.uploader = galleryInfo.uploader;
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
public DownloadInfo(int state, int legacy, long time, String label, long speed, long remaining,
|
||||
int finished, int downloaded, int total) {
|
||||
this.state = state;
|
||||
this.legacy = legacy;
|
||||
this.time = time;
|
||||
this.label = label;
|
||||
this.speed = speed;
|
||||
this.remaining = remaining;
|
||||
this.finished = finished;
|
||||
this.downloaded = downloaded;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitleJpn() {
|
||||
return titleJpn;
|
||||
}
|
||||
|
||||
public void setTitleJpn(String titleJpn) {
|
||||
this.titleJpn = titleJpn;
|
||||
}
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getPosted() {
|
||||
return posted;
|
||||
}
|
||||
|
||||
public void setPosted(String posted) {
|
||||
this.posted = posted;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public float getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public String getSimpleLanguage() {
|
||||
return simpleLanguage;
|
||||
}
|
||||
|
||||
public void setSimpleLanguage(String simpleLanguage) {
|
||||
this.simpleLanguage = simpleLanguage;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public int getLegacy() {
|
||||
return legacy;
|
||||
}
|
||||
|
||||
public void setLegacy(int legacy) {
|
||||
this.legacy = legacy;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(this.state);
|
||||
dest.writeInt(this.legacy);
|
||||
dest.writeLong(this.time);
|
||||
dest.writeString(this.label);
|
||||
}
|
||||
|
||||
public long getSpeed() {
|
||||
return this.speed;
|
||||
}
|
||||
|
||||
public void setSpeed(long speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public long getRemaining() {
|
||||
return this.remaining;
|
||||
}
|
||||
|
||||
public void setRemaining(long remaining) {
|
||||
this.remaining = remaining;
|
||||
}
|
||||
|
||||
public int getFinished() {
|
||||
return this.finished;
|
||||
}
|
||||
|
||||
public void setFinished(int finished) {
|
||||
this.finished = finished;
|
||||
}
|
||||
|
||||
public int getDownloaded() {
|
||||
return this.downloaded;
|
||||
}
|
||||
|
||||
public void setDownloaded(int downloaded) {
|
||||
this.downloaded = downloaded;
|
||||
}
|
||||
|
||||
public int getTotal() {
|
||||
return this.total;
|
||||
}
|
||||
|
||||
public void setTotal(int total) {
|
||||
this.total = total;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "DOWNLOAD_LABELS")
|
||||
public class DownloadLabel {
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "_id")
|
||||
private Long id;
|
||||
@ColumnInfo(name = "LABEL")
|
||||
private String label;
|
||||
@ColumnInfo(name = "TIME")
|
||||
private long time;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DownloadLabelDao extends BasicDao<DownloadLabel> {
|
||||
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC")
|
||||
List<DownloadLabel> list();
|
||||
|
||||
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
|
||||
List<DownloadLabel> list(int offset, int limit);
|
||||
|
||||
@Update
|
||||
void update(List<DownloadLabel> downloadLabels);
|
||||
|
||||
@Update
|
||||
void update(DownloadLabel downloadLabel);
|
||||
|
||||
@Insert
|
||||
long insert(DownloadLabel downloadLabel);
|
||||
|
||||
@Delete
|
||||
void delete(DownloadLabel downloadLabel);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DownloadsDao extends BasicDao<DownloadInfo> {
|
||||
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC")
|
||||
List<DownloadInfo> list();
|
||||
|
||||
@Query("SELECT * FROM DOWNLOADS WHERE GID = :gid")
|
||||
DownloadInfo load(long gid);
|
||||
|
||||
@Update
|
||||
void update(DownloadInfo downloadInfo);
|
||||
|
||||
@Insert
|
||||
long insert(DownloadInfo downloadInfo);
|
||||
|
||||
@Delete
|
||||
void delete(DownloadInfo downloadInfo);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
@Database(entities = {BookmarkInfo.class, DownloadInfo.class, DownloadLabel.class, DownloadDirname.class, Filter.class, HistoryInfo.class, LocalFavoriteInfo.class, QuickSearch.class}, version = 4, exportSchema = false)
|
||||
public abstract class EhDatabase extends RoomDatabase {
|
||||
public abstract BookmarksDao bookmarksBao();
|
||||
|
||||
public abstract DownloadDirnameDao downloadDirnameDao();
|
||||
|
||||
public abstract DownloadLabelDao downloadLabelDao();
|
||||
|
||||
public abstract DownloadsDao downloadsDao();
|
||||
|
||||
public abstract FilterDao filterDao();
|
||||
|
||||
public abstract HistoryDao historyDao();
|
||||
|
||||
public abstract LocalFavoritesDao localFavoritesDao();
|
||||
|
||||
public abstract QuickSearchDao quickSearchDao();
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.hippo.util.HashCodeUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
|
||||
@Entity(tableName = "FILTER")
|
||||
public class Filter {
|
||||
@ColumnInfo(name = "MODE")
|
||||
public int mode;
|
||||
@ColumnInfo(name = "TEXT")
|
||||
public String text;
|
||||
@ColumnInfo(name = "ENABLE")
|
||||
public Boolean enable;
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "_id")
|
||||
private Long id;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public Boolean getEnable() {
|
||||
return enable;
|
||||
}
|
||||
|
||||
public void setEnable(Boolean enable) {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HashCodeUtils.hashCode(mode, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Filter)) {
|
||||
return false;
|
||||
}
|
||||
Filter filter = (Filter) o;
|
||||
return filter.mode == mode && ObjectUtils.equal(filter.text, text);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface FilterDao extends BasicDao<Filter> {
|
||||
@Query("SELECT * FROM FILTER")
|
||||
List<Filter> list();
|
||||
|
||||
@Update
|
||||
void update(Filter filter);
|
||||
|
||||
@Insert
|
||||
long insert(Filter filter);
|
||||
|
||||
@Delete
|
||||
void delete(Filter filter);
|
||||
|
||||
@Query("SELECT * FROM FILTER WHERE TEXT = :text AND MODE = :mode")
|
||||
Filter load(String text, int mode);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.paging.PagingSource;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface HistoryDao extends BasicDao<HistoryInfo> {
|
||||
@Query("SELECT * FROM HISTORY WHERE GID = :gid")
|
||||
HistoryInfo load(long gid);
|
||||
|
||||
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC")
|
||||
List<HistoryInfo> list();
|
||||
|
||||
@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);
|
||||
|
||||
@Insert
|
||||
long insert(HistoryInfo historyInfo);
|
||||
|
||||
@Delete
|
||||
void delete(HistoryInfo historyInfos);
|
||||
|
||||
@Delete
|
||||
void delete(List<HistoryInfo> historyInfos);
|
||||
|
||||
@Query("DELETE FROM HISTORY")
|
||||
void deleteAll();
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
|
||||
@Entity(tableName = "HISTORY")
|
||||
public class HistoryInfo extends GalleryInfo {
|
||||
public static final Creator<HistoryInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public HistoryInfo createFromParcel(Parcel source) {
|
||||
return new HistoryInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HistoryInfo[] newArray(int size) {
|
||||
return new HistoryInfo[size];
|
||||
}
|
||||
};
|
||||
@ColumnInfo(name = "MODE")
|
||||
public int mode;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
|
||||
protected HistoryInfo(Parcel in) {
|
||||
super(in);
|
||||
this.mode = in.readInt();
|
||||
this.time = in.readLong();
|
||||
}
|
||||
|
||||
public HistoryInfo(GalleryInfo galleryInfo) {
|
||||
this.gid = galleryInfo.gid;
|
||||
this.token = galleryInfo.token;
|
||||
this.title = galleryInfo.title;
|
||||
this.titleJpn = galleryInfo.titleJpn;
|
||||
this.thumb = galleryInfo.thumb;
|
||||
this.category = galleryInfo.category;
|
||||
this.posted = galleryInfo.posted;
|
||||
this.uploader = galleryInfo.uploader;
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
public HistoryInfo(int mode, long time) {
|
||||
this.mode = mode;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitleJpn() {
|
||||
return titleJpn;
|
||||
}
|
||||
|
||||
public void setTitleJpn(String titleJpn) {
|
||||
this.titleJpn = titleJpn;
|
||||
}
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getPosted() {
|
||||
return posted;
|
||||
}
|
||||
|
||||
public void setPosted(String posted) {
|
||||
this.posted = posted;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public float getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public String getSimpleLanguage() {
|
||||
return simpleLanguage;
|
||||
}
|
||||
|
||||
public void setSimpleLanguage(String simpleLanguage) {
|
||||
this.simpleLanguage = simpleLanguage;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(this.mode);
|
||||
dest.writeLong(this.time);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
|
||||
@Entity(tableName = "LOCAL_FAVORITES")
|
||||
public class LocalFavoriteInfo extends GalleryInfo {
|
||||
|
||||
public static final Creator<LocalFavoriteInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public LocalFavoriteInfo createFromParcel(Parcel source) {
|
||||
return new LocalFavoriteInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFavoriteInfo[] newArray(int size) {
|
||||
return new LocalFavoriteInfo[size];
|
||||
}
|
||||
};
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
|
||||
protected LocalFavoriteInfo(Parcel in) {
|
||||
super(in);
|
||||
this.time = in.readLong();
|
||||
}
|
||||
|
||||
public LocalFavoriteInfo(GalleryInfo galleryInfo) {
|
||||
this.gid = galleryInfo.gid;
|
||||
this.token = galleryInfo.token;
|
||||
this.title = galleryInfo.title;
|
||||
this.titleJpn = galleryInfo.titleJpn;
|
||||
this.thumb = galleryInfo.thumb;
|
||||
this.category = galleryInfo.category;
|
||||
this.posted = galleryInfo.posted;
|
||||
this.uploader = galleryInfo.uploader;
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
public LocalFavoriteInfo() {
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitleJpn() {
|
||||
return titleJpn;
|
||||
}
|
||||
|
||||
public void setTitleJpn(String titleJpn) {
|
||||
this.titleJpn = titleJpn;
|
||||
}
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getPosted() {
|
||||
return posted;
|
||||
}
|
||||
|
||||
public void setPosted(String posted) {
|
||||
this.posted = posted;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public float getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public String getSimpleLanguage() {
|
||||
return simpleLanguage;
|
||||
}
|
||||
|
||||
public void setSimpleLanguage(String simpleLanguage) {
|
||||
this.simpleLanguage = simpleLanguage;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(this.time);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface LocalFavoritesDao extends BasicDao<LocalFavoriteInfo> {
|
||||
@Query("SELECT * FROM LOCAL_FAVORITES ORDER BY TIME DESC")
|
||||
List<LocalFavoriteInfo> list();
|
||||
|
||||
@Query("SELECT * FROM LOCAL_FAVORITES WHERE TITLE LIKE :title ORDER BY TIME DESC")
|
||||
List<LocalFavoriteInfo> list(String title);
|
||||
|
||||
@Query("SELECT * FROM LOCAL_FAVORITES WHERE GID = :gid")
|
||||
LocalFavoriteInfo load(long gid);
|
||||
|
||||
@Insert
|
||||
long insert(LocalFavoriteInfo localFavoriteInfo);
|
||||
|
||||
@Delete
|
||||
void delete(LocalFavoriteInfo localFavoriteInfo);
|
||||
|
||||
@Query("DELETE FROM LOCAL_FAVORITES WHERE GID = :gid")
|
||||
void deleteByKey(long gid);
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "QUICK_SEARCH")
|
||||
public class QuickSearch {
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "_id")
|
||||
public Long id;
|
||||
@ColumnInfo(name = "NAME")
|
||||
public String name;
|
||||
@ColumnInfo(name = "MODE")
|
||||
public int mode;
|
||||
@ColumnInfo(name = "CATEGORY")
|
||||
public int category;
|
||||
@ColumnInfo(name = "KEYWORD")
|
||||
public String keyword;
|
||||
@ColumnInfo(name = "ADVANCE_SEARCH")
|
||||
public int advanceSearch;
|
||||
@ColumnInfo(name = "MIN_RATING")
|
||||
public int minRating;
|
||||
@ColumnInfo(name = "PAGE_FROM")
|
||||
public int pageFrom;
|
||||
@ColumnInfo(name = "PAGE_TO")
|
||||
public int pageTo;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public void setKeyword(String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
|
||||
public int getAdvanceSearch() {
|
||||
return advanceSearch;
|
||||
}
|
||||
|
||||
public void setAdvanceSearch(int advanceSearch) {
|
||||
this.advanceSearch = advanceSearch;
|
||||
}
|
||||
|
||||
public int getMinRating() {
|
||||
return minRating;
|
||||
}
|
||||
|
||||
public void setMinRating(int minRating) {
|
||||
this.minRating = minRating;
|
||||
}
|
||||
|
||||
public int getPageFrom() {
|
||||
return pageFrom;
|
||||
}
|
||||
|
||||
public void setPageFrom(int pageFrom) {
|
||||
this.pageFrom = pageFrom;
|
||||
}
|
||||
|
||||
public int getPageTo() {
|
||||
return pageTo;
|
||||
}
|
||||
|
||||
public void setPageTo(int pageTo) {
|
||||
this.pageTo = pageTo;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface QuickSearchDao extends BasicDao<QuickSearch> {
|
||||
@Query("SELECT * FROM QUICK_SEARCH ORDER BY TIME ASC")
|
||||
List<QuickSearch> list();
|
||||
|
||||
@Query("SELECT * FROM QUICK_SEARCH ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
|
||||
List<QuickSearch> list(int offset, int limit);
|
||||
|
||||
@Update
|
||||
void update(List<QuickSearch> downloadLabels);
|
||||
|
||||
@Update
|
||||
void update(QuickSearch quickSearch);
|
||||
|
||||
@Insert
|
||||
long insert(QuickSearch quickSearch);
|
||||
|
||||
@Delete
|
||||
void delete(QuickSearch quickSearch);
|
||||
}
|
||||
@@ -516,7 +516,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
|
||||
DownloadInfo info = mAllInfoMap.get(gid);
|
||||
if (info != null) {
|
||||
// Remove from DB
|
||||
EhDB.removeDownloadInfo(info);
|
||||
EhDB.removeDownloadInfo(info.gid);
|
||||
|
||||
// Remove all list and map
|
||||
mAllInfoList.remove(info);
|
||||
@@ -552,7 +552,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
|
||||
}
|
||||
|
||||
// Remove from DB
|
||||
EhDB.removeDownloadInfo(info);
|
||||
EhDB.removeDownloadInfo(info.gid);
|
||||
|
||||
// Remove from all info map
|
||||
mAllInfoList.remove(info);
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
/*
|
||||
* 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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,550 +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.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,19 +24,17 @@ 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.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -51,15 +49,11 @@ 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 LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
|
||||
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private final LinkedHashMap<Integer, InputStream> streams = new LinkedHashMap<>();
|
||||
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private Thread archiveThread;
|
||||
private Thread[] decodeThread = new Thread[] {
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask())
|
||||
};
|
||||
|
||||
private Thread decodeThread;
|
||||
private volatile int size = STATE_WAIT;
|
||||
private String error;
|
||||
|
||||
@@ -81,22 +75,25 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
int id = sIdGenerator.incrementAndGet();
|
||||
|
||||
archiveThread = new PriorityThread(
|
||||
new ArchiveHostTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
new ArchiveTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
archiveThread.start();
|
||||
for (Thread i : decodeThread) {
|
||||
i.start();
|
||||
}
|
||||
|
||||
decodeThread = new PriorityThread(
|
||||
new DecodeTask(), "DecodeTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
decodeThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
|
||||
if (archiveThread != null) {
|
||||
archiveThread.interrupt();
|
||||
archiveThread = null;
|
||||
}
|
||||
for (Thread i : decodeThread) {
|
||||
i.interrupt();
|
||||
if (decodeThread != null) {
|
||||
decodeThread.interrupt();
|
||||
decodeThread = null;
|
||||
}
|
||||
try {
|
||||
archiveAccessor.close();
|
||||
@@ -114,11 +111,11 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
protected void onRequest(int index) {
|
||||
boolean inDecodeTask;
|
||||
synchronized (streams) {
|
||||
inDecodeTask = streams.containsKey(index);
|
||||
inDecodeTask = streams.containsKey(index) || index == decodingIndex.get();
|
||||
}
|
||||
|
||||
synchronized (requests) {
|
||||
boolean inArchiveTask = requests.contains(index);
|
||||
boolean inArchiveTask = requests.contains(index) || index == extractingIndex.get();
|
||||
if (!inArchiveTask && !inDecodeTask) {
|
||||
requests.add(index);
|
||||
requests.notify();
|
||||
@@ -147,53 +144,31 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
@NonNull
|
||||
@Override
|
||||
public String getImageFilename(int index) {
|
||||
return FileUtils.getNameFromFilename(getImageFilenameWithExtension(index));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getImageFilenameWithExtension(int index) {
|
||||
return FileUtils.sanitizeFilename(archiveAccessor.getFilename(index));
|
||||
// TODO
|
||||
return Integer.toString(index);
|
||||
}
|
||||
|
||||
@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;
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
@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;
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
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())
|
||||
};
|
||||
@NonNull
|
||||
@Override
|
||||
public String getImageFilenameWithExtension(int index) {
|
||||
// TODO
|
||||
return Integer.toString(index);
|
||||
}
|
||||
|
||||
private class ArchiveTask implements Runnable {
|
||||
private void waitPasswd() throws InterruptedException {
|
||||
showPasswd.sendEmptyMessage(0);
|
||||
pv.p();
|
||||
@@ -251,26 +226,7 @@ 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) {
|
||||
@@ -284,26 +240,28 @@ 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;
|
||||
}
|
||||
|
||||
Pipe pipe = new Pipe();
|
||||
|
||||
synchronized (streams) {
|
||||
if (streams.get(index) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
long addr = archiveAccessor.extractToAddr(index);
|
||||
|
||||
synchronized (streams) {
|
||||
streams.put(index, addr);
|
||||
streams.put(index, pipe.getInputStream());
|
||||
streams.notify();
|
||||
}
|
||||
|
||||
archiveAccessor.extracttoOutputStream(index, pipe.getOutputFd());
|
||||
pipe.closeOutputFd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,29 +271,35 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
public void run() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int index;
|
||||
Long addr;
|
||||
InputStream stream;
|
||||
synchronized (streams) {
|
||||
if (streams.isEmpty()) {
|
||||
try {
|
||||
streams.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Interrupted
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, Long> entry = iterator.next();
|
||||
Iterator<Map.Entry<Integer, InputStream>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, InputStream> entry = iterator.next();
|
||||
iterator.remove();
|
||||
index = entry.getKey();
|
||||
addr = entry.getValue();
|
||||
stream = entry.getValue();
|
||||
decodingIndex.lazySet(index);
|
||||
}
|
||||
|
||||
Image image = Image.decodeAddr(addr, true);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
try {
|
||||
Image image = Image.decode(stream, true);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
}
|
||||
} finally {
|
||||
decodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -259,7 +258,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = files[index].openInputStream();
|
||||
Image image = Image.decode(((FileInputStream) is).getFD(), true);
|
||||
Image image = Image.decode(is, true);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
class Pipe {
|
||||
private ParcelFileDescriptor[] fd;
|
||||
{
|
||||
try {
|
||||
fd = ParcelFileDescriptor.createPipe();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private final InputStream inputStream = new FileInputStream(fd[0].getFileDescriptor());
|
||||
|
||||
InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
int getOutputFd() {
|
||||
return fd[1].getFd();
|
||||
}
|
||||
|
||||
void closeOutputFd() {
|
||||
try {
|
||||
fd[1].close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.preference
|
||||
|
||||
import android.content.*
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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,12 +18,14 @@ package com.hippo.ehviewer.preference
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.hippo.Native
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.preference.MessagePreference
|
||||
|
||||
class VersionPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Preference(context, attrs) {
|
||||
) : MessagePreference(context, attrs) {
|
||||
init {
|
||||
setTitle(R.string.settings_about_version)
|
||||
@Suppress("DEPRECATION") val version: String = try {
|
||||
@@ -35,4 +37,32 @@ 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("libjpeg-turbo", Native.getlibjpeg_turboVersion())
|
||||
.addLib(Native.getnettleVersion())
|
||||
builder.setMessage(info.getMessage())
|
||||
}
|
||||
|
||||
inner class NativeLibInfo {
|
||||
private var message: String = ""
|
||||
fun addLib(libname:String, libversion:String): NativeLibInfo {
|
||||
message = "$message$libname $libversion\n"
|
||||
return this
|
||||
}
|
||||
|
||||
fun getMessage(): String {
|
||||
return message;
|
||||
}
|
||||
|
||||
fun addLib(msg: String): NativeLibInfo {
|
||||
message = "$message$msg\n"
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,6 @@ import com.hippo.yorozuya.thread.PriorityThread;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -1097,6 +1096,33 @@ public final class SpiderQueen implements Runnable {
|
||||
void onGetImageFailure(int index, String error);
|
||||
}
|
||||
|
||||
private static class AutoCloseInputStream extends InputStream {
|
||||
|
||||
private final InputStreamPipe mPipe;
|
||||
private final InputStream mIs;
|
||||
|
||||
public AutoCloseInputStream(InputStreamPipe pipe, InputStream is) {
|
||||
mPipe = pipe;
|
||||
mIs = is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return mIs.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
||||
return mIs.read(buffer, byteOffset, byteCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mPipe.close();
|
||||
mPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
private class SpiderWorker implements Runnable {
|
||||
|
||||
private final long mGid;
|
||||
@@ -1667,7 +1693,7 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
pipe.obtain();
|
||||
try {
|
||||
is = pipe.open();
|
||||
is = new AutoCloseInputStream(pipe, pipe.open());
|
||||
} catch (IOException e) {
|
||||
// Can't open pipe
|
||||
error = GetText.getString(R.string.error_reading_failed);
|
||||
@@ -1677,17 +1703,7 @@ public final class SpiderQueen implements Runnable {
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
image = Image.decode(((FileInputStream) is).getFD(), true);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
image = Image.decode(is, true);
|
||||
if (image == null) {
|
||||
error = GetText.getString(R.string.error_decoding_failed);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.hippo.ehviewer.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
@@ -43,7 +42,6 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class CommonOperations {
|
||||
|
||||
@@ -116,14 +114,8 @@ 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();
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.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() {
|
||||
super.onResume();
|
||||
if (Settings.getEnabledSecurity()) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.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 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)
|
||||
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 (locked_delay_time < Settings.getSecurityDelay() * 60) {
|
||||
EhApplication.locked = false
|
||||
} else if (Settings.getSecurity() && isAuthenticationSupported() && 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;
|
||||
@@ -226,9 +226,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
private int mSize;
|
||||
private int mCurrentIndex;
|
||||
private int mSavingPage = -1;
|
||||
private EditTextDialogBuilder builder;
|
||||
private boolean dialogShown = false;
|
||||
private AlertDialog dialog;
|
||||
|
||||
private void buildProvider() {
|
||||
if (mGalleryProvider != null) {
|
||||
@@ -303,16 +300,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
break;
|
||||
}
|
||||
super.attachBaseContext(newBase);
|
||||
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> {
|
||||
if (result && mSavingPage != -1) {
|
||||
saveImage(mSavingPage);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSavingPage = -1;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"WrongConstant"})
|
||||
@@ -410,7 +398,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
if (scroll < 0.0f) {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
|
||||
} 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);
|
||||
@@ -494,7 +482,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
FrameLayout mainLayout = (FrameLayout) ViewUtils.$$(this, R.id.main);
|
||||
mainLayout.addView(new GalleryGuideView(this));
|
||||
}
|
||||
}
|
||||
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> {
|
||||
if (result && mSavingPage != -1) {
|
||||
saveImage(mSavingPage);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSavingPage = -1;
|
||||
});
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
@@ -858,8 +855,7 @@ 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);
|
||||
if (mGalleryInfo != null)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, EhUrl.getGalleryDetailUrl(mGalleryInfo.gid, mGalleryInfo.token));
|
||||
intent.putExtra(Intent.EXTRA_TEXT, EhUrl.getGalleryDetailUrl(mGalleryInfo.gid, mGalleryInfo.token));
|
||||
intent.setDataAndType(uri, mimeType);
|
||||
|
||||
try {
|
||||
@@ -991,7 +987,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
|
||||
private void showPageDialog(final int page) {
|
||||
Resources resources = GalleryActivity.this.getResources();
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(GalleryActivity.this);
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
|
||||
builder.setTitle(resources.getString(R.string.page_menu_title, page + 1));
|
||||
|
||||
final CharSequence[] items;
|
||||
@@ -1055,66 +1051,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
}
|
||||
|
||||
private void showPasswdDialog() {
|
||||
if (!dialogShown) {
|
||||
dialogShown = true;
|
||||
dialog.show();
|
||||
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
GalleryActivity.this.onProvidePasswd();
|
||||
});
|
||||
}
|
||||
dialog.setOnCancelListener(v -> finish());
|
||||
}
|
||||
}
|
||||
|
||||
private void onProvidePasswd() {
|
||||
String passwd = builder.getText();
|
||||
if (passwd.isEmpty())
|
||||
builder.setError(getString(R.string.passwd_cannot_be_empty));
|
||||
else {
|
||||
ArchiveGalleryProvider.passwd = passwd;
|
||||
ArchiveGalleryProvider.pv.v();
|
||||
}
|
||||
}
|
||||
|
||||
private void onPasswdWrong() {
|
||||
builder.setError(getString(R.string.passwd_wrong));
|
||||
}
|
||||
|
||||
private void onPasswdCorrect() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
private static class ShowPasswdDialogHandler extends Handler {
|
||||
|
||||
//弱引用持有HandlerActivity , GC 回收时会被回收掉
|
||||
private final WeakReference<GalleryActivity> weakReference;
|
||||
|
||||
public ShowPasswdDialogHandler(GalleryActivity activity) {
|
||||
this.weakReference = new WeakReference(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
GalleryActivity activity = weakReference.get();
|
||||
super.handleMessage(msg);
|
||||
if (null != activity) {
|
||||
switch (msg.what) {
|
||||
case 0:
|
||||
activity.showPasswdDialog();
|
||||
break;
|
||||
case 1:
|
||||
activity.onPasswdWrong();
|
||||
break;
|
||||
case 2:
|
||||
activity.onPasswdCorrect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class GalleryMenuHelper implements DialogInterface.OnClickListener {
|
||||
|
||||
private final View mView;
|
||||
@@ -1287,7 +1223,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
|
||||
private void onTapMenuArea() {
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(GalleryActivity.this);
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
|
||||
GalleryMenuHelper helper = new GalleryMenuHelper(builder.getContext());
|
||||
builder.setTitle(R.string.gallery_menu_title)
|
||||
.setView(helper.getView())
|
||||
@@ -1375,7 +1311,69 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
}
|
||||
|
||||
private EditTextDialogBuilder builder;
|
||||
|
||||
private boolean dialogShown = false;
|
||||
|
||||
private AlertDialog dialog;
|
||||
|
||||
private void showPasswdDialog() {
|
||||
if (!dialogShown) {
|
||||
dialogShown = true;
|
||||
dialog.show();
|
||||
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
GalleryActivity.this.onProvidePasswd();
|
||||
});
|
||||
}
|
||||
dialog.setOnCancelListener(v -> finish());
|
||||
}
|
||||
}
|
||||
|
||||
private void onProvidePasswd() {
|
||||
String passwd = builder.getText();
|
||||
if (passwd.isEmpty())
|
||||
builder.setError(getString(R.string.passwd_cannot_be_empty));
|
||||
else {
|
||||
ArchiveGalleryProvider.passwd = passwd;
|
||||
ArchiveGalleryProvider.pv.v();
|
||||
}
|
||||
}
|
||||
|
||||
private void onPasswdWrong() {
|
||||
builder.setError(getString(R.string.passwd_wrong));
|
||||
}
|
||||
|
||||
private void onPasswdCorrect() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
private static class ShowPasswdDialogHandler extends Handler {
|
||||
|
||||
//弱引用持有HandlerActivity , GC 回收时会被回收掉
|
||||
private final WeakReference<GalleryActivity> weakReference;
|
||||
|
||||
public ShowPasswdDialogHandler(GalleryActivity activity) {
|
||||
this.weakReference = new WeakReference(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
GalleryActivity activity = weakReference.get();
|
||||
super.handleMessage(msg);
|
||||
if (null != activity) {
|
||||
switch (msg.what) {
|
||||
case 0:
|
||||
activity.showPasswdDialog();
|
||||
break;
|
||||
case 1:
|
||||
activity.onPasswdWrong();
|
||||
break;
|
||||
case 2:
|
||||
activity.onPasswdCorrect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,11 +42,12 @@ 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.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhTagDatabase;
|
||||
@@ -66,9 +67,11 @@ import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryPreviewsScene;
|
||||
import com.hippo.ehviewer.ui.scene.HistoryScene;
|
||||
import com.hippo.ehviewer.ui.scene.ProgressScene;
|
||||
import com.hippo.ehviewer.ui.scene.SecurityScene;
|
||||
import com.hippo.ehviewer.ui.scene.SelectSiteScene;
|
||||
import com.hippo.ehviewer.ui.scene.SignInScene;
|
||||
import com.hippo.ehviewer.ui.scene.SolidScene;
|
||||
import com.hippo.ehviewer.ui.scene.WarningScene;
|
||||
import com.hippo.ehviewer.ui.scene.WebViewSignInScene;
|
||||
import com.hippo.ehviewer.widget.EhStageLayout;
|
||||
import com.hippo.io.UniFileInputStreamPipe;
|
||||
@@ -77,7 +80,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.ClipboardUtilKt;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.widget.DrawerView;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
@@ -95,6 +98,8 @@ public final class MainActivity extends StageActivity
|
||||
private static final String KEY_NAV_CHECKED_ITEM = "nav_checked_item";
|
||||
|
||||
static {
|
||||
registerLaunchMode(SecurityScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(WarningScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(SignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(WebViewSignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(CookieSignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
@@ -138,7 +143,11 @@ public final class MainActivity extends StageActivity
|
||||
@NonNull
|
||||
@Override
|
||||
protected Announcer getLaunchAnnouncer() {
|
||||
if (EhUtils.needSignedIn(this)) {
|
||||
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this)) {
|
||||
return new Announcer(SecurityScene.class);
|
||||
} else if (Settings.getShowWarning()) {
|
||||
return new Announcer(WarningScene.class);
|
||||
} else if (EhUtils.needSignedIn(this)) {
|
||||
return new Announcer(SignInScene.class);
|
||||
} else if (Settings.getSelectSite()) {
|
||||
return new Announcer(SelectSiteScene.class);
|
||||
@@ -152,7 +161,17 @@ public final class MainActivity extends StageActivity
|
||||
// Sometimes scene can't show directly
|
||||
private Announcer processAnnouncer(Announcer announcer) {
|
||||
if (0 == getSceneCount()) {
|
||||
if (EhUtils.needSignedIn(this)) {
|
||||
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this)) {
|
||||
Bundle newArgs = new Bundle();
|
||||
newArgs.putString(SecurityScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
||||
newArgs.putBundle(SecurityScene.KEY_TARGET_ARGS, announcer.getArgs());
|
||||
return new Announcer(SecurityScene.class).setArgs(newArgs);
|
||||
} else if (Settings.getShowWarning()) {
|
||||
Bundle newArgs = new Bundle();
|
||||
newArgs.putString(WarningScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
||||
newArgs.putBundle(WarningScene.KEY_TARGET_ARGS, announcer.getArgs());
|
||||
return new Announcer(WarningScene.class).setArgs(newArgs);
|
||||
} else if (EhUtils.needSignedIn(this)) {
|
||||
Bundle newArgs = new Bundle();
|
||||
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
||||
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.getArgs());
|
||||
@@ -259,7 +278,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) -> ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(this), url, false, () -> null))
|
||||
.setPositiveButton(android.R.string.copy, (dialog, which) -> ClipboardUtil.addTextToClipboard(url))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -337,7 +356,7 @@ public final class MainActivity extends StageActivity
|
||||
break;
|
||||
}
|
||||
if (hasUnverified) {
|
||||
new BaseDialogBuilder(this)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.app_link_not_verified_title)
|
||||
.setMessage(R.string.app_link_not_verified_message)
|
||||
.setPositiveButton(R.string.open_settings, (dialogInterface, i) -> {
|
||||
@@ -363,7 +382,7 @@ public final class MainActivity extends StageActivity
|
||||
if (null == uniFile || uniFile.ensureDir()) {
|
||||
return;
|
||||
}
|
||||
new BaseDialogBuilder(this)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.waring)
|
||||
.setMessage(R.string.invalid_download_location)
|
||||
.setPositiveButton(R.string.get_it, null)
|
||||
@@ -407,6 +426,9 @@ public final class MainActivity extends StageActivity
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this) && EhApplication.locked) {
|
||||
startScene(new Announcer(SecurityScene.class), true);
|
||||
}
|
||||
super.onResume();
|
||||
setNavCheckedItem(mNavCheckedItem);
|
||||
checkClipboardUrl();
|
||||
@@ -456,7 +478,7 @@ public final class MainActivity extends StageActivity
|
||||
}
|
||||
|
||||
private void checkClipboardUrlInternal() {
|
||||
String text = ClipboardUtilKt.getUrlFromClipboard(ClipboardUtilKt.getClipboardManager(this), this);
|
||||
String text = ClipboardUtil.getUrlFromClipboard();
|
||||
int hashCode = text != null ? text.hashCode() : 0;
|
||||
|
||||
if (text != null && hashCode != 0 && Settings.getClipboardTextHashCode() != hashCode) {
|
||||
|
||||
@@ -33,8 +33,7 @@ class SettingsActivity : EhActivity() {
|
||||
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
|
||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
@@ -57,7 +56,7 @@ class SettingsActivity : EhActivity() {
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
|
||||
@@ -33,7 +33,7 @@ import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.BuildConfig;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
@@ -66,6 +66,7 @@ import java.util.zip.ZipOutputStream;
|
||||
public class AdvancedFragment extends BasePreferenceFragment {
|
||||
|
||||
private static final String KEY_DUMP_LOGCAT = "dump_logcat";
|
||||
private static final String KEY_CLEAR_MEMORY_CACHE = "clear_memory_cache";
|
||||
private static final String KEY_APP_LANGUAGE = "app_language";
|
||||
private static final String KEY_IMPORT_DATA = "import_data";
|
||||
private static final String KEY_EXPORT_DATA = "export_data";
|
||||
@@ -84,7 +85,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
AlertDialog alertDialog = new BaseDialogBuilder(requireActivity())
|
||||
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.preference_dialog_task)
|
||||
.show();
|
||||
@@ -201,7 +202,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
AlertDialog alertDialog = new BaseDialogBuilder(requireActivity())
|
||||
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.preference_dialog_task)
|
||||
.show();
|
||||
@@ -233,9 +234,8 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
addPreferencesFromResource(R.xml.advanced_settings);
|
||||
|
||||
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);
|
||||
@@ -248,6 +248,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
}
|
||||
|
||||
dumpLogcat.setOnPreferenceClickListener(this);
|
||||
clearMemoryCache.setOnPreferenceClickListener(this);
|
||||
importData.setOnPreferenceClickListener(this);
|
||||
exportData.setOnPreferenceClickListener(this);
|
||||
backupFavorite.setOnPreferenceClickListener(this);
|
||||
@@ -266,6 +267,10 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
return true;
|
||||
} else if (KEY_CLEAR_MEMORY_CACHE.equals(key)) {
|
||||
((EhApplication) requireContext().getApplicationContext()).clearMemoryCache();
|
||||
Runtime.getRuntime().gc();
|
||||
showTip(R.string.settings_advanced_clear_memory_cache_done, BaseScene.LENGTH_SHORT);
|
||||
} else if (KEY_IMPORT_DATA.equals(key)) {
|
||||
try {
|
||||
importDataLauncher.launch(new String[]{"*/*"});
|
||||
@@ -352,7 +357,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
String key = preference.getKey();
|
||||
if (KEY_APP_LANGUAGE.equals(key) && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
if (KEY_APP_LANGUAGE.equals(key)) {
|
||||
if ("system".equals(newValue)) {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.wrap(Resources.getSystem().getConfiguration().getLocales()));
|
||||
} else {
|
||||
|
||||
@@ -70,8 +70,7 @@ 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.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
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 BaseDialogBuilder(requireContext())
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.settings_download_download_location)
|
||||
.setMessage(file.getUri().toString())
|
||||
.setPositiveButton(R.string.pick_new_download_location, (dialogInterface, i) -> openDirPickerL())
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 listThumbSize = findPreference(Settings.KEY_LIST_THUMB_SIZE);
|
||||
Preference detailSize = findPreference(Settings.KEY_DETAIL_SIZE);
|
||||
Preference thumbSize = findPreference(Settings.KEY_THUMB_SIZE);
|
||||
Preference showTagTranslations = findPreference(Settings.KEY_SHOW_TAG_TRANSLATIONS);
|
||||
Preference tagTranslationsSource = findPreference("tag_translations_source");
|
||||
|
||||
theme.setOnPreferenceChangeListener(this);
|
||||
gallerySite.setOnPreferenceChangeListener(this);
|
||||
listMode.setOnPreferenceChangeListener(this);
|
||||
listThumbSize.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_LIST_THUMB_SIZE.equals(key)) {
|
||||
Settings.LIST_THUMB_SIZE_INITED = false;
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
return true;
|
||||
} else if (Settings.KEY_DETAIL_SIZE.equals(key)) {
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
} else if (Settings.KEY_THUMB_SIZE.equals(key)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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.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() {
|
||||
BaseDialogBuilder(requireActivity())
|
||||
MaterialAlertDialogBuilder(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 = BaseDialogBuilder(requireActivity())
|
||||
val dialog = MaterialAlertDialogBuilder(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)
|
||||
BaseDialogBuilder(requireActivity())
|
||||
MaterialAlertDialogBuilder(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,8 +149,7 @@ public class HostsFragment extends BaseFragment
|
||||
ip.setText(arguments.getString(KEY_IP));
|
||||
}
|
||||
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(requireContext());
|
||||
builder.setView(view);
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()).setView(view);
|
||||
onCreateDialogBuilder(builder);
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
|
||||
|
||||
@@ -19,55 +19,29 @@
|
||||
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
|
||||
import com.hippo.ehviewer.ui.isAuthenticationSupported
|
||||
import com.hippo.ehviewer.ui.scene.SecurityScene.Companion.isAuthenticationSupported
|
||||
|
||||
/**
|
||||
* 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 (!requireContext().isAuthenticationSupported()) {
|
||||
if (!isAuthenticationSupported(requireContext())) {
|
||||
Settings.putSecurity(false)
|
||||
requireUnlock.isEnabled = false
|
||||
requireUnlock.isChecked = false
|
||||
unlockDelay.isEnabled = false
|
||||
} else {
|
||||
unlockDelay.isEnabled = requireUnlock.isChecked == true
|
||||
setUnlockDelaySummary(unlockDelay.value)
|
||||
findPreference<SwitchPreferenceCompat>("require_unlock")?.isEnabled = false
|
||||
findPreference<SwitchPreferenceCompat>("require_unlock")?.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
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,8 +42,6 @@ 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;
|
||||
@@ -108,12 +106,11 @@ public abstract class BaseScene extends SceneFragment {
|
||||
}
|
||||
}
|
||||
|
||||
public Unit showTip(@StringRes int id, int length) {
|
||||
public void showTip(@StringRes int id, int length) {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity instanceof MainActivity) {
|
||||
((MainActivity) activity).showTip(id, length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean needShowLeftDrawer() {
|
||||
|
||||
@@ -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.ClipboardUtilKt;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
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 BaseDialogBuilder(context).setTitle(R.string.waring)
|
||||
new MaterialAlertDialogBuilder(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 = ClipboardUtilKt.getTextFromClipboard(ClipboardUtilKt.getClipboardManager(getContext()), getContext());
|
||||
String text = ClipboardUtil.getTextFromClipboard();
|
||||
if (text == null) {
|
||||
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
|
||||
return;
|
||||
|
||||
@@ -51,8 +51,8 @@ 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.easyrecyclerview.EasyRecyclerView;
|
||||
@@ -72,7 +72,9 @@ 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;
|
||||
@@ -81,10 +83,14 @@ 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;
|
||||
@@ -417,7 +423,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_filter) {
|
||||
new BaseDialogBuilder(requireActivity())
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setSingleChoiceItems(R.array.download_state, mType + 1, (dialog, which) -> {
|
||||
mType = which - 1;
|
||||
updateForLabel();
|
||||
@@ -440,7 +446,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
openDrawer(Gravity.RIGHT);
|
||||
return true;
|
||||
} else if (id == R.id.action_reset_reading_progress) {
|
||||
new BaseDialogBuilder(getContext())
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
.setMessage(R.string.reset_reading_progress_message)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
@@ -513,7 +519,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
items[i + 2] = list.get(i).getLabel();
|
||||
}
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.default_download_label)
|
||||
.setItems(items, (dialog1, which) -> {
|
||||
if (which == 0) {
|
||||
@@ -700,7 +706,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
MoveDialogHelper helper = new MoveDialogHelper(labels, downloadInfoList);
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show();
|
||||
|
||||
@@ -21,11 +21,14 @@ 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.os.Parcelable;
|
||||
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;
|
||||
@@ -43,9 +46,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;
|
||||
@@ -75,6 +78,7 @@ 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;
|
||||
@@ -399,7 +403,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 BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.default_favorites_collection)
|
||||
.setItems(items, (dialog, which) -> Settings.putDefaultFavSlot(which - 2)).show();
|
||||
return true;
|
||||
@@ -728,7 +732,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
case 4: { // Delete
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper();
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_favorites_dialog_title)
|
||||
.setMessage(getString(R.string.delete_favorites_dialog_message, mModifyGiList.size()))
|
||||
.setPositiveButton(android.R.string.ok, helper)
|
||||
@@ -742,7 +746,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 BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(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.ClipboardUtilKt;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.util.TextUrl;
|
||||
@@ -290,7 +290,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
}
|
||||
}
|
||||
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(context);
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||
context = builder.getContext();
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
EasyRecyclerView rv = (EasyRecyclerView) inflater.inflate(R.layout.dialog_recycler_view, null);
|
||||
@@ -347,14 +347,15 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
menuId.add(R.id.check_vote_status);
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(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) {
|
||||
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), comment.comment, false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
|
||||
ClipboardUtil.addTextToClipboard(comment.comment);
|
||||
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) {
|
||||
|
||||
@@ -53,6 +53,7 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -61,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;
|
||||
@@ -102,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.ClipboardUtilKt;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.view.ViewTransition;
|
||||
@@ -202,6 +203,8 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
@Nullable
|
||||
private RatingBar mRating;
|
||||
@Nullable
|
||||
private View mHeartGroup;
|
||||
@Nullable
|
||||
private TextView mHeart;
|
||||
@Nullable
|
||||
private TextView mHeartOutline;
|
||||
@@ -257,7 +260,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
result -> {
|
||||
if (result && mGalleryDetail != null) {
|
||||
TorrentListDialogHelper helper = new TorrentListDialogHelper();
|
||||
Dialog dialog = new BaseDialogBuilder(requireActivity())
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.torrents)
|
||||
.setView(R.layout.dialog_torrent_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -601,8 +604,9 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mNewerVersion = (TextView) ViewUtils.$$(mActions, R.id.newerVersion);
|
||||
mRatingText = (TextView) ViewUtils.$$(mActions, R.id.rating_text);
|
||||
mRating = (RatingBar) ViewUtils.$$(mActions, R.id.rating);
|
||||
mHeart = (TextView) ViewUtils.$$(mActions, R.id.heart);
|
||||
mHeartOutline = (TextView) ViewUtils.$$(mActions, R.id.heart_outline);
|
||||
mHeartGroup = ViewUtils.$$(mActions, R.id.heart_group);
|
||||
mHeart = (TextView) ViewUtils.$$(mHeartGroup, R.id.heart);
|
||||
mHeartOutline = (TextView) ViewUtils.$$(mHeartGroup, R.id.heart_outline);
|
||||
mTorrent = (TextView) ViewUtils.$$(mActions, R.id.torrent);
|
||||
mArchive = (TextView) ViewUtils.$$(mActions, R.id.archive);
|
||||
mShare = (TextView) ViewUtils.$$(mActions, R.id.share);
|
||||
@@ -610,16 +614,15 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mSimilar = (TextView) ViewUtils.$$(mActions, R.id.similar);
|
||||
mSearchCover = (TextView) ViewUtils.$$(mActions, R.id.search_cover);
|
||||
mNewerVersion.setOnClickListener(this);
|
||||
mHeart.setOnClickListener(this);
|
||||
mHeart.setOnLongClickListener(this);
|
||||
mHeartOutline.setOnClickListener(this);
|
||||
mHeartOutline.setOnLongClickListener(this);
|
||||
mHeartGroup.setOnClickListener(this);
|
||||
mHeartGroup.setOnLongClickListener(this);
|
||||
mTorrent.setOnClickListener(this);
|
||||
mArchive.setOnClickListener(this);
|
||||
mShare.setOnClickListener(this);
|
||||
mRate.setOnClickListener(this);
|
||||
mSimilar.setOnClickListener(this);
|
||||
mSearchCover.setOnClickListener(this);
|
||||
ensureActionDrawable();
|
||||
|
||||
mTags = (LinearLayout) ViewUtils.$$(belowHeader, R.id.tags);
|
||||
mNoTags = (TextView) ViewUtils.$$(mTags, R.id.no_tags);
|
||||
@@ -692,6 +695,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mNewerVersion = null;
|
||||
mRatingText = null;
|
||||
mRating = null;
|
||||
mHeartGroup = null;
|
||||
mHeart = null;
|
||||
mHeartOutline = null;
|
||||
mTorrent = null;
|
||||
@@ -765,6 +769,25 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setActionDrawable(@Nullable TextView text, @DrawableRes int resId) {
|
||||
if (text == null) return;
|
||||
Context context = text.getContext();
|
||||
Drawable drawable = ContextCompat.getDrawable(context, resId);
|
||||
if (drawable == null) return;
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
text.setCompoundDrawables(null, drawable, null, null);
|
||||
}
|
||||
|
||||
private void ensureActionDrawable() {
|
||||
setActionDrawable(mHeart, R.drawable.v_heart_primary_x48);
|
||||
setActionDrawable(mHeartOutline, R.drawable.v_heart_outline_primary_x48);
|
||||
setActionDrawable(mTorrent, R.drawable.v_utorrent_primary_x48);
|
||||
setActionDrawable(mArchive, R.drawable.v_archive_primary_x48);
|
||||
setActionDrawable(mShare, R.drawable.v_share_primary_x48);
|
||||
setActionDrawable(mSimilar, R.drawable.v_similar_primary_x48);
|
||||
setActionDrawable(mSearchCover, R.drawable.v_file_find_primary_x48);
|
||||
}
|
||||
|
||||
private boolean createCircularReveal() {
|
||||
if (mColorBg == null) {
|
||||
return false;
|
||||
@@ -1044,7 +1067,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
if (gd.previewPages <= 0 || previewSet == null || previewSet.size() == 0) {
|
||||
mPreviewText.setText(R.string.no_previews);
|
||||
return;
|
||||
} else if (gd.previewPages == 1) {
|
||||
} else if (gd.previewPages == 1 && previewSet.size() <= 60) {
|
||||
mPreviewText.setText(R.string.no_more_previews);
|
||||
} else {
|
||||
mPreviewText.setText(R.string.more_previews);
|
||||
@@ -1053,7 +1076,8 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
int columnWidth = resources.getDimensionPixelOffset(Settings.getThumbSizeResId());
|
||||
mGridLayout.setColumnSize(columnWidth);
|
||||
mGridLayout.setStrategy(SimpleGridAutoSpanLayout.STRATEGY_SUITABLE_SIZE);
|
||||
for (int i = 0, size = previewSet.size(); i < size; i++) {
|
||||
int size = Math.min(60, previewSet.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
View view = inflater.inflate(R.layout.item_gallery_preview, mGridLayout, false);
|
||||
mGridLayout.addView(view);
|
||||
|
||||
@@ -1179,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 BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(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))
|
||||
@@ -1205,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 BaseDialogBuilder(requireContext())
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setItems(titles.toArray(new CharSequence[0]), (dialog, which) -> {
|
||||
GalleryInfo newerVersion = mGalleryDetail.newerVersions.get(which);
|
||||
Bundle args = new Bundle();
|
||||
@@ -1220,7 +1244,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(GalleryInfoScene.KEY_GALLERY_DETAIL, mGalleryDetail);
|
||||
startScene(new Announcer(GalleryInfoScene.class).setArgs(args));
|
||||
} else if (mHeart == v || mHeartOutline == v) {
|
||||
} else if (mHeartGroup == v) {
|
||||
if (mGalleryDetail != null && !mModifingFavorites) {
|
||||
boolean remove = false;
|
||||
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
|
||||
@@ -1251,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 BaseDialogBuilder(context)
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.torrents)
|
||||
.setView(R.layout.dialog_torrent_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -1268,7 +1292,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
ArchiveListDialogHelper helper = new ArchiveListDialogHelper();
|
||||
Dialog dialog = new BaseDialogBuilder(context)
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.dialog_archive_title)
|
||||
.setView(R.layout.dialog_archive_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -1283,7 +1307,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
RateDialogHelper helper = new RateDialogHelper();
|
||||
Dialog dialog = new BaseDialogBuilder(context)
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.rate)
|
||||
.setView(R.layout.dialog_rate)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
@@ -1345,7 +1369,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setMessage(getString(R.string.filter_the_uploader, uploader))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
Filter filter = new Filter();
|
||||
@@ -1365,7 +1389,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setMessage(getString(R.string.filter_the_tag, tag))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
Filter filter = new Filter();
|
||||
@@ -1414,7 +1438,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
menuId.add(R.id.vote_down);
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(tag)
|
||||
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
|
||||
if (which < 0 || which >= menuId.size()) {
|
||||
@@ -1430,9 +1454,11 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
} else if (id == R.id.add_filter) {
|
||||
showFilterTagDialog(tag);
|
||||
} else if (id == R.id.copy) {
|
||||
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), tag, false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
|
||||
ClipboardUtil.addTextToClipboard(tag);
|
||||
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
|
||||
} else if (id == R.id.copy_trans) {
|
||||
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), tv.getText().toString(), false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
|
||||
ClipboardUtil.addTextToClipboard(tv.getText().toString());
|
||||
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
@@ -1467,7 +1493,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
CommonOperations.startDownload(activity, galleryInfo, true);
|
||||
}
|
||||
return true;
|
||||
} else if (mHeart == v || mHeartOutline == v) {
|
||||
} else if (mHeartGroup == v) {
|
||||
if (mGalleryDetail != null && !mModifingFavorites) {
|
||||
boolean remove = false;
|
||||
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
|
||||
|
||||
@@ -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.ClipboardUtilKt;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
@@ -188,11 +188,14 @@ public final class GalleryInfoScene extends ToolbarScene {
|
||||
if (position == INDEX_PARENT) {
|
||||
UrlOpener.openUrl(context, mValues.get(position), true);
|
||||
} else {
|
||||
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), mValues.get(position), false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
|
||||
ClipboardUtil.addTextToClipboard(mValues.get(position));
|
||||
|
||||
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,7 +40,6 @@ 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;
|
||||
@@ -55,8 +54,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;
|
||||
@@ -91,7 +90,6 @@ import com.hippo.ehviewer.widget.SearchBar;
|
||||
import com.hippo.ehviewer.widget.SearchLayout;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.view.BringOutTransition;
|
||||
import com.hippo.view.ViewTransition;
|
||||
@@ -151,8 +149,8 @@ public final class GalleryListScene extends BaseScene
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
private SearchLayout mSearchLayout;
|
||||
ActivityResultLauncher<PickVisualMediaRequest> selectImageLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.PickVisualMedia(),
|
||||
ActivityResultLauncher<String[]> selectImageLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.OpenDocument(),
|
||||
result -> mSearchLayout.setImageUri(result));
|
||||
@Nullable
|
||||
private SearchBar mSearchBar;
|
||||
@@ -216,7 +214,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
};
|
||||
// Double click back exit
|
||||
private final long mPressBackTime = 0;
|
||||
private long mPressBackTime = 0;
|
||||
private boolean mHasFirstRefresh = false;
|
||||
private int mNavCheckedId = 0;
|
||||
private DownloadManager mDownloadManager;
|
||||
@@ -664,7 +662,7 @@ public final class GalleryListScene extends BaseScene
|
||||
if (null == context) {
|
||||
return;
|
||||
}
|
||||
final BaseDialogBuilder builder = new BaseDialogBuilder(context);
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||
builder.setMessage(R.string.add_quick_search_tip);
|
||||
builder.setTitle(R.string.readme);
|
||||
builder.show();
|
||||
@@ -779,21 +777,6 @@ public final class GalleryListScene extends BaseScene
|
||||
return view;
|
||||
}
|
||||
|
||||
public boolean isBackpressCanPreviewLauncherStatus() {
|
||||
if (null != mFabLayout && mFabLayout.isExpanded()) {
|
||||
return false;
|
||||
}
|
||||
switch (mState) {
|
||||
case STATE_NORMAL:
|
||||
return getStackIndex() == 0;
|
||||
case STATE_SIMPLE_SEARCH:
|
||||
case STATE_SEARCH:
|
||||
case STATE_SEARCH_SHOW_LIST:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (null != mFabLayout && mFabLayout.isExpanded()) {
|
||||
@@ -966,7 +949,7 @@ public final class GalleryListScene extends BaseScene
|
||||
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
|
||||
};
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(EhUtils.getSuitableTitle(gi))
|
||||
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
|
||||
switch (which) {
|
||||
@@ -978,7 +961,7 @@ public final class GalleryListScene extends BaseScene
|
||||
break;
|
||||
case 1: // Download
|
||||
if (downloaded) {
|
||||
new BaseDialogBuilder(context)
|
||||
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))
|
||||
@@ -1005,7 +988,7 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show();
|
||||
@@ -1200,7 +1183,6 @@ public final class GalleryListScene extends BaseScene
|
||||
break;
|
||||
}
|
||||
}
|
||||
((StageActivity)requireActivity()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1352,9 +1334,7 @@ public final class GalleryListScene extends BaseScene
|
||||
@Override
|
||||
public void onSelectImage() {
|
||||
try {
|
||||
PickVisualMediaRequest.Builder builder = new PickVisualMediaRequest.Builder();
|
||||
builder.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE);
|
||||
selectImageLauncher.launch(builder.build());
|
||||
selectImageLauncher.launch(new String[]{"image/*"});
|
||||
} 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 BaseDialogBuilder(context).setTitle(R.string.go_to)
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setTitle(R.string.go_to)
|
||||
.setView(R.layout.dialog_go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
|
||||
@@ -0,0 +1,654 @@
|
||||
/*
|
||||
* 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 org.greenrobot.greendao.query.LazyList;
|
||||
|
||||
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 LazyList<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.close();
|
||||
mLazyList = null;
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView.stopScroll();
|
||||
mRecyclerView = null;
|
||||
}
|
||||
|
||||
mViewTransition = null;
|
||||
mAdapter = null;
|
||||
}
|
||||
|
||||
// Remember to notify
|
||||
private void updateLazyList() {
|
||||
LazyList<HistoryInfo> lazyList = EhDB.getHistoryLazyList();
|
||||
if (mLazyList != null) {
|
||||
mLazyList.close();
|
||||
}
|
||||
mLazyList = lazyList;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,489 +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.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-13
@@ -16,10 +16,10 @@
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
import androidx.biometric.BiometricPrompt
|
||||
@@ -30,16 +30,14 @@ import androidx.fragment.app.FragmentActivity
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
fun Context.isAuthenticationSupported(): Boolean {
|
||||
val authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||
return BiometricManager.from(this)
|
||||
.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
class SecurityScene : SolidScene() {
|
||||
override fun needShowLeftDrawer(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
class SecurityActivity : AppCompatActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isAuthenticationSupported())
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (isAuthenticationSupported(requireContext()))
|
||||
startAuthentication(getString(R.string.settings_privacy_require_unlock))
|
||||
else
|
||||
onSuccess()
|
||||
@@ -54,7 +52,7 @@ class SecurityActivity : AppCompatActivity() {
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
confirmationRequired = confirmationRequired,
|
||||
executor = ContextCompat.getMainExecutor(this),
|
||||
executor = ContextCompat.getMainExecutor(requireContext()),
|
||||
callback = AuthenticationCallback()
|
||||
)
|
||||
}
|
||||
@@ -72,12 +70,22 @@ class SecurityActivity : AppCompatActivity() {
|
||||
errorCode: Int,
|
||||
errString: CharSequence
|
||||
) {
|
||||
moveTaskToBack(true)
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun isAuthenticationSupported(context: Context): Boolean {
|
||||
val authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||
return BiometricManager.from(context)
|
||||
.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSuccess() {
|
||||
EhApplication.locked = false
|
||||
if (stackIndex == 0)
|
||||
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -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 BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(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,6 +20,7 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.scene.Announcer;
|
||||
|
||||
/**
|
||||
@@ -27,14 +28,26 @@ import com.hippo.scene.Announcer;
|
||||
*/
|
||||
public class SolidScene extends BaseScene {
|
||||
|
||||
public static final int CHECK_STEP_SIGN_IN = 1;
|
||||
public static final int CHECK_STEP_SELECT_SITE = 2;
|
||||
public static final int CHECK_STEP_SECURITY = 0;
|
||||
public static final int CHECK_STEP_WARNING = 1;
|
||||
public static final int CHECK_STEP_SIGN_IN = 2;
|
||||
public static final int CHECK_STEP_SELECT_SITE = 3;
|
||||
public static final String KEY_TARGET_SCENE = "target_scene";
|
||||
public static final String KEY_TARGET_ARGS = "target_args";
|
||||
private static final String TAG = SolidScene.class.getSimpleName();
|
||||
|
||||
public void startSceneForCheckStep(int checkStep, Bundle args) {
|
||||
switch (checkStep) {
|
||||
case CHECK_STEP_SECURITY:
|
||||
if (Settings.getShowWarning()) {
|
||||
startScene(new Announcer(WarningScene.class).setArgs(args), true);
|
||||
break;
|
||||
}
|
||||
case CHECK_STEP_WARNING:
|
||||
if (EhUtils.needSignedIn(getContext())) {
|
||||
startScene(new Announcer(SignInScene.class).setArgs(args), true);
|
||||
break;
|
||||
}
|
||||
case CHECK_STEP_SIGN_IN:
|
||||
if (Settings.getSelectSite()) {
|
||||
startScene(new Announcer(SelectSiteScene.class).setArgs(args), true);
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.annotation.ViewLifeCircle;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
public final class WarningScene extends SolidScene implements View.OnClickListener {
|
||||
|
||||
@Nullable
|
||||
private View mReject;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private View mAccept;
|
||||
|
||||
@Override
|
||||
public boolean needShowLeftDrawer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_warning, container, false);
|
||||
|
||||
mReject = ViewUtils.$$(view, R.id.reject);
|
||||
mAccept = ViewUtils.$$(view, R.id.accept);
|
||||
|
||||
mReject.setOnClickListener(this);
|
||||
mAccept.setOnClickListener(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
mReject = null;
|
||||
mAccept = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mReject == v) {
|
||||
finishStage();
|
||||
} else if (mAccept == v) {
|
||||
// Never show this warning anymore
|
||||
Settings.putShowWarning(false);
|
||||
|
||||
// Start new scene and finish it self
|
||||
MainActivity activity = getMainActivity();
|
||||
if (null != activity) {
|
||||
startSceneForCheckStep(CHECK_STEP_WARNING, getArguments());
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import android.webkit.WebView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
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 BaseDialogBuilder(view.getContext())
|
||||
new MaterialAlertDialogBuilder(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 BaseDialogBuilder(view.getContext())
|
||||
new MaterialAlertDialogBuilder(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 BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setView(promptView)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm(valueView.getText().toString()))
|
||||
.setOnCancelListener(dialog -> result.cancel())
|
||||
|
||||
@@ -46,7 +46,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
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 BaseDialogBuilder(getContext())
|
||||
new MaterialAlertDialogBuilder(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) {
|
||||
BaseDialogBuilder(context)
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.search_tip)
|
||||
.show()
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ public abstract class GalleryProvider {
|
||||
public void stop() {
|
||||
OSUtils.checkMainLoop();
|
||||
mImageCache.evictAll();
|
||||
Image.lazyEvictAll();
|
||||
}
|
||||
|
||||
public void setGLRoot(GLRoot glRoot) {
|
||||
@@ -246,14 +245,18 @@ public abstract class GalleryProvider {
|
||||
}
|
||||
|
||||
public void add(Integer key, ImageWrapper value) {
|
||||
if (value.getFormat() != Image.FORMAT_ANIMATED)
|
||||
if (value.obtain())
|
||||
put(key, value);
|
||||
if (value.getFormat() != Image.FORMAT_GIF && value.obtain()) {
|
||||
put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(Integer key, ImageWrapper value) {
|
||||
return value.getWidth() * value.getHeight() * 4;
|
||||
int size = value.getWidth() * value.getHeight() * 4;
|
||||
if (value.getFormat() == Image.FORMAT_GIF) {
|
||||
size *= 5;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -153,6 +153,12 @@ public class ImageTexture implements Texture, Animatable {
|
||||
}
|
||||
|
||||
mTiles = list.toArray(new Tile[list.size()]);
|
||||
|
||||
if (!mImage.isCompleted()) {
|
||||
Runnable runnable = new AnimateRunnable();
|
||||
mAnimateRunnable = runnable;
|
||||
sThreadExecutor.execute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private static Tile obtainSmallTile() {
|
||||
@@ -233,7 +239,7 @@ public class ImageTexture implements Texture, Animatable {
|
||||
}
|
||||
|
||||
boolean end = mReleased.get() || mImage.isImageRecycled() || mNeedRelease.get() ||
|
||||
(mImage.getFormat() == 0) || mRunning.get();
|
||||
(mImage.isCompleted() && mImage.getFrameCount() <= 1) || mRunning.get();
|
||||
|
||||
synchronized (mImage) {
|
||||
mImageBusy = false;
|
||||
@@ -591,11 +597,25 @@ public class ImageTexture implements Texture, Animatable {
|
||||
mImageBusy = true;
|
||||
}
|
||||
|
||||
if (!mImage.isCompleted()) {
|
||||
try {
|
||||
sPVLock.p();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
if (!mNeedRelease.get()) {
|
||||
mImage.complete();
|
||||
}
|
||||
sPVLock.v();
|
||||
}
|
||||
|
||||
int frameCount = mImage.getFrameCount();
|
||||
|
||||
synchronized (mImage) {
|
||||
// Release image
|
||||
mImageBusy = false;
|
||||
// Check need release, frameCount <= 1
|
||||
if (mNeedRelease.get() || mImage.getFormat() == 0) {
|
||||
if (mNeedRelease.get() || frameCount <= 1) {
|
||||
mAnimateRunnable = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,22 @@ public class ImageWrapper {
|
||||
return mCut.height();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#complete()
|
||||
*/
|
||||
public boolean complete() {
|
||||
synchronized (mImage) {
|
||||
return mImage.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#isCompleted()
|
||||
*/
|
||||
public boolean isCompleted() {
|
||||
return mImage.isCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int)
|
||||
*/
|
||||
@@ -152,6 +168,13 @@ public class ImageWrapper {
|
||||
return mImage.getDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getFrameCount()
|
||||
*/
|
||||
public int getFrameCount() {
|
||||
return mImage.getFrameCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#isOpaque()
|
||||
*/
|
||||
|
||||
@@ -1,267 +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.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.hippo.Native.getFd
|
||||
import java.io.FileDescriptor
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* The `Image` is a image which stored pixel data in native heap
|
||||
*/
|
||||
class Image private constructor(nativePtr: Long) {
|
||||
private var mNativePtr: Long
|
||||
private var needRecycle = false
|
||||
private var nativeProcessCnt = 0
|
||||
|
||||
init {
|
||||
if (needEvictAll) evictAll()
|
||||
mNativePtr = nativePtr
|
||||
imageList.add(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
val format: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetFormat(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the image
|
||||
*/
|
||||
val width: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetWidth(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the image
|
||||
*/
|
||||
val height: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetHeight(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun checkRecycledAndLock() {
|
||||
check(mNativePtr != 0L) { "The image is recycled." }
|
||||
nativeProcessCnt++
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun releaseNativeLock() {
|
||||
nativeProcessCnt--
|
||||
if (needRecycle) recycle()
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the image to `Bitmap`
|
||||
*/
|
||||
fun render(
|
||||
srcX: Int, srcY: Int, dst: Bitmap, dstX: Int, dstY: Int,
|
||||
width: Int, height: Int, fillBlank: Boolean, defaultColor: Int
|
||||
) {
|
||||
checkRecycledAndLock()
|
||||
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height)
|
||||
releaseNativeLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Call `glTexImage2D` for init is true and
|
||||
* call `glTexSubImage2D` for init is false.
|
||||
* width * height must <= 512 * 512 or do nothing
|
||||
*/
|
||||
fun texImage(init: Boolean, offsetX: Int, offsetY: Int, width: Int, height: Int) {
|
||||
checkRecycledAndLock()
|
||||
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height)
|
||||
releaseNativeLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to next frame. Do nothing for non-animation image
|
||||
*/
|
||||
fun advance() {
|
||||
checkRecycledAndLock()
|
||||
nativeAdvance(mNativePtr)
|
||||
releaseNativeLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current frame delay. 0 for non-animation image
|
||||
*/
|
||||
val delay: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val delay = nativeGetDelay(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return if (delay <= 10) 100 else delay
|
||||
}
|
||||
|
||||
/**
|
||||
* Return is the image opaque
|
||||
*/
|
||||
val isOpaque: Boolean
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeIsOpaque(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the native object associated with this image.
|
||||
* It must be called when the image will not be used.
|
||||
* The image can't be used after this method is called.
|
||||
*/
|
||||
@Synchronized
|
||||
fun recycle() {
|
||||
if (mNativePtr != 0L) {
|
||||
if (nativeProcessCnt != 0) {
|
||||
needRecycle = true
|
||||
return
|
||||
}
|
||||
nativeRecycle(mNativePtr)
|
||||
mNativePtr = 0
|
||||
imageList.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this image has been recycled.
|
||||
*/
|
||||
val isRecycled: Boolean
|
||||
get() = mNativePtr == 0L
|
||||
|
||||
companion object {
|
||||
const val FORMAT_NORMAL = 0
|
||||
const val FORMAT_ANIMATED = 1
|
||||
private val imageList = ArrayList<Image>()
|
||||
private var needEvictAll = false
|
||||
private fun newFromAddr(addr: Long): Image? {
|
||||
return if (addr == 0L) null else Image(addr)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun lazyEvictAll() {
|
||||
needEvictAll = true
|
||||
}
|
||||
|
||||
private fun evictAll() {
|
||||
ArrayList(imageList).forEach(
|
||||
Consumer { obj: Image -> obj.recycle() })
|
||||
needEvictAll = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: FileDescriptor?, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecode(getFd(fd)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: Int, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecode(fd))
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decodeAddr(addr: Long, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecodeAddr(addr))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@JvmStatic
|
||||
fun create(bitmap: Bitmap): Image? {
|
||||
return newFromAddr(nativeCreate(bitmap))
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled `Image` instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
@JvmStatic
|
||||
val imageCount: Int
|
||||
get() = imageList.size
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeDecode(fd: Int): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeDecodeAddr(addr: Long): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeCreate(bitmap: Bitmap): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeRender(
|
||||
nativePtr: Long,
|
||||
srcX: Int, srcY: Int, dst: Bitmap, dstX: Int, dstY: Int,
|
||||
width: Int, height: Int
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeTexImage(
|
||||
nativePtr: Long,
|
||||
init: Boolean,
|
||||
offsetX: Int,
|
||||
offsetY: Int,
|
||||
width: Int,
|
||||
height: Int
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeAdvance(nativePtr: Long)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetDelay(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeIsOpaque(nativePtr: Long): Boolean
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeRecycle(nativePtr: Long)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetFormat(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetWidth(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetHeight(nativePtr: Long): Int
|
||||
}
|
||||
}
|
||||
@@ -1,303 +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.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import java.io.FileDescriptor
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* A image with [Image] for data and [Bitmap] for render.
|
||||
*/
|
||||
class ImageBitmap : Animatable, Runnable {
|
||||
private val mBitmap: Bitmap
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
val format: Int
|
||||
|
||||
/**
|
||||
* Return image is opaque
|
||||
*/
|
||||
val isOpaque: Boolean
|
||||
private val mCallbackSet: MutableSet<WeakReference<Callback>> = LinkedHashSet()
|
||||
private var mImage: Image? = null
|
||||
private var mReferences = 0
|
||||
private var mAnimationReferences = 0
|
||||
private var mRunning = false
|
||||
|
||||
private constructor(image: Image) {
|
||||
val width = image.width
|
||||
val height = image.height
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
image.render(0, 0, mBitmap, 0, 0, width, height, false, 0)
|
||||
format = image.format
|
||||
isOpaque = image.isOpaque
|
||||
if (format == 1) {
|
||||
// For animated image, save image object
|
||||
mImage = image
|
||||
} else {
|
||||
// Free the image
|
||||
image.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(bitmap: Bitmap) {
|
||||
format = Image.FORMAT_NORMAL
|
||||
mBitmap = bitmap
|
||||
isOpaque = !bitmap.hasAlpha()
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the image bitmap
|
||||
*
|
||||
* @return false for the image is recycled and obtain failed
|
||||
*/
|
||||
@Synchronized
|
||||
fun obtain(): Boolean {
|
||||
return if (mBitmap.isRecycled) {
|
||||
false
|
||||
} else {
|
||||
++mReferences
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the image bitmap
|
||||
*/
|
||||
@Synchronized
|
||||
fun release() {
|
||||
--mReferences
|
||||
if (mReferences <= 0 && !mBitmap.isRecycled) {
|
||||
mBitmap.recycle()
|
||||
if (mImage != null) {
|
||||
mImage!!.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback for invalidating
|
||||
*/
|
||||
fun addCallback(callback: Callback) {
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var c: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
c = iterator.next().get()
|
||||
if (c == null) {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
} else if (c === callback) {
|
||||
return
|
||||
}
|
||||
}
|
||||
mCallbackSet.add(WeakReference(callback))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback
|
||||
*/
|
||||
fun removeCallback(callback: Callback) {
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var c: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
c = iterator.next().get()
|
||||
if (c == null) {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
} else if (c === callback) {
|
||||
iterator.remove()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image width
|
||||
*/
|
||||
val width: Int
|
||||
get() = mBitmap.width
|
||||
|
||||
/**
|
||||
* Return image height
|
||||
*/
|
||||
val height: Int
|
||||
get() = mBitmap.height
|
||||
|
||||
/**
|
||||
* Return image is animated
|
||||
*/
|
||||
val isAnimated: Boolean
|
||||
get() = mImage != null
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, left: Float, top: Float, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, left, top, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, src: Rect?, dst: Rect, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, src: Rect?, dst: RectF, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `start()` and `stop()` is a pair
|
||||
*/
|
||||
override fun start() {
|
||||
mAnimationReferences++
|
||||
if (mBitmap.isRecycled || mImage == null || mRunning) {
|
||||
return
|
||||
}
|
||||
mRunning = true
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage!!.delay).toLong())
|
||||
}
|
||||
|
||||
/**
|
||||
* `start()` and `stop()` is a pair
|
||||
*/
|
||||
override fun stop() {
|
||||
mAnimationReferences--
|
||||
if (mAnimationReferences <= 0) {
|
||||
mRunning = false
|
||||
HANDLER.removeCallbacks(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRunning(): Boolean {
|
||||
return mRunning
|
||||
}
|
||||
|
||||
private fun notifyUpdate(): Boolean {
|
||||
var hasCallback = false
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var callback: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
callback = iterator.next().get()
|
||||
if (callback != null) {
|
||||
// Render bitmap int the first time
|
||||
if (!hasCallback) {
|
||||
hasCallback = true
|
||||
mImage!!.render(0, 0, mBitmap, 0, 0, mImage!!.width, mImage!!.height, false, 0)
|
||||
}
|
||||
callback.invalidateImage(this)
|
||||
} else {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
return hasCallback
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
// Check recycled
|
||||
if (mBitmap.isRecycled || mImage == null) {
|
||||
mRunning = false
|
||||
return
|
||||
}
|
||||
mImage!!.advance()
|
||||
if (notifyUpdate()) {
|
||||
if (mRunning) {
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage!!.delay).toLong())
|
||||
}
|
||||
} else {
|
||||
mRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun invalidateImage(who: ImageBitmap?)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val HANDLER = Handler(Looper.getMainLooper())
|
||||
|
||||
/**
|
||||
* Decode `InputStream`, then create image.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: FileDescriptor): ImageBitmap? {
|
||||
val image = Image.decode(fd, false)
|
||||
return if (image != null) {
|
||||
create(image)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `ImageBitmap` from `Image`.
|
||||
* It is not recommended. Use [.decode] if you can.
|
||||
*
|
||||
* @param image the image should not be used before and
|
||||
* it must not be recycled. And the image should
|
||||
* not be used directly anymore.
|
||||
*/
|
||||
fun create(image: Image): ImageBitmap? {
|
||||
return if (!image.isRecycled) {
|
||||
try {
|
||||
ImageBitmap(image)
|
||||
} catch (e: OutOfMemoryError) {
|
||||
image.recycle()
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `ImageBitmap` from `Bitmap`.
|
||||
*
|
||||
* @param bitmap the bitmap should not be recycled
|
||||
*/
|
||||
fun create(bitmap: Bitmap): ImageBitmap? {
|
||||
return if (!bitmap.isRecycled) {
|
||||
ImageBitmap(bitmap)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +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.image
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
* A drawable to draw [ImageBitmap]
|
||||
*/
|
||||
class ImageDrawable(imageBitmap: ImageBitmap) : Drawable(), Animatable, ImageBitmap.Callback {
|
||||
private val mImageBitmap: ImageBitmap
|
||||
private val mPaint: Paint
|
||||
|
||||
init {
|
||||
if (!imageBitmap.obtain()) {
|
||||
throw Exception()
|
||||
}
|
||||
mImageBitmap = imageBitmap
|
||||
mPaint = Paint(Paint.FILTER_BITMAP_FLAG)
|
||||
|
||||
// Add callback
|
||||
imageBitmap.addCallback(this)
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
mImageBitmap.removeCallback(this)
|
||||
mImageBitmap.release()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
mImageBitmap.draw(canvas, null, bounds, mPaint)
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
mPaint.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
mPaint.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int {
|
||||
return if (mImageBitmap.isOpaque) PixelFormat.OPAQUE else PixelFormat.UNKNOWN
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return mImageBitmap.width
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
return mImageBitmap.height
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
mImageBitmap.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
mImageBitmap.stop()
|
||||
}
|
||||
|
||||
override fun isRunning(): Boolean {
|
||||
return mImageBitmap.isRunning
|
||||
}
|
||||
|
||||
override fun invalidateImage(who: ImageBitmap?) {
|
||||
invalidateSelf()
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
/**
|
||||
@@ -255,7 +255,7 @@ public abstract class DialogPreference extends Preference implements
|
||||
|
||||
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
|
||||
|
||||
mBuilder = new BaseDialogBuilder(context)
|
||||
mBuilder = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(mDialogTitle)
|
||||
.setIcon(mDialogIcon)
|
||||
.setPositiveButton(mPositiveButtonText, this)
|
||||
|
||||
@@ -61,7 +61,6 @@ public class SceneFragment extends Fragment {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity instanceof StageActivity) {
|
||||
((StageActivity) activity).startScene(announcer, horizontal);
|
||||
((StageActivity) activity).updateBackPressCallBackStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -32,7 +31,6 @@ import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.ui.EhActivity;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
|
||||
@@ -60,8 +58,6 @@ public abstract class StageActivity extends EhActivity {
|
||||
private final SceneViewComparator mSceneViewComparator = new SceneViewComparator();
|
||||
private int mStageId = IntIdGenerator.INVALID_ID;
|
||||
|
||||
private OnBackPressedCallback callback;
|
||||
|
||||
public static void registerLaunchMode(Class<?> clazz, @SceneFragment.LaunchMode int launchMode) {
|
||||
if (launchMode != SceneFragment.LAUNCH_MODE_STANDARD &&
|
||||
launchMode != SceneFragment.LAUNCH_MODE_SINGLE_TOP &&
|
||||
@@ -144,8 +140,6 @@ public abstract class StageActivity extends EhActivity {
|
||||
protected final void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
callback = new BackPressCallBack(false);
|
||||
getOnBackPressedDispatcher().addCallback(callback);
|
||||
if (savedInstanceState != null) {
|
||||
mStageId = savedInstanceState.getInt(KEY_STAGE_ID, IntIdGenerator.INVALID_ID);
|
||||
ArrayList<String> list = savedInstanceState.getStringArrayList(KEY_SCENE_TAG_LIST);
|
||||
@@ -253,7 +247,6 @@ public abstract class StageActivity extends EhActivity {
|
||||
|
||||
public void startScene(Announcer announcer) {
|
||||
startScene(announcer, false);
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
public void startScene(Announcer announcer, boolean horizontal) {
|
||||
@@ -468,7 +461,6 @@ public abstract class StageActivity extends EhActivity {
|
||||
// TODO Call onNewArguments when view created ?
|
||||
scene.onNewArguments(args);
|
||||
}
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
int getSceneIndex(SceneFragment scene) {
|
||||
@@ -485,7 +477,6 @@ public abstract class StageActivity extends EhActivity {
|
||||
|
||||
public void finishScene(SceneFragment scene) {
|
||||
finishScene(scene.getTag());
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
private void finishScene(String tag) {
|
||||
@@ -567,51 +558,25 @@ public abstract class StageActivity extends EhActivity {
|
||||
} else {
|
||||
fragmentManager.beginTransaction().detach(fragment).attach(fragment).commitAllowingStateLoss();
|
||||
}
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
private boolean shouldEnableBackPressCallBack() {
|
||||
if (mSceneTagList.size() > 1)
|
||||
return true;
|
||||
if (mSceneTagList.size() == 0)
|
||||
return false;
|
||||
String tag = mSceneTagList.get(0);
|
||||
GalleryListScene fragment = (GalleryListScene) getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null)
|
||||
return false;
|
||||
return !fragment.isBackpressCanPreviewLauncherStatus();
|
||||
}
|
||||
|
||||
public void updateBackPressCallBackStatus() {
|
||||
var isEnabled = callback.isEnabled();
|
||||
var shouldEnable = shouldEnableBackPressCallBack();
|
||||
if (isEnabled != shouldEnable)
|
||||
callback.setEnabled(shouldEnable);
|
||||
}
|
||||
|
||||
class BackPressCallBack extends OnBackPressedCallback {
|
||||
public BackPressCallBack(boolean enabled) {
|
||||
super(enabled);
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
int size = mSceneTagList.size();
|
||||
String tag = mSceneTagList.get(size - 1);
|
||||
SceneFragment scene;
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
Log.e(TAG, "onBackPressed: Can't find scene by tag: " + tag);
|
||||
return;
|
||||
}
|
||||
if (!(fragment instanceof SceneFragment)) {
|
||||
Log.e(TAG, "onBackPressed: The fragment is not SceneFragment");
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
int size = mSceneTagList.size();
|
||||
String tag = mSceneTagList.get(size - 1);
|
||||
SceneFragment scene;
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
Log.e(TAG, "onBackPressed: Can't find scene by tag: " + tag);
|
||||
return;
|
||||
}
|
||||
if (!(fragment instanceof SceneFragment)) {
|
||||
Log.e(TAG, "onBackPressed: The fragment is not SceneFragment");
|
||||
return;
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onBackPressed();
|
||||
}
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -619,17 +584,19 @@ public abstract class StageActivity extends EhActivity {
|
||||
super.onProvideAssistContent(outContent);
|
||||
int size = mSceneTagList.size();
|
||||
String tag = mSceneTagList.get(size - 1);
|
||||
SceneFragment scene;
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
Log.e(TAG, "onProvideAssistContent: Can't find scene by tag: " + tag);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fragment instanceof SceneFragment scene) {
|
||||
scene.onProvideAssistContent(outContent);
|
||||
} else {
|
||||
if (!(fragment instanceof SceneFragment)) {
|
||||
Log.e(TAG, "onProvideAssistContent: The fragment is not SceneFragment");
|
||||
return;
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onProvideAssistContent(outContent);
|
||||
}
|
||||
|
||||
public SceneFragment findSceneByTag(String tag) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ final class DocumentsContractApi21 {
|
||||
private static final String TAG = DocumentsContractApi21.class.getSimpleName();
|
||||
private static final String PATH_DOCUMENT = "document";
|
||||
private static final String PATH_TREE = "tree";
|
||||
|
||||
private DocumentsContractApi21() {
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ class FileDescriptorRandomAccessFile implements UniRandomAccessFile {
|
||||
private final int flushAfterWrite;
|
||||
private final Object closeLock = new Object();
|
||||
private volatile boolean closed = false;
|
||||
|
||||
private FileDescriptorRandomAccessFile(ParcelFileDescriptor pfd, FileDescriptor fd, String mode) {
|
||||
this.pfd = pfd;
|
||||
this.fd = fd;
|
||||
|
||||
Loaded 100 of 227 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user