Compare commits
188
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
306eb9d23c | ||
|
|
158218e501 | ||
|
|
d773332b74 | ||
|
|
5dc2eb0062 | ||
|
|
bee7febafc | ||
|
|
f49cc2d498 | ||
|
|
3f4d2b75fc | ||
|
|
af8733c2d1 | ||
|
|
545eed98a7 | ||
|
|
2da436189a | ||
|
|
906ea11733 | ||
|
|
47c5629f7d | ||
|
|
a37c676d96 | ||
|
|
d9981f15f2 | ||
|
|
27b75746fd | ||
|
|
ee80a3a841 | ||
|
|
f8d639054f | ||
|
|
7f76e58e4e | ||
|
|
1b326a86a2 | ||
|
|
607bf4c67f | ||
|
|
3e4836c91e | ||
|
|
dae8f21388 | ||
|
|
84b328b60b | ||
|
|
b98d55b444 | ||
|
|
ee9413e54a | ||
|
|
76dedce5da | ||
|
|
37f99ae706 | ||
|
|
aee58d967d | ||
|
|
5ecabeb045 | ||
|
|
408c142aad | ||
|
|
a56d541380 | ||
|
|
ae79417845 | ||
|
|
010daa583e | ||
|
|
3034957c99 | ||
|
|
99134346ed | ||
|
|
9a70708300 | ||
|
|
50f0df5012 | ||
|
|
599bb80106 | ||
|
|
aa0db1f400 | ||
|
|
82b6fd22a9 | ||
|
|
b5c730d37a | ||
|
|
a2cb6444f4 | ||
|
|
8a144eabca | ||
|
|
75c2a3b0ec | ||
|
|
beec02a69b | ||
|
|
df78b45f69 | ||
|
|
e6a3c8f165 | ||
|
|
4d948e93a3 | ||
|
|
ece082727b | ||
|
|
5b5040f4af | ||
|
|
d9d88e690b | ||
|
|
bda123fc39 | ||
|
|
2200bb9b45 | ||
|
|
884d460b52 | ||
|
|
beb97270a0 | ||
|
|
52681bd386 | ||
|
|
d772a737fc | ||
|
|
638fcfea12 | ||
|
|
cae32cca5a | ||
|
|
c429e7054a | ||
|
|
f526f11c73 | ||
|
|
892016368f | ||
|
|
d9464ac278 | ||
|
|
a68a9995a1 | ||
|
|
1a0c2cb180 | ||
|
|
d7572f5a79 | ||
|
|
a208651e13 | ||
|
|
41c231b4f3 | ||
|
|
567b6f7659 | ||
|
|
6d1e0089e5 | ||
|
|
502f532fe7 | ||
|
|
ddc47f09ab | ||
|
|
5dbcdf2fce | ||
|
|
706588c39c | ||
|
|
3e3e64942e | ||
|
|
3700ac8a84 | ||
|
|
cc0dc0abf5 | ||
|
|
5861147d17 | ||
|
|
732035a0e2 | ||
|
|
643f71009d | ||
|
|
4643e37adb | ||
|
|
1bc8854685 | ||
|
|
66ebf6a391 | ||
|
|
1239277cec | ||
|
|
9cb8fb5527 | ||
|
|
819d67dca8 | ||
|
|
f895982fa0 | ||
|
|
505c097659 | ||
|
|
783fa10157 | ||
|
|
82ffbf2a1d | ||
|
|
3c3ae47511 | ||
|
|
858d7acf68 | ||
|
|
f8b7005690 | ||
|
|
33a8cb98e6 | ||
|
|
338a22f2ed | ||
|
|
f7cecaf709 | ||
|
|
c38b3c15e7 | ||
|
|
4665678e9f | ||
|
|
07416eb74d | ||
|
|
6f37dc53c7 | ||
|
|
55b963dcf9 | ||
|
|
1a433019b3 | ||
|
|
7b292cb1ab | ||
|
|
b2f5590c04 | ||
|
|
2cf2c87a8b | ||
|
|
01420f7e72 | ||
|
|
3f699973b9 | ||
|
|
3f27c9139c | ||
|
|
9eee6fc4af | ||
|
|
a343dbe8f8 | ||
|
|
bc08a09621 | ||
|
|
586ad66bff | ||
|
|
1a426e1f5f | ||
|
|
e9a9f1070c | ||
|
|
7d3dd61bc4 | ||
|
|
f37894af88 | ||
|
|
14129545b8 | ||
|
|
f06346b6c7 | ||
|
|
13e3e8c534 | ||
|
|
4be282813c | ||
|
|
74e66a407d | ||
|
|
b19da4c82e | ||
|
|
2f49764e1b | ||
|
|
08dfb0f72d | ||
|
|
9251ed238c | ||
|
|
3b96328765 | ||
|
|
108928e524 | ||
|
|
b1191bbf9b | ||
|
|
3424260cfd | ||
|
|
e1fe56160e | ||
|
|
5838ea3e3f | ||
|
|
0906b17ed1 | ||
|
|
49b9cb72d3 | ||
|
|
01508cc3c1 | ||
|
|
79eba83812 | ||
|
|
a455dee24c | ||
|
|
f58b4959e4 | ||
|
|
a6b4182ecd | ||
|
|
9010591ffc | ||
|
|
e0dde2c982 | ||
|
|
6109d97bf7 | ||
|
|
95936e01ce | ||
|
|
beadc7b185 | ||
|
|
0bc2e45137 | ||
|
|
b8e649e995 | ||
|
|
bdcd21a535 | ||
|
|
1b779a8b77 | ||
|
|
6c59a341ee | ||
|
|
1a6e03c8f8 | ||
|
|
653c0a2ac6 | ||
|
|
e4189f367a | ||
|
|
48b7355ba4 | ||
|
|
d7d3a903ff | ||
|
|
bab3067273 | ||
|
|
fdd70a6908 | ||
|
|
3699a8fe30 | ||
|
|
32f98f8a15 | ||
|
|
c5366760a2 | ||
|
|
c98ec2a7a4 | ||
|
|
e4c729e51b | ||
|
|
f36726d8fb | ||
|
|
d2fc2ee136 | ||
|
|
6edc2164af | ||
|
|
e34dc30f1f | ||
|
|
c03081d3fa | ||
|
|
97287e4538 | ||
|
|
fc44507a34 | ||
|
|
2597004750 | ||
|
|
d1bd9c5fae | ||
|
|
54542f9246 | ||
|
|
377b23458f | ||
|
|
b7973cdb76 | ||
|
|
5f1a70886c | ||
|
|
5fbc8b0b66 | ||
|
|
780b2b6e1d | ||
|
|
039ad2032c | ||
|
|
a24222b693 | ||
|
|
6047d91d7e | ||
|
|
f0158f6946 | ||
|
|
00ecdc58e6 | ||
|
|
0a075a97af | ||
|
|
34ee70ca7d | ||
|
|
338751ef2b | ||
|
|
d4465ba864 | ||
|
|
9a8fdd4edb | ||
|
|
338848f1eb | ||
|
|
9b918ec036 | ||
|
|
24439c7853 |
No files matched your search
@@ -1,8 +1,17 @@
|
||||
name: Bug 反馈 / Bug report
|
||||
description: 提交一个问题报告 / Create a bug report to help us improve
|
||||
description: 提交一个问题报告 / Create a bug report
|
||||
labels:
|
||||
- "bug"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
|
||||
提交问题报告前,还请首先完成文末的自查步骤
|
||||
|
||||
Thanks for your contribution on Ehviewer-Overhauled Project!
|
||||
Please finish verify steps which list in the end first before create bug report
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
@@ -44,12 +53,25 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: more
|
||||
attributes:
|
||||
label: 备注 / Addition details
|
||||
description: |
|
||||
在此处写下其他您想说的内容
|
||||
Describe addition details here
|
||||
placeholder: |
|
||||
其他有用的信息与附件
|
||||
Additional details and attachments
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: EhViewer 版本号 / EhViewer version code
|
||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
||||
placeholder: 四位版本号 / 4 digits version code
|
||||
placeholder: 1.6.9.13
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -119,4 +141,4 @@ body:
|
||||
required: true
|
||||
|
||||
- label: 您确保已使用最新 CI 版本测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest CI version
|
||||
required: true
|
||||
required: true
|
||||
@@ -1,8 +1,17 @@
|
||||
name: 功能请求 / Feature request
|
||||
description: 提出一个功能建议 / Suggest an idea for this project
|
||||
description: 提出一个功能建议 / Suggest an idea
|
||||
labels:
|
||||
- "enhancement"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
|
||||
提交功能建议前,还请首先完成文末的自查步骤
|
||||
|
||||
Thanks for your contribution on Ehviewer-Overhauled Project!
|
||||
Please finish verify steps which list in the end first before suggest an idea
|
||||
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
@@ -33,7 +42,7 @@ body:
|
||||
- type: textarea
|
||||
id: more
|
||||
attributes:
|
||||
label: 其他细节 / Addition details
|
||||
label: 备注 / Addition details
|
||||
description: |
|
||||
在此处写下其他您想说的内容
|
||||
Describe addition details here
|
||||
@@ -48,7 +57,7 @@ body:
|
||||
attributes:
|
||||
label: EhViewer 版本号 / EhViewer version code
|
||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
||||
placeholder: 四位版本号 / 4 digits version code
|
||||
placeholder: 1.6.9.13
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -82,4 +91,4 @@ body:
|
||||
required: true
|
||||
|
||||
- label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
|
||||
required: true
|
||||
required: true
|
||||
@@ -17,19 +17,20 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
arguments: assembleRelease
|
||||
|
||||
- name: Update ARM64
|
||||
|
||||
@@ -12,15 +12,15 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
@@ -15,15 +15,15 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
@@ -64,11 +64,15 @@ EhViewer fork dedicated to lightweight and high-performance
|
||||
|
||||
with Material Design 3 and Dynamic Color Support
|
||||
|
||||
Maintained by Tarsin Norbin aka 飛鳥澪 (asuka-mio)
|
||||
|
||||
Also thanks @txyyh for her help in docs, release notes, design and 1.7.x.x maintenance
|
||||
|
||||
# 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>
|
||||
|
||||
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
|
||||
There are two development branches: 1.8.x.x and 1.7.x.x . Version 1.8 only support platform api 31 (aka Android S) and above, also the develop mainline. Version 1.7 should only be used on Android 11 and below, since these platforms lack native api of image decoding.
|
||||
|
||||
# Screenshot
|
||||
|
||||
@@ -79,7 +83,7 @@ App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 a
|
||||
|
||||
Here is the libraries
|
||||
|
||||
- [AOSP&AndroidX](http://source.android.com/)
|
||||
- [AOSP & AndroidX](http://source.android.com/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
|
||||
+26
-31
@@ -7,12 +7,7 @@ plugins {
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
ndkVersion "25.0.8775105"
|
||||
|
||||
Properties localProperties = new Properties()
|
||||
if (project.rootProject.file('local.properties').exists()) {
|
||||
localProperties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
}
|
||||
ndkVersion "25.1.8937393"
|
||||
|
||||
splits {
|
||||
abi {
|
||||
@@ -36,8 +31,8 @@ android {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
targetSdkVersion 33
|
||||
versionCode 180009
|
||||
versionName "1.8.2.0"
|
||||
versionCode 180020
|
||||
versionName "1.8.3.0-beta02"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -48,12 +43,12 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
jvmTarget = '17'
|
||||
}
|
||||
|
||||
lint {
|
||||
@@ -91,33 +86,33 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.0"
|
||||
kotlinCompilerExtensionVersion = "1.4.0-alpha02"
|
||||
}
|
||||
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.0-beta01"
|
||||
def activity_version = "1.6.1"
|
||||
implementation "androidx.activity:activity:$activity_version"
|
||||
implementation "androidx.activity:activity-ktx:$activity_version"
|
||||
|
||||
implementation "androidx.annotation:annotation:1.5.0-alpha02"
|
||||
implementation "androidx.annotation:annotation-experimental:1.3.0-beta01"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0-beta01"
|
||||
implementation "androidx.annotation:annotation:1.5.0"
|
||||
implementation "androidx.annotation:annotation-experimental:1.3.0"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0-rc01"
|
||||
|
||||
def arch_core_version = "2.1.0"
|
||||
implementation "androidx.arch.core:core-common:$arch_core_version"
|
||||
implementation "androidx.arch.core:core-runtime:$arch_core_version"
|
||||
|
||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
|
||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||
implementation "androidx.browser:browser:1.4.0"
|
||||
|
||||
def collection_version = "1.3.0-alpha02"
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||
|
||||
def compose_version = "1.3.0-alpha03"
|
||||
def compose_version = "1.3.1"
|
||||
implementation "androidx.compose.animation:animation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
|
||||
@@ -125,23 +120,23 @@ dependencies {
|
||||
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
|
||||
implementation "androidx.compose.ui:ui:$compose_version"
|
||||
|
||||
implementation "androidx.compose.material3:material3:1.0.0-alpha16"
|
||||
implementation "androidx.compose.material3:material3:1.0.1"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
|
||||
def core_version = "1.9.0-beta01"
|
||||
def core_version = "1.9.0"
|
||||
implementation "androidx.core:core:$core_version"
|
||||
implementation "androidx.core:core-ktx:$core_version"
|
||||
|
||||
implementation "androidx.customview:customview:1.2.0-alpha01"
|
||||
implementation "androidx.customview:customview:1.2.0-alpha02"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.6.0-alpha01"
|
||||
def fragment_version = "1.6.0-alpha03"
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
def lifecycle_version = "2.6.0-alpha01"
|
||||
def lifecycle_version = "2.6.0-alpha03"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
@@ -151,18 +146,18 @@ dependencies {
|
||||
|
||||
implementation "androidx.loader:loader:1.1.0"
|
||||
|
||||
def nav_version = "2.5.1"
|
||||
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-alpha02"
|
||||
implementation "androidx.paging:paging-runtime:3.2.0-alpha03"
|
||||
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta02'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
|
||||
|
||||
def room_version = "2.5.0-alpha02"
|
||||
def room_version = "2.5.0-beta02"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-paging:$room_version"
|
||||
@@ -174,10 +169,10 @@ dependencies {
|
||||
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
|
||||
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
implementation 'androidx.webkit:webkit:1.5.0-rc01'
|
||||
implementation 'androidx.webkit:webkit:1.5.0'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.7.0-beta01'
|
||||
implementation 'com.google.android.material:material:1.7.0'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
@@ -189,7 +184,7 @@ dependencies {
|
||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.2'
|
||||
implementation 'org.jsoup:jsoup:1.15.3'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
|
||||
@@ -23,16 +23,11 @@
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<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" />
|
||||
@@ -42,6 +37,7 @@
|
||||
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"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
project(ehviewer)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto \
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto=thin \
|
||||
-mllvm -polly \
|
||||
-mllvm -polly-run-dce \
|
||||
-mllvm -polly-run-inliner \
|
||||
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
|
||||
add_subdirectory(libarchive)
|
||||
|
||||
# Build and link our app's native lib
|
||||
add_library(${PROJECT_NAME} SHARED main.c archive.c image.c)
|
||||
add_library(${PROJECT_NAME} SHARED archive.c image.c fdutils.c)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
|
||||
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
|
||||
+62
-44
@@ -32,12 +32,8 @@
|
||||
#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__)
|
||||
|
||||
#include "ehviewer.h"
|
||||
|
||||
typedef struct {
|
||||
int using;
|
||||
@@ -53,7 +49,7 @@ static archive_ctx **ctx_pool;
|
||||
static bool need_encrypt = false;
|
||||
static char *passwd = NULL;
|
||||
static void *archiveAddr = NULL;
|
||||
static jlong archiveSize = 0;
|
||||
static size_t archiveSize = 0;
|
||||
|
||||
const char supportExt[9][6] = {
|
||||
"jpeg",
|
||||
@@ -108,8 +104,12 @@ static int archive_alloc_ctx(archive_ctx **ctxptr) {
|
||||
free(ctx);
|
||||
return -ENOMEM;
|
||||
}
|
||||
archive_read_support_format_all(ctx->arc);
|
||||
archive_read_support_filter_all(ctx->arc);
|
||||
archive_read_support_format_tar(ctx->arc);
|
||||
archive_read_support_format_7zip(ctx->arc);
|
||||
archive_read_support_format_rar5(ctx->arc);
|
||||
archive_read_support_format_zip(ctx->arc);
|
||||
archive_read_support_filter_gzip(ctx->arc);
|
||||
archive_read_support_filter_xz(ctx->arc);
|
||||
archive_read_set_option(ctx->arc, "zip", "ignorecrc32", "1");
|
||||
if (passwd)
|
||||
archive_read_add_passphrase(ctx->arc, passwd);
|
||||
@@ -195,15 +195,16 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd, jlong size) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
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);
|
||||
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
archiveSize = size;
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_WILLNEED);
|
||||
pthread_mutex_init(&ctx_lock, 0);
|
||||
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
||||
if (!ctx_pool) {
|
||||
@@ -231,66 +232,56 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
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);
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_SEQUENTIAL);
|
||||
break;
|
||||
case ARCHIVE_FORMAT_7ZIP: // Seek is bad
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_RANDOM);
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
archive_release_ctx(ctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
typedef struct Memarea {
|
||||
void *buffer;
|
||||
long size;
|
||||
} Memarea;
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *env, jobject thiz, jint index) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
Java_com_hippo_UriArchiveAccessor_extractToAddr(JNIEnv *env, jobject thiz, jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (ret)
|
||||
return 0;
|
||||
Memarea *memarea = malloc(sizeof(Memarea));
|
||||
if (!memarea) {
|
||||
long long size = archive_entry_size(ctx->entry);
|
||||
void *buffer = malloc(size + sizeof(long long));
|
||||
if (!buffer) {
|
||||
ctx->using = 0;
|
||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||
return 0;
|
||||
}
|
||||
memarea->size = (long) archive_entry_size(ctx->entry);
|
||||
memarea->buffer = malloc(memarea->size);
|
||||
if (!memarea->buffer) {
|
||||
ctx->using = 0;
|
||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||
free(memarea);
|
||||
return 0;
|
||||
}
|
||||
ret = archive_read_data(ctx->arc, memarea->buffer, memarea->size);
|
||||
*(long long *) buffer = size;
|
||||
ret = archive_read_data(ctx->arc, buffer + sizeof(long long), size);
|
||||
ctx->using = 0;
|
||||
if (ret == memarea->size)
|
||||
return (jlong) memarea;
|
||||
if (ret != memarea->size)
|
||||
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(memarea->buffer);
|
||||
free(memarea);
|
||||
free(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
||||
archive_release_ctx(ctx_pool[i]);
|
||||
free(passwd);
|
||||
@@ -303,14 +294,14 @@ Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *env, jobject thiz) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
return need_encrypt;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jstring str) {
|
||||
(void)thiz; /* UNUSED */
|
||||
EH_UNUSED(thiz);
|
||||
struct archive_entry *entry;
|
||||
archive_ctx *ctx;
|
||||
jboolean ret = true;
|
||||
@@ -337,3 +328,30 @@ Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jst
|
||||
archive_release_ctx(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_getFilename(JNIEnv *env, jobject thiz, jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
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) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (!ret) {
|
||||
archive_read_data_into_fd(ctx->arc, fd);
|
||||
ctx->using = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef EHVIEWER_EHVIEWER_H
|
||||
#define EHVIEWER_EHVIEWER_H
|
||||
|
||||
#define EH_UNUSED(x) (void)x
|
||||
#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__)
|
||||
|
||||
#define madvise_log_if_error(addr, len, advice) \
|
||||
if (madvise(addr, len, advice)) \
|
||||
LOGE("%s%p%s%zu%s%d%s%s%s", "madvise addr:", addr, "len:", len, "with advice ", advice, " failed with error: ", strerror(errno), ", Ignored")
|
||||
|
||||
#endif /* EHVIEWER_EHVIEWER_H */
|
||||
@@ -0,0 +1,16 @@
|
||||
#include <jni.h>
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_Native_getFd(JNIEnv *env, jclass clazz, jobject fileDescriptor) {
|
||||
jint fd = -1;
|
||||
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
|
||||
|
||||
if (fdClass != NULL) {
|
||||
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
|
||||
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
|
||||
fd = (*env)->GetIntField(env, fileDescriptor, fdClassDescriptorFieldID);
|
||||
}
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
+12
-50
@@ -26,8 +26,6 @@
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#include "image.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__)
|
||||
@@ -38,17 +36,7 @@
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static void *tile_buffer = NULL;
|
||||
|
||||
bool image_onLoad() {
|
||||
tile_buffer = malloc(IMAGE_TILE_MAX_SIZE * 4);
|
||||
return tile_buffer != NULL;
|
||||
}
|
||||
|
||||
void image_onUnload() {
|
||||
free(tile_buffer);
|
||||
tile_buffer = NULL;
|
||||
}
|
||||
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
|
||||
|
||||
typedef struct {
|
||||
AImageDecoder *decoder;
|
||||
@@ -84,11 +72,6 @@ static void recycle(IMAGE *image) {
|
||||
free(image);
|
||||
}
|
||||
|
||||
typedef struct Memarea {
|
||||
void *buffer;
|
||||
long size;
|
||||
} Memarea;
|
||||
|
||||
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
@@ -159,17 +142,17 @@ bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
|
||||
|
||||
// First line
|
||||
dst_pos += (int)dst_blank_length;
|
||||
dst_pos += (int) dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int)line_stride;
|
||||
dst_pos += (int) line_stride;
|
||||
src_pos += src_stride;
|
||||
|
||||
// Other lines
|
||||
dst_blank_length = (size_t) ((dst_w - width) * 4);
|
||||
for (line = 1; line < height; line++) {
|
||||
dst_pos += (int)dst_blank_length;
|
||||
dst_pos += (int) dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int)line_stride;
|
||||
dst_pos += (int) line_stride;
|
||||
src_pos += src_stride;
|
||||
}
|
||||
|
||||
@@ -216,9 +199,9 @@ IMAGE *createFromFd(int fd) {
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *createFromAddr(void *addr, long size) {
|
||||
IMAGE *createFromAddr(void *addr, long long size) {
|
||||
AImageDecoder *decoder;
|
||||
AImageDecoder_createFromBuffer((const void *) addr, size, &decoder);
|
||||
AImageDecoder_createFromBuffer((const void *) addr + sizeof(long long), size, &decoder);
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
||||
src->decoder = decoder;
|
||||
@@ -263,32 +246,12 @@ void advance(IMAGE *image) {
|
||||
}
|
||||
}
|
||||
|
||||
static int jniGetFDFromFileDescriptor(JNIEnv *env, jobject fileDescriptor) {
|
||||
jint fd = -1;
|
||||
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
|
||||
|
||||
if (fdClass != NULL) {
|
||||
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
|
||||
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
|
||||
fd = (*env)->GetIntField(env, fileDescriptor, fdClassDescriptorFieldID);
|
||||
}
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecodeFdInt(JNIEnv *env, jclass clazz, jint fd) {
|
||||
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jint fd) {
|
||||
IMAGE *image = createFromFd(fd);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jobject fd) {
|
||||
return Java_com_hippo_image_Image_nativeDecodeFdInt(env, clazz,
|
||||
jniGetFDFromFileDescriptor(env, fd));
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeCreate(JNIEnv *env, jclass clazz, jobject bitmap) {
|
||||
AndroidBitmapInfo info;
|
||||
@@ -298,7 +261,7 @@ Java_com_hippo_image_Image_nativeCreate(JNIEnv *env, jclass clazz, jobject bitma
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
|
||||
image = create((int)info.width, (int)info.height, pixels);
|
||||
image = create((int) info.width, (int) info.height, pixels);
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
return (jlong) image;
|
||||
}
|
||||
@@ -313,7 +276,8 @@ Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jlong ptr, ji
|
||||
AndroidBitmap_getInfo(env, dst, &info);
|
||||
AndroidBitmap_lockPixels(env, dst, &pixels);
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, pixels, (int)info.width, (int)info.height, dst_x, dst_y, width,
|
||||
render((IMAGE *) ptr, src_x, src_y, pixels, (int) info.width, (int) info.height, dst_x, dst_y,
|
||||
width,
|
||||
height);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, dst);
|
||||
@@ -358,9 +322,7 @@ Java_com_hippo_image_Image_nativeRecycle(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecodeAddr(JNIEnv *env, jclass clazz, jlong addr) {
|
||||
Memarea *memarea = (Memarea *) addr;
|
||||
IMAGE *image = createFromAddr(memarea->buffer, memarea->size);
|
||||
free(memarea);
|
||||
IMAGE *image = createFromAddr((void *) addr, *(long long *) addr);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +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/>.
|
||||
*/
|
||||
|
||||
#ifndef IMAGE_IMAGE_H
|
||||
#define IMAGE_IMAGE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool image_onLoad();
|
||||
|
||||
void image_onUnload();
|
||||
|
||||
#endif /* IMAGE_IMAGE_H */
|
||||
@@ -1,65 +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 <jni.h>
|
||||
#include <archive.h>
|
||||
#include <lzma.h>
|
||||
#include <zlib.h>
|
||||
#include <nettle/config.h>
|
||||
|
||||
#include "image.h"
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env;
|
||||
|
||||
if ((*vm)->GetEnv(vm, (void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
if (!image_onLoad(vm)) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
image_onUnload();
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_Native_getlibarchiveVersion(JNIEnv *env, jclass clazz) {
|
||||
return (*env)->NewStringUTF(env, ARCHIVE_VERSION_STRING);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_Native_getliblzmaVersion(JNIEnv *env, jclass clazz) {
|
||||
return (*env)->NewStringUTF(env, lzma_version_string());
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_Native_getzlibVersion(JNIEnv *env, jclass clazz) {
|
||||
return (*env)->NewStringUTF(env, ZLIB_VERSION);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_Native_getnettleVersion(JNIEnv *env, jclass clazz) {
|
||||
return (*env)->NewStringUTF(env, PACKAGE_STRING);
|
||||
}
|
||||
@@ -15,20 +15,16 @@
|
||||
* 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
|
||||
|
||||
package com.hippo;
|
||||
import java.io.FileDescriptor
|
||||
|
||||
public class Native {
|
||||
|
||||
public static void initialize() {
|
||||
System.loadLibrary("ehviewer");
|
||||
object Native {
|
||||
@JvmStatic
|
||||
fun initialize() {
|
||||
System.loadLibrary("ehviewer")
|
||||
}
|
||||
|
||||
public static native String getlibarchiveVersion();
|
||||
|
||||
public static native String getliblzmaVersion();
|
||||
|
||||
public static native String getzlibVersion();
|
||||
|
||||
public static native String getnettleVersion();
|
||||
}
|
||||
@JvmStatic
|
||||
external fun getFd(fd: FileDescriptor?): Int
|
||||
}
|
||||
@@ -1,54 +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;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
public class UriArchiveAccessor {
|
||||
ParcelFileDescriptor pfd;
|
||||
Context ctx;
|
||||
Uri uri;
|
||||
|
||||
public UriArchiveAccessor(Context ctx, Uri uri) {
|
||||
this.ctx = ctx;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public int open() throws Exception {
|
||||
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
|
||||
return openArchive(pfd.getFd(), pfd.getStatSize());
|
||||
}
|
||||
|
||||
private native int openArchive(int fd, long size);
|
||||
|
||||
public native long extracttoOutputStream(int index);
|
||||
|
||||
public native boolean needPassword();
|
||||
|
||||
public native boolean providePassword(String str);
|
||||
|
||||
private native void closeArchive();
|
||||
|
||||
public void close() throws Exception {
|
||||
closeArchive();
|
||||
pfd.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
open class BaseDialogBuilder constructor(
|
||||
context: Context
|
||||
) : MaterialAlertDialogBuilder(context) {
|
||||
override fun create(): AlertDialog {
|
||||
return super.create().apply {
|
||||
window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package com.hippo.app
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.CheckBox
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
class CheckBoxDialogBuilder constructor(
|
||||
@@ -26,7 +25,7 @@ class CheckBoxDialogBuilder constructor(
|
||||
message: String?,
|
||||
checkText: String?,
|
||||
checked: Boolean
|
||||
) : MaterialAlertDialogBuilder(
|
||||
) : BaseDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.TextView.OnEditorActionListener
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
@@ -31,7 +30,7 @@ class EditTextDialogBuilder constructor(
|
||||
context: Context,
|
||||
text: String?,
|
||||
hint: String?
|
||||
) : MaterialAlertDialogBuilder(
|
||||
) : BaseDialogBuilder(
|
||||
context
|
||||
), OnEditorActionListener {
|
||||
private val mTextInputLayout: TextInputLayout
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.HapticFeedbackConstants
|
||||
import com.google.android.material.slider.Slider
|
||||
|
||||
class HapticSlider @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Slider(context, attrs) {
|
||||
init {
|
||||
addOnChangeListener { _, _, _ -> performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) }
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,13 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.CheckBox
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
|
||||
class ListCheckBoxDialogBuilder constructor(
|
||||
context: Context, items: Array<CharSequence>,
|
||||
listener: OnItemClickListener?, checkText: String?, checked: Boolean
|
||||
) : MaterialAlertDialogBuilder(
|
||||
) : BaseDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
|
||||
@@ -231,12 +231,6 @@ public final class DiskLruCache implements Closeable {
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
@@ -253,6 +247,12 @@ public final class DiskLruCache implements Closeable {
|
||||
}
|
||||
};
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
|
||||
if (deleteDestination) {
|
||||
deleteIfExists(to);
|
||||
|
||||
@@ -51,7 +51,6 @@ public final class ConacoTask<V> {
|
||||
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
|
||||
private final String mKey;
|
||||
private final String mUrl;
|
||||
private final DataContainer mDataContainer;
|
||||
private final boolean mUseMemoryCache;
|
||||
private final boolean mUseDiskCache;
|
||||
private final boolean mUseNetwork;
|
||||
@@ -73,7 +72,6 @@ public final class ConacoTask<V> {
|
||||
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
|
||||
mKey = builder.mKey;
|
||||
mUrl = builder.mUrl;
|
||||
mDataContainer = builder.mDataContainer;
|
||||
mUseMemoryCache = builder.mUseMemoryCache;
|
||||
mUseDiskCache = builder.mUseDiskCache;
|
||||
mUseNetwork = builder.mUseNetwork;
|
||||
@@ -85,42 +83,6 @@ public final class ConacoTask<V> {
|
||||
mConaco = builder.mConaco;
|
||||
}
|
||||
|
||||
private static void putFromDiskCacheToDataContainer(String key, ValueCache cache, DataContainer container) {
|
||||
SimpleDiskCache diskCache = cache.getDiskCache();
|
||||
if (diskCache != null) {
|
||||
InputStreamPipe pipe = diskCache.getInputStreamPipe(key);
|
||||
if (pipe != null) {
|
||||
try {
|
||||
pipe.obtain();
|
||||
container.save(pipe.open(), -1L, null, null);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Can't save value from disk cache to data container");
|
||||
e.printStackTrace();
|
||||
container.remove();
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void putFromDataContainerToDiskCache(String key, ValueCache cache, DataContainer container) {
|
||||
InputStreamPipe pipe = container.get();
|
||||
if (pipe != null) {
|
||||
try {
|
||||
pipe.obtain();
|
||||
cache.putRawToDisk(key, pipe.open());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Can't save value from data container to disk cache", e);
|
||||
cache.removeFromDisk(key);
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getId() {
|
||||
return mId;
|
||||
}
|
||||
@@ -194,7 +156,6 @@ public final class ConacoTask<V> {
|
||||
} else if (mNetworkLoadTask != null) { // Getting from network
|
||||
mNetworkLoadTask.cancel(false);
|
||||
if (mCall != null) {
|
||||
mCall.cancel();
|
||||
mCall = null;
|
||||
}
|
||||
}
|
||||
@@ -219,7 +180,6 @@ public final class ConacoTask<V> {
|
||||
private Unikery<T> mUnikery;
|
||||
private String mKey;
|
||||
private String mUrl;
|
||||
private DataContainer mDataContainer;
|
||||
private boolean mUseMemoryCache = true;
|
||||
private boolean mUseDiskCache = true;
|
||||
private boolean mUseNetwork = true;
|
||||
@@ -262,11 +222,6 @@ public final class ConacoTask<V> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setDataContainer(DataContainer dataContainer) {
|
||||
mDataContainer = dataContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseMemoryCache() {
|
||||
return mUseMemoryCache;
|
||||
}
|
||||
@@ -332,7 +287,7 @@ public final class ConacoTask<V> {
|
||||
if (mUnikery == null) {
|
||||
throw new IllegalStateException("Must set unikery");
|
||||
}
|
||||
if (mKey == null && mUrl == null && mDataContainer == null) {
|
||||
if (mKey == null && mUrl == null) {
|
||||
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
|
||||
}
|
||||
}
|
||||
@@ -352,26 +307,13 @@ public final class ConacoTask<V> {
|
||||
} else {
|
||||
V value = null;
|
||||
|
||||
// First check data container
|
||||
if (mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
InputStreamPipe isp = mDataContainer.get();
|
||||
if (isp != null) {
|
||||
value = mHelper.decode(isp);
|
||||
}
|
||||
}
|
||||
|
||||
// Then check disk cache
|
||||
if (mKey != null) {
|
||||
if (value == null && mUseDiskCache) {
|
||||
value = mCache.getFromDisk(mKey);
|
||||
// Put back to data container
|
||||
if (value != null && mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
putFromDiskCacheToDataContainer(mKey, mCache, mDataContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
}
|
||||
@@ -451,18 +393,6 @@ public final class ConacoTask<V> {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean putToDataContainer(InputStream is, ResponseBody body) {
|
||||
// Get media type
|
||||
String mediaType;
|
||||
MediaType mt = body.contentType();
|
||||
if (mt != null) {
|
||||
mediaType = mt.type() + '/' + mt.subtype();
|
||||
} else {
|
||||
mediaType = null;
|
||||
}
|
||||
return mDataContainer.save(is, body.contentLength(), mediaType, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected V doInBackground(Void... params) {
|
||||
if (isNotNecessary(this)) {
|
||||
@@ -489,7 +419,7 @@ public final class ConacoTask<V> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((mDataContainer == null || !mDataContainer.isEnabled()) && mKey != null) {
|
||||
if (mKey != null) {
|
||||
if (putToDiskCache(is, body.contentLength())) {
|
||||
// Get object from disk cache
|
||||
value = mCache.getFromDisk(mKey);
|
||||
@@ -506,37 +436,6 @@ public final class ConacoTask<V> {
|
||||
mCache.removeFromDisk(mKey);
|
||||
return null;
|
||||
}
|
||||
} else if (mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
// Check url Moved
|
||||
HttpUrl requestHttpUrl = request.url();
|
||||
HttpUrl responseHttpUrl = response.request().url();
|
||||
if (!responseHttpUrl.equals(requestHttpUrl)) {
|
||||
mDataContainer.onUrlMoved(mUrl, responseHttpUrl.url().toString());
|
||||
}
|
||||
|
||||
// Put to data container
|
||||
if (!putToDataContainer(is, body)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get value from data container
|
||||
InputStreamPipe isp = mDataContainer.get();
|
||||
if (isp == null) {
|
||||
return null;
|
||||
}
|
||||
value = mHelper.decode(isp);
|
||||
if (value == null) {
|
||||
mDataContainer.remove();
|
||||
} else if (mKey != null) {
|
||||
// Put to disk cache
|
||||
putFromDataContainerToDiskCache(mKey, mCache, mDataContainer);
|
||||
|
||||
if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface DataContainer {
|
||||
|
||||
/**
|
||||
* Returns the enabled status for this DataContainer.
|
||||
*/
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Get 304 or something like that
|
||||
*/
|
||||
void onUrlMoved(String requestUrl, String responseUrl);
|
||||
|
||||
/**
|
||||
* Save the {@code InputStream}
|
||||
*/
|
||||
boolean save(InputStream is, long length, @Nullable String mediaType, @Nullable ProgressNotifier notify);
|
||||
|
||||
/**
|
||||
* Get {@code InputStreamPipe} for saved before
|
||||
*/
|
||||
@Nullable
|
||||
InputStreamPipe get();
|
||||
|
||||
/**
|
||||
* Remove saved stuff
|
||||
*/
|
||||
void remove();
|
||||
}
|
||||
@@ -44,8 +44,8 @@ import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.Native;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
@@ -68,7 +68,6 @@ 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;
|
||||
@@ -110,6 +109,7 @@ public class EhApplication extends SceneApplication {
|
||||
private static final long DEBUG_PRINT_INTERVAL = 3000L;
|
||||
|
||||
public static boolean locked = true;
|
||||
public static long locked_last_leave_time = 0;
|
||||
|
||||
private static EhApplication instance;
|
||||
|
||||
@@ -328,7 +328,6 @@ public class EhApplication extends SceneApplication {
|
||||
super.onCreate();
|
||||
|
||||
Native.initialize();
|
||||
ClipboardUtil.initialize(this);
|
||||
GetText.initialize(this);
|
||||
StatusCodeException.initialize(this);
|
||||
Settings.initialize(this);
|
||||
@@ -430,7 +429,7 @@ public class EhApplication extends SceneApplication {
|
||||
Activity activity = getTopActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(activity)
|
||||
AlertDialog dialog = new BaseDialogBuilder(activity)
|
||||
.setMessage(Html.fromHtml(html))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
@@ -591,6 +590,8 @@ public class EhApplication extends SceneApplication {
|
||||
static class EhlifecycleObserver implements DefaultLifecycleObserver {
|
||||
@Override
|
||||
public void onPause(@NonNull LifecycleOwner owner) {
|
||||
if (!locked)
|
||||
locked_last_leave_time = System.currentTimeMillis() / 1000;
|
||||
locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,60 +490,73 @@ public class EhDB {
|
||||
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
|
||||
.createFromFile(file).allowMainThreadQueries().build();
|
||||
|
||||
// Download label
|
||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
||||
try {
|
||||
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// Downloads
|
||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
|
||||
// Download label
|
||||
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
try {
|
||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// Download dirname
|
||||
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
try {
|
||||
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// History
|
||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
try {
|
||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// QuickSearch
|
||||
List<QuickSearch> quickSearchList = oldRoomDatabase.quickSearchDao().list();
|
||||
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
|
||||
List<QuickSearch> importList = new ArrayList<>();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
String name = quickSearch.name;
|
||||
for (QuickSearch q : currentQuickSearchList) {
|
||||
if (ObjectUtils.equal(q.name, name)) {
|
||||
// The same name
|
||||
name = null;
|
||||
break;
|
||||
try {
|
||||
List<QuickSearch> quickSearchList = oldRoomDatabase.quickSearchDao().list();
|
||||
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
|
||||
List<QuickSearch> importList = new ArrayList<>();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
String name = quickSearch.name;
|
||||
for (QuickSearch q : currentQuickSearchList) {
|
||||
if (ObjectUtils.equal(q.name, name)) {
|
||||
// The same name
|
||||
name = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null == name) {
|
||||
continue;
|
||||
}
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
if (null == name) {
|
||||
continue;
|
||||
}
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
importQuickSearch(importList);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// LocalFavorites
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
try {
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// Filter
|
||||
List<Filter> filterList = oldRoomDatabase.filterDao().list();
|
||||
List<Filter> currentFilterList = db.filterDao().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
try {
|
||||
List<Filter> filterList = oldRoomDatabase.filterDao().list();
|
||||
List<Filter> currentFilterList = db.filterDao().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -50,6 +50,8 @@ public class Settings {
|
||||
********************/
|
||||
|
||||
public static final String KEY_THEME = "theme";
|
||||
|
||||
public static final String KEY_ACCOUNT = "account";
|
||||
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
|
||||
public static final int THEME_LIGHT = 1;
|
||||
public static final int THEME_SYSTEM = -1;
|
||||
@@ -90,6 +92,7 @@ public class Settings {
|
||||
********************/
|
||||
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
|
||||
public static final String KEY_SECURITY = "require_unlock";
|
||||
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
|
||||
public static final String DEFAULT_SECURITY = "";
|
||||
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
|
||||
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
|
||||
@@ -97,13 +100,16 @@ public class Settings {
|
||||
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts_2";
|
||||
public static final String KEY_DOMAIN_FRONTING = "domain_fronting";
|
||||
public static final String KEY_APP_LANGUAGE = "app_language";
|
||||
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
|
||||
private static final String TAG = Settings.class.getSimpleName();
|
||||
private static final String KEY_VERSION_CODE = "version_code";
|
||||
private static final int DEFAULT_VERSION_CODE = 0;
|
||||
private static final String KEY_DISPLAY_NAME = "display_name";
|
||||
private static final String DEFAULT_DISPLAY_NAME = null;
|
||||
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
|
||||
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
|
||||
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||
private static final String KEY_AVATAR = "avatar";
|
||||
private static final String DEFAULT_AVATAR = null;
|
||||
private static final String KEY_SHOW_WARNING = "show_warning";
|
||||
@@ -276,9 +282,11 @@ public class Settings {
|
||||
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
|
||||
private static final String KEY_REQUEST_NEWS = "request_news";
|
||||
private static final boolean DEFAULT_REQUEST_NEWS = true;
|
||||
|
||||
|
||||
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
|
||||
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
|
||||
|
||||
private static Context sContext;
|
||||
private static SharedPreferences sSettingsPre;
|
||||
private static EhConfig sEhConfig;
|
||||
@@ -428,6 +436,7 @@ public class Settings {
|
||||
putInt(KEY_VERSION_CODE, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getDisplayName() {
|
||||
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
|
||||
}
|
||||
@@ -1058,6 +1067,9 @@ public class Settings {
|
||||
public static void putSecurity(boolean value) {
|
||||
putBoolean(KEY_SECURITY, value);
|
||||
}
|
||||
public static int getSecurityDelay() {
|
||||
return getInt(KEY_SECURITY_DELAY, 0);
|
||||
}
|
||||
|
||||
public static boolean getEnableFingerprint() {
|
||||
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
@@ -94,6 +95,7 @@ public class EhUtils {
|
||||
return CATEGORY_VALUES[i];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getCategory(int type) {
|
||||
int i;
|
||||
for (i = 0; i < CATEGORY_VALUES.length - 1; i++) {
|
||||
|
||||
@@ -19,12 +19,13 @@ package com.hippo.ehviewer.client.data;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.network.UrlBuilder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FavListUrlBuilder implements Parcelable {
|
||||
|
||||
@@ -43,7 +44,9 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
}
|
||||
};
|
||||
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
|
||||
private int mIndex;
|
||||
private String mPrev;
|
||||
private String mNext;
|
||||
private String mJumpTo;
|
||||
private String mKeyword;
|
||||
private int mFavCat = FAV_CAT_ALL;
|
||||
|
||||
@@ -51,7 +54,9 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
}
|
||||
|
||||
protected FavListUrlBuilder(Parcel in) {
|
||||
this.mIndex = in.readInt();
|
||||
this.mPrev = in.readString();
|
||||
this.mNext = in.readString();
|
||||
this.mJumpTo = in.readString();
|
||||
this.mKeyword = in.readString();
|
||||
this.mFavCat = in.readInt();
|
||||
}
|
||||
@@ -60,8 +65,16 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
return favCat >= 0 && favCat <= 9;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
mIndex = index;
|
||||
public void setPrev(String prev) {
|
||||
mPrev = prev;
|
||||
}
|
||||
|
||||
public void setNext(String next) {
|
||||
mNext = next;
|
||||
}
|
||||
|
||||
public void setJumpTo(String jumpTo) {
|
||||
mJumpTo = jumpTo;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
@@ -92,16 +105,26 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
ub.addQuery("favcat", "all");
|
||||
}
|
||||
if (!TextUtils.isEmpty(mKeyword)) {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||
// Name
|
||||
ub.addQuery("sn", "on");
|
||||
// Tags
|
||||
ub.addQuery("st", "on");
|
||||
// Note
|
||||
ub.addQuery("sf", "on");
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"));
|
||||
// Name
|
||||
ub.addQuery("sn", "on");
|
||||
// Tags
|
||||
ub.addQuery("st", "on");
|
||||
// Note
|
||||
ub.addQuery("sf", "on");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "Can't URLEncoder.encode " + mKeyword);
|
||||
}
|
||||
}
|
||||
if (mIndex > 0) {
|
||||
ub.addQuery("page", Integer.toString(mIndex));
|
||||
if (mPrev != null && !mPrev.isEmpty()) {
|
||||
ub.addQuery("prev", mPrev);
|
||||
}
|
||||
if (mNext != null && !mNext.isEmpty()) {
|
||||
ub.addQuery("next", mNext);
|
||||
}
|
||||
if (mJumpTo != null && !mJumpTo.isEmpty()) {
|
||||
ub.addQuery("seek", mJumpTo);
|
||||
}
|
||||
return ub.build();
|
||||
}
|
||||
@@ -113,7 +136,9 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mIndex);
|
||||
dest.writeString(this.mPrev);
|
||||
dest.writeString(this.mNext);
|
||||
dest.writeString(this.mJumpTo);
|
||||
dest.writeString(this.mKeyword);
|
||||
dest.writeInt(this.mFavCat);
|
||||
}
|
||||
|
||||
@@ -1,620 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.client.EhConfig;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.dao.QuickSearch;
|
||||
import com.hippo.ehviewer.widget.AdvanceSearchTable;
|
||||
import com.hippo.network.UrlBuilder;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
|
||||
// Mode
|
||||
public static final int MODE_NORMAL = 0x0;
|
||||
public static final int MODE_UPLOADER = 0x1;
|
||||
public static final int MODE_TAG = 0x2;
|
||||
public static final int MODE_WHATS_HOT = 0x3;
|
||||
public static final int MODE_IMAGE_SEARCH = 0x4;
|
||||
public static final int MODE_SUBSCRIPTION = 0x5;
|
||||
public static final int MODE_TOPLIST = 0x6;
|
||||
public static final int DEFAULT_ADVANCE = AdvanceSearchTable.SNAME | AdvanceSearchTable.STAGS;
|
||||
public static final int DEFAULT_MIN_RATING = 2;
|
||||
public static final Creator<ListUrlBuilder> CREATOR = new Creator<ListUrlBuilder>() {
|
||||
@Override
|
||||
public ListUrlBuilder createFromParcel(Parcel source) {
|
||||
return new ListUrlBuilder(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListUrlBuilder[] newArray(int size) {
|
||||
return new ListUrlBuilder[size];
|
||||
}
|
||||
};
|
||||
@Mode
|
||||
private int mMode = MODE_NORMAL;
|
||||
|
||||
private int mPageIndex = 0;
|
||||
|
||||
private int mCategory = EhUtils.NONE;
|
||||
private String mKeyword = null;
|
||||
private String mSHash = null;
|
||||
|
||||
private int mAdvanceSearch = -1;
|
||||
private int mMinRating = -1;
|
||||
private int mPageFrom = -1;
|
||||
private int mPageTo = -1;
|
||||
|
||||
private String mImagePath;
|
||||
private boolean mUseSimilarityScan;
|
||||
private boolean mOnlySearchCovers;
|
||||
private boolean mShowExpunged;
|
||||
|
||||
public ListUrlBuilder() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("WrongConstant")
|
||||
protected ListUrlBuilder(Parcel in) {
|
||||
this.mMode = in.readInt();
|
||||
this.mPageIndex = in.readInt();
|
||||
this.mCategory = in.readInt();
|
||||
this.mKeyword = in.readString();
|
||||
this.mAdvanceSearch = in.readInt();
|
||||
this.mMinRating = in.readInt();
|
||||
this.mPageFrom = in.readInt();
|
||||
this.mPageTo = in.readInt();
|
||||
this.mImagePath = in.readString();
|
||||
this.mUseSimilarityScan = in.readByte() != 0;
|
||||
this.mOnlySearchCovers = in.readByte() != 0;
|
||||
this.mShowExpunged = in.readByte() != 0;
|
||||
this.mSHash = in.readString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this ListUrlBuilder point to homepage
|
||||
*/
|
||||
public void reset() {
|
||||
mMode = MODE_NORMAL;
|
||||
mPageIndex = 0;
|
||||
mCategory = EhUtils.NONE;
|
||||
mKeyword = null;
|
||||
mAdvanceSearch = -1;
|
||||
mMinRating = -1;
|
||||
mPageFrom = -1;
|
||||
mPageTo = -1;
|
||||
mImagePath = null;
|
||||
mUseSimilarityScan = false;
|
||||
mOnlySearchCovers = false;
|
||||
mShowExpunged = false;
|
||||
mSHash = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListUrlBuilder clone() {
|
||||
try {
|
||||
return (ListUrlBuilder) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Mode
|
||||
public int getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
public void setMode(@Mode int mode) {
|
||||
mMode = mode;
|
||||
}
|
||||
|
||||
public int getPageIndex() {
|
||||
return mPageIndex;
|
||||
}
|
||||
|
||||
public void setPageIndex(int pageIndex) {
|
||||
mPageIndex = pageIndex;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return mCategory;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
mCategory = category;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return MODE_UPLOADER == mMode ? "uploader:" + mKeyword : mKeyword;
|
||||
}
|
||||
|
||||
public void setKeyword(String keyword) {
|
||||
mKeyword = keyword;
|
||||
}
|
||||
|
||||
public int getAdvanceSearch() {
|
||||
return mAdvanceSearch;
|
||||
}
|
||||
|
||||
public void setAdvanceSearch(int advanceSearch) {
|
||||
mAdvanceSearch = advanceSearch;
|
||||
}
|
||||
|
||||
public int getMinRating() {
|
||||
return mMinRating;
|
||||
}
|
||||
|
||||
public void setMinRating(int minRating) {
|
||||
mMinRating = minRating;
|
||||
}
|
||||
|
||||
public int getPageFrom() {
|
||||
return mPageFrom;
|
||||
}
|
||||
|
||||
public void setPageFrom(int pageFrom) {
|
||||
mPageFrom = pageFrom;
|
||||
}
|
||||
|
||||
public int getPageTo() {
|
||||
return mPageTo;
|
||||
}
|
||||
|
||||
public void setPageTo(int pageTo) {
|
||||
mPageTo = pageTo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getImagePath() {
|
||||
return mImagePath;
|
||||
}
|
||||
|
||||
public void setImagePath(String imagePath) {
|
||||
mImagePath = imagePath;
|
||||
}
|
||||
|
||||
public boolean isUseSimilarityScan() {
|
||||
return mUseSimilarityScan;
|
||||
}
|
||||
|
||||
public void setUseSimilarityScan(boolean useSimilarityScan) {
|
||||
mUseSimilarityScan = useSimilarityScan;
|
||||
}
|
||||
|
||||
public boolean isOnlySearchCovers() {
|
||||
return mOnlySearchCovers;
|
||||
}
|
||||
|
||||
public void setOnlySearchCovers(boolean onlySearchCovers) {
|
||||
mOnlySearchCovers = onlySearchCovers;
|
||||
}
|
||||
|
||||
public boolean isShowExpunged() {
|
||||
return mShowExpunged;
|
||||
}
|
||||
|
||||
public void setShowExpunged(boolean showExpunged) {
|
||||
mShowExpunged = showExpunged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make them the same
|
||||
*
|
||||
* @param lub The template
|
||||
*/
|
||||
public void set(ListUrlBuilder lub) {
|
||||
mMode = lub.mMode;
|
||||
mPageIndex = lub.mPageIndex;
|
||||
mCategory = lub.mCategory;
|
||||
mKeyword = lub.mKeyword;
|
||||
mAdvanceSearch = lub.mAdvanceSearch;
|
||||
mMinRating = lub.mMinRating;
|
||||
mPageFrom = lub.mPageFrom;
|
||||
mPageTo = lub.mPageTo;
|
||||
mImagePath = lub.mImagePath;
|
||||
mUseSimilarityScan = lub.mUseSimilarityScan;
|
||||
mOnlySearchCovers = lub.mOnlySearchCovers;
|
||||
mShowExpunged = lub.mShowExpunged;
|
||||
mSHash = lub.mSHash;
|
||||
}
|
||||
|
||||
public void set(QuickSearch q) {
|
||||
mMode = q.mode;
|
||||
mCategory = q.category;
|
||||
mKeyword = q.keyword;
|
||||
mAdvanceSearch = q.advanceSearch;
|
||||
mMinRating = q.minRating;
|
||||
mPageFrom = q.pageFrom;
|
||||
mPageTo = q.pageTo;
|
||||
mImagePath = null;
|
||||
mUseSimilarityScan = false;
|
||||
mOnlySearchCovers = false;
|
||||
mShowExpunged = false;
|
||||
}
|
||||
|
||||
public QuickSearch toQuickSearch() {
|
||||
QuickSearch q = new QuickSearch();
|
||||
q.mode = mMode;
|
||||
q.category = mCategory;
|
||||
q.keyword = mKeyword;
|
||||
q.advanceSearch = mAdvanceSearch;
|
||||
q.minRating = mMinRating;
|
||||
q.pageFrom = mPageFrom;
|
||||
q.pageTo = mPageTo;
|
||||
return q;
|
||||
}
|
||||
|
||||
public boolean equalsQuickSearch(QuickSearch q) {
|
||||
if (null == q) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (q.mode != mMode) {
|
||||
return false;
|
||||
}
|
||||
if (q.category != mCategory) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.equals(q.keyword, mKeyword)) {
|
||||
return false;
|
||||
}
|
||||
if (q.advanceSearch != mAdvanceSearch) {
|
||||
return false;
|
||||
}
|
||||
if (q.minRating != mMinRating) {
|
||||
return false;
|
||||
}
|
||||
if (q.pageFrom != mPageFrom) {
|
||||
return false;
|
||||
}
|
||||
return q.pageTo == mPageTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param query xxx=yyy&mmm=nnn
|
||||
*/
|
||||
// TODO page
|
||||
public void setQuery(String query) {
|
||||
reset();
|
||||
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
return;
|
||||
}
|
||||
String[] querys = StringUtils.split(query, '&');
|
||||
int category = 0;
|
||||
String keyword = null;
|
||||
boolean enableAdvanceSearch = false;
|
||||
int advanceSearch = 0;
|
||||
boolean enableMinRating = false;
|
||||
int minRating = -1;
|
||||
boolean enablePage = false;
|
||||
int pageFrom = -1;
|
||||
int pageTo = -1;
|
||||
for (String str : querys) {
|
||||
int index = str.indexOf('=');
|
||||
if (index < 0) {
|
||||
continue;
|
||||
}
|
||||
String key = str.substring(0, index);
|
||||
String value = str.substring(index + 1);
|
||||
switch (key) {
|
||||
case "f_cats":
|
||||
int cats = NumberUtils.parseIntSafely(value, EhConfig.ALL_CATEGORY);
|
||||
category |= (~cats) & EhConfig.ALL_CATEGORY;
|
||||
break;
|
||||
case "f_doujinshi":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.DOUJINSHI;
|
||||
}
|
||||
break;
|
||||
case "f_manga":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.MANGA;
|
||||
}
|
||||
break;
|
||||
case "f_artistcg":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.ARTIST_CG;
|
||||
}
|
||||
break;
|
||||
case "f_gamecg":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.GAME_CG;
|
||||
}
|
||||
break;
|
||||
case "f_western":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.WESTERN;
|
||||
}
|
||||
break;
|
||||
case "f_non-h":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.NON_H;
|
||||
}
|
||||
break;
|
||||
case "f_imageset":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.IMAGE_SET;
|
||||
}
|
||||
break;
|
||||
case "f_cosplay":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.COSPLAY;
|
||||
}
|
||||
break;
|
||||
case "f_asianporn":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.ASIAN_PORN;
|
||||
}
|
||||
break;
|
||||
case "f_misc":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.MISC;
|
||||
}
|
||||
break;
|
||||
case "f_search":
|
||||
try {
|
||||
keyword = URLDecoder.decode(value, StandardCharsets.UTF_8);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Ignore
|
||||
}
|
||||
break;
|
||||
case "advsearch":
|
||||
if ("1".equals(value)) {
|
||||
enableAdvanceSearch = true;
|
||||
}
|
||||
break;
|
||||
case "f_sname":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SNAME;
|
||||
}
|
||||
break;
|
||||
case "f_stags":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.STAGS;
|
||||
}
|
||||
break;
|
||||
case "f_sdesc":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SDESC;
|
||||
}
|
||||
break;
|
||||
case "f_storr":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.STORR;
|
||||
}
|
||||
break;
|
||||
case "f_sto":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.STO;
|
||||
}
|
||||
break;
|
||||
case "f_sdt1":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SDT1;
|
||||
}
|
||||
break;
|
||||
case "f_sdt2":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SDT2;
|
||||
}
|
||||
break;
|
||||
case "f_sh":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SH;
|
||||
}
|
||||
break;
|
||||
case "f_sfl":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SFL;
|
||||
}
|
||||
break;
|
||||
case "f_sfu":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SFU;
|
||||
}
|
||||
break;
|
||||
case "f_sft":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SFT;
|
||||
}
|
||||
break;
|
||||
case "f_sr":
|
||||
if ("on".equals(value)) {
|
||||
enableMinRating = true;
|
||||
}
|
||||
break;
|
||||
case "f_srdd":
|
||||
minRating = NumberUtils.parseIntSafely(value, -1);
|
||||
break;
|
||||
case "f_sp":
|
||||
if ("on".equals(value)) {
|
||||
enablePage = true;
|
||||
}
|
||||
break;
|
||||
case "f_spf":
|
||||
pageFrom = NumberUtils.parseIntSafely(value, -1);
|
||||
break;
|
||||
case "f_spt":
|
||||
pageTo = NumberUtils.parseIntSafely(value, -1);
|
||||
break;
|
||||
case "f_shash":
|
||||
mSHash = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mCategory = category;
|
||||
mKeyword = keyword;
|
||||
if (enableAdvanceSearch) {
|
||||
mAdvanceSearch = advanceSearch;
|
||||
if (enableMinRating) {
|
||||
mMinRating = minRating;
|
||||
} else {
|
||||
mMinRating = -1;
|
||||
}
|
||||
if (enablePage) {
|
||||
mPageFrom = pageFrom;
|
||||
mPageTo = pageTo;
|
||||
} else {
|
||||
mPageFrom = -1;
|
||||
mPageTo = -1;
|
||||
}
|
||||
} else {
|
||||
mAdvanceSearch = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public String build() {
|
||||
switch (mMode) {
|
||||
default:
|
||||
case MODE_NORMAL:
|
||||
case MODE_SUBSCRIPTION: {
|
||||
String url;
|
||||
if (mMode == MODE_NORMAL) {
|
||||
url = EhUrl.getHost();
|
||||
} else {
|
||||
url = EhUrl.getWatchedUrl();
|
||||
}
|
||||
|
||||
UrlBuilder ub = new UrlBuilder(url);
|
||||
if (mCategory != EhUtils.NONE) {
|
||||
ub.addQuery("f_cats", (~mCategory) & EhConfig.ALL_CATEGORY);
|
||||
}
|
||||
// Search key
|
||||
if (mKeyword != null) {
|
||||
String keyword = mKeyword.trim();
|
||||
if (!keyword.isEmpty()) {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash);
|
||||
}
|
||||
// Page index
|
||||
if (mPageIndex != 0) {
|
||||
ub.addQuery("page", mPageIndex);
|
||||
}
|
||||
// Advance search
|
||||
if (mAdvanceSearch != -1) {
|
||||
ub.addQuery("advsearch", "1");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SNAME) != 0)
|
||||
ub.addQuery("f_sname", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.STAGS) != 0)
|
||||
ub.addQuery("f_stags", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SDESC) != 0)
|
||||
ub.addQuery("f_sdesc", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.STORR) != 0)
|
||||
ub.addQuery("f_storr", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.STO) != 0) ub.addQuery("f_sto", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SDT1) != 0)
|
||||
ub.addQuery("f_sdt1", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SDT2) != 0)
|
||||
ub.addQuery("f_sdt2", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SH) != 0) ub.addQuery("f_sh", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SFL) != 0) ub.addQuery("f_sfl", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SFU) != 0) ub.addQuery("f_sfu", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SFT) != 0) ub.addQuery("f_sft", "on");
|
||||
// Set min star
|
||||
if (mMinRating != -1) {
|
||||
ub.addQuery("f_sr", "on");
|
||||
ub.addQuery("f_srdd", mMinRating);
|
||||
}
|
||||
// Pages
|
||||
if (mPageFrom != -1 || mPageTo != -1) {
|
||||
ub.addQuery("f_sp", "on");
|
||||
ub.addQuery("f_spf", mPageFrom != -1 ? Integer.toString(mPageFrom) : "");
|
||||
ub.addQuery("f_spt", mPageTo != -1 ? Integer.toString(mPageTo) : "");
|
||||
}
|
||||
}
|
||||
return ub.build();
|
||||
}
|
||||
case MODE_UPLOADER: {
|
||||
StringBuilder sb = new StringBuilder(EhUrl.getHost());
|
||||
sb.append("uploader/");
|
||||
sb.append(URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||
if (mPageIndex != 0) {
|
||||
sb.append('/').append(mPageIndex);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
case MODE_TAG: {
|
||||
StringBuilder sb = new StringBuilder(EhUrl.getHost());
|
||||
sb.append("tag/");
|
||||
sb.append(URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||
if (mPageIndex != 0) {
|
||||
sb.append('/').append(mPageIndex);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
case MODE_WHATS_HOT:
|
||||
return EhUrl.getPopularUrl();
|
||||
case MODE_IMAGE_SEARCH:
|
||||
return EhUrl.getImageSearchUrl();
|
||||
case MODE_TOPLIST:
|
||||
StringBuilder sb = new StringBuilder(EhUrl.HOST_E);
|
||||
sb.append("toplist.php?tl=");
|
||||
sb.append(URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||
if (mPageIndex != 0) {
|
||||
sb.append("&p=").append(mPageIndex);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mMode);
|
||||
dest.writeInt(this.mPageIndex);
|
||||
dest.writeInt(this.mCategory);
|
||||
dest.writeString(this.mKeyword);
|
||||
dest.writeInt(this.mAdvanceSearch);
|
||||
dest.writeInt(this.mMinRating);
|
||||
dest.writeInt(this.mPageFrom);
|
||||
dest.writeInt(this.mPageTo);
|
||||
dest.writeString(this.mImagePath);
|
||||
dest.writeByte(mUseSimilarityScan ? (byte) 1 : (byte) 0);
|
||||
dest.writeByte(mOnlySearchCovers ? (byte) 1 : (byte) 0);
|
||||
dest.writeByte(mShowExpunged ? (byte) 1 : (byte) 0);
|
||||
dest.writeString(this.mSHash);
|
||||
}
|
||||
|
||||
@IntDef({MODE_NORMAL, MODE_UPLOADER, MODE_TAG, MODE_WHATS_HOT, MODE_IMAGE_SEARCH, MODE_SUBSCRIPTION, MODE_TOPLIST})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Mode {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.Parcelable.Creator
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.IntDef
|
||||
import com.hippo.ehviewer.client.EhConfig
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.dao.QuickSearch
|
||||
import com.hippo.ehviewer.widget.AdvanceSearchTable
|
||||
import com.hippo.network.UrlBuilder
|
||||
import com.hippo.yorozuya.NumberUtils
|
||||
import com.hippo.yorozuya.StringUtils
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
|
||||
open class ListUrlBuilder : Cloneable, Parcelable {
|
||||
@get:Mode
|
||||
@Mode
|
||||
var mode = MODE_NORMAL
|
||||
private var mPrevGid = 0
|
||||
private var mNextGid = 0
|
||||
private var mJumpTo: String? = null
|
||||
var category = EhUtils.NONE
|
||||
private var mKeyword: String? = null
|
||||
private var mSHash: String? = null
|
||||
var advanceSearch = -1
|
||||
var minRating = -1
|
||||
var pageFrom = -1
|
||||
var pageTo = -1
|
||||
var imagePath: String? = null
|
||||
var isUseSimilarityScan = false
|
||||
var isOnlySearchCovers = false
|
||||
var isShowExpunged = false
|
||||
|
||||
constructor()
|
||||
protected constructor(parcel: Parcel) {
|
||||
mode = parcel.readInt()
|
||||
mPrevGid = parcel.readInt()
|
||||
mNextGid = parcel.readInt()
|
||||
mJumpTo = parcel.readString()
|
||||
this.category = parcel.readInt()
|
||||
mKeyword = parcel.readString()
|
||||
advanceSearch = parcel.readInt()
|
||||
minRating = parcel.readInt()
|
||||
pageFrom = parcel.readInt()
|
||||
pageTo = parcel.readInt()
|
||||
imagePath = parcel.readString()
|
||||
isUseSimilarityScan = parcel.readByte().toInt() != 0
|
||||
isOnlySearchCovers = parcel.readByte().toInt() != 0
|
||||
isShowExpunged = parcel.readByte().toInt() != 0
|
||||
mSHash = parcel.readString()
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
mode = MODE_NORMAL
|
||||
mPrevGid = 0
|
||||
mNextGid = 0
|
||||
mJumpTo = null
|
||||
this.category = EhUtils.NONE
|
||||
mKeyword = null
|
||||
advanceSearch = -1
|
||||
minRating = -1
|
||||
pageFrom = -1
|
||||
pageTo = -1
|
||||
imagePath = null
|
||||
isUseSimilarityScan = false
|
||||
isOnlySearchCovers = false
|
||||
isShowExpunged = false
|
||||
mSHash = null
|
||||
}
|
||||
|
||||
public override fun clone(): ListUrlBuilder {
|
||||
return try {
|
||||
super.clone() as ListUrlBuilder
|
||||
} catch (e: CloneNotSupportedException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPrevGid(prevGid: Int) {
|
||||
mPrevGid = prevGid
|
||||
}
|
||||
|
||||
fun setNextGid(nextGid: Int) {
|
||||
mNextGid = nextGid
|
||||
}
|
||||
|
||||
fun setJumpTo(jumpTo: String?) {
|
||||
mJumpTo = jumpTo
|
||||
}
|
||||
|
||||
var keyword: String?
|
||||
get() = if (MODE_UPLOADER == mode) "uploader:$mKeyword" else mKeyword
|
||||
set(keyword) {
|
||||
mKeyword = keyword
|
||||
}
|
||||
|
||||
/**
|
||||
* Make them the same
|
||||
*
|
||||
* @param lub The template
|
||||
*/
|
||||
fun set(lub: ListUrlBuilder) {
|
||||
mode = lub.mode
|
||||
mPrevGid = lub.mPrevGid
|
||||
mNextGid = lub.mNextGid
|
||||
mJumpTo = lub.mJumpTo
|
||||
this.category = lub.category
|
||||
mKeyword = lub.mKeyword
|
||||
advanceSearch = lub.advanceSearch
|
||||
minRating = lub.minRating
|
||||
pageFrom = lub.pageFrom
|
||||
pageTo = lub.pageTo
|
||||
imagePath = lub.imagePath
|
||||
isUseSimilarityScan = lub.isUseSimilarityScan
|
||||
isOnlySearchCovers = lub.isOnlySearchCovers
|
||||
isShowExpunged = lub.isShowExpunged
|
||||
mSHash = lub.mSHash
|
||||
}
|
||||
|
||||
fun set(q: QuickSearch) {
|
||||
mode = q.mode
|
||||
this.category = q.category
|
||||
mKeyword = q.keyword
|
||||
advanceSearch = q.advanceSearch
|
||||
minRating = q.minRating
|
||||
pageFrom = q.pageFrom
|
||||
pageTo = q.pageTo
|
||||
imagePath = null
|
||||
isUseSimilarityScan = false
|
||||
isOnlySearchCovers = false
|
||||
isShowExpunged = false
|
||||
}
|
||||
|
||||
fun toQuickSearch(): QuickSearch {
|
||||
val q = QuickSearch()
|
||||
q.mode = mode
|
||||
q.category = this.category
|
||||
q.keyword = mKeyword
|
||||
q.advanceSearch = advanceSearch
|
||||
q.minRating = minRating
|
||||
q.pageFrom = pageFrom
|
||||
q.pageTo = pageTo
|
||||
return q
|
||||
}
|
||||
|
||||
fun equalsQuickSearch(q: QuickSearch?): Boolean {
|
||||
if (null == q) {
|
||||
return false
|
||||
}
|
||||
if (q.mode != mode) {
|
||||
return false
|
||||
}
|
||||
if (q.category != this.category) {
|
||||
return false
|
||||
}
|
||||
if (!StringUtils.equals(q.keyword, mKeyword)) {
|
||||
return false
|
||||
}
|
||||
if (q.advanceSearch != advanceSearch) {
|
||||
return false
|
||||
}
|
||||
if (q.minRating != minRating) {
|
||||
return false
|
||||
}
|
||||
return if (q.pageFrom != pageFrom) {
|
||||
false
|
||||
} else q.pageTo == pageTo
|
||||
}
|
||||
|
||||
/**
|
||||
* @param query xxx=yyy&mmm=nnn
|
||||
*/
|
||||
// TODO page
|
||||
fun setQuery(query: String?) {
|
||||
reset()
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
return
|
||||
}
|
||||
val querys = StringUtils.split(query, '&')
|
||||
var category = 0
|
||||
var keyword: String? = null
|
||||
var enableAdvanceSearch = false
|
||||
var advanceSearch = 0
|
||||
var enableMinRating = false
|
||||
var minRating = -1
|
||||
var enablePage = false
|
||||
var pageFrom = -1
|
||||
var pageTo = -1
|
||||
for (str in querys) {
|
||||
val index = str.indexOf('=')
|
||||
if (index < 0) {
|
||||
continue
|
||||
}
|
||||
val key = str.substring(0, index)
|
||||
val value = str.substring(index + 1)
|
||||
when (key) {
|
||||
"f_cats" -> {
|
||||
val cats = NumberUtils.parseIntSafely(value, EhConfig.ALL_CATEGORY)
|
||||
category = category or (cats.inv() and EhConfig.ALL_CATEGORY)
|
||||
}
|
||||
|
||||
"f_doujinshi" -> if ("1" == value) {
|
||||
category = category or EhConfig.DOUJINSHI
|
||||
}
|
||||
|
||||
"f_manga" -> if ("1" == value) {
|
||||
category = category or EhConfig.MANGA
|
||||
}
|
||||
|
||||
"f_artistcg" -> if ("1" == value) {
|
||||
category = category or EhConfig.ARTIST_CG
|
||||
}
|
||||
|
||||
"f_gamecg" -> if ("1" == value) {
|
||||
category = category or EhConfig.GAME_CG
|
||||
}
|
||||
|
||||
"f_western" -> if ("1" == value) {
|
||||
category = category or EhConfig.WESTERN
|
||||
}
|
||||
|
||||
"f_non-h" -> if ("1" == value) {
|
||||
category = category or EhConfig.NON_H
|
||||
}
|
||||
|
||||
"f_imageset" -> if ("1" == value) {
|
||||
category = category or EhConfig.IMAGE_SET
|
||||
}
|
||||
|
||||
"f_cosplay" -> if ("1" == value) {
|
||||
category = category or EhConfig.COSPLAY
|
||||
}
|
||||
|
||||
"f_asianporn" -> if ("1" == value) {
|
||||
category = category or EhConfig.ASIAN_PORN
|
||||
}
|
||||
|
||||
"f_misc" -> if ("1" == value) {
|
||||
category = category or EhConfig.MISC
|
||||
}
|
||||
|
||||
"f_search" -> try {
|
||||
keyword = URLDecoder.decode(value, "utf-8")
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Ignore
|
||||
} catch (_: IllegalArgumentException) {
|
||||
}
|
||||
|
||||
"advsearch" -> if ("1" == value) {
|
||||
enableAdvanceSearch = true
|
||||
}
|
||||
|
||||
"f_sh" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.SH
|
||||
}
|
||||
|
||||
"f_sto" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.STO
|
||||
}
|
||||
|
||||
"f_sfl" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.SFL
|
||||
}
|
||||
|
||||
"f_sfu" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.SFU
|
||||
}
|
||||
|
||||
"f_sft" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.SFT
|
||||
}
|
||||
|
||||
"f_sr" -> if ("on" == value) {
|
||||
enableMinRating = true
|
||||
}
|
||||
|
||||
"f_srdd" -> minRating = NumberUtils.parseIntSafely(value, -1)
|
||||
"f_sp" -> if ("on" == value) {
|
||||
enablePage = true
|
||||
}
|
||||
|
||||
"f_spf" -> pageFrom = NumberUtils.parseIntSafely(value, -1)
|
||||
"f_spt" -> pageTo = NumberUtils.parseIntSafely(value, -1)
|
||||
"f_shash" -> mSHash = value
|
||||
}
|
||||
}
|
||||
this.category = category
|
||||
mKeyword = keyword
|
||||
if (enableAdvanceSearch) {
|
||||
this.advanceSearch = advanceSearch
|
||||
if (enableMinRating) {
|
||||
this.minRating = minRating
|
||||
} else {
|
||||
this.minRating = -1
|
||||
}
|
||||
if (enablePage) {
|
||||
this.pageFrom = pageFrom
|
||||
this.pageTo = pageTo
|
||||
} else {
|
||||
this.pageFrom = -1
|
||||
this.pageTo = -1
|
||||
}
|
||||
} else {
|
||||
this.advanceSearch = -1
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): String {
|
||||
return when (mode) {
|
||||
MODE_NORMAL, MODE_SUBSCRIPTION -> {
|
||||
val url: String = if (mode == MODE_NORMAL) {
|
||||
EhUrl.getHost()
|
||||
} else {
|
||||
EhUrl.getWatchedUrl()
|
||||
}
|
||||
val ub = UrlBuilder(url)
|
||||
if (this.category != EhUtils.NONE) {
|
||||
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
|
||||
}
|
||||
// Search key
|
||||
if (mKeyword != null) {
|
||||
val keyword = mKeyword!!.trim { it <= ' ' }
|
||||
if (keyword.isNotEmpty()) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash!!)
|
||||
}
|
||||
mJumpTo?.let {
|
||||
ub.addQuery("seek", mJumpTo!!)
|
||||
mPrevGid = 0
|
||||
mNextGid = 0
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
ub.addQuery("prev", mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
ub.addQuery("next", mNextGid)
|
||||
}
|
||||
// Advance search
|
||||
if (advanceSearch != -1) {
|
||||
ub.addQuery("advsearch", "1")
|
||||
if (advanceSearch and AdvanceSearchTable.SH != 0) ub.addQuery("f_sh", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.STO != 0) ub.addQuery("f_sto", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFL != 0) ub.addQuery("f_sfl", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFU != 0) ub.addQuery("f_sfu", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFT != 0) ub.addQuery("f_sft", "on")
|
||||
// Set min star
|
||||
if (minRating != -1) {
|
||||
ub.addQuery("f_sr", "on")
|
||||
ub.addQuery("f_srdd", minRating)
|
||||
}
|
||||
// Pages
|
||||
if (pageFrom != -1 || pageTo != -1) {
|
||||
ub.addQuery("f_sp", "on")
|
||||
ub.addQuery("f_spf", if (pageFrom != -1) pageFrom.toString() else "")
|
||||
ub.addQuery("f_spt", if (pageTo != -1) pageTo.toString() else "")
|
||||
}
|
||||
}
|
||||
ub.build()
|
||||
}
|
||||
|
||||
MODE_UPLOADER -> {
|
||||
val sb = StringBuilder(EhUrl.getHost())
|
||||
sb.append("uploader/")
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
sb.append("?prev=").append(mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
MODE_TAG -> {
|
||||
val sb = StringBuilder(EhUrl.getHost())
|
||||
sb.append("tag/")
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
sb.append("?prev=").append(mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
MODE_WHATS_HOT -> EhUrl.getPopularUrl()
|
||||
MODE_IMAGE_SEARCH -> EhUrl.getImageSearchUrl()
|
||||
MODE_TOPLIST -> {
|
||||
val sb = StringBuilder(EhUrl.HOST_E)
|
||||
sb.append("toplist.php?tl=")
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("&p=").append(mNextGid)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
else -> {
|
||||
val url: String = if (mode == MODE_NORMAL) {
|
||||
EhUrl.getHost()
|
||||
} else {
|
||||
EhUrl.getWatchedUrl()
|
||||
}
|
||||
val ub = UrlBuilder(url)
|
||||
if (this.category != EhUtils.NONE) {
|
||||
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
|
||||
}
|
||||
if (mKeyword != null) {
|
||||
val keyword = mKeyword!!.trim { it <= ' ' }
|
||||
if (keyword.isNotEmpty()) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash!!)
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
ub.addQuery("prev", mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
ub.addQuery("next", mNextGid)
|
||||
}
|
||||
if (advanceSearch != -1) {
|
||||
ub.addQuery("advsearch", "1")
|
||||
if (advanceSearch and AdvanceSearchTable.SH != 0) ub.addQuery("f_sh", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.STO != 0) ub.addQuery("f_sto", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFL != 0) ub.addQuery("f_sfl", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFU != 0) ub.addQuery("f_sfu", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFT != 0) ub.addQuery("f_sft", "on")
|
||||
if (minRating != -1) {
|
||||
ub.addQuery("f_sr", "on")
|
||||
ub.addQuery("f_srdd", minRating)
|
||||
}
|
||||
if (pageFrom != -1 || pageTo != -1) {
|
||||
ub.addQuery("f_sp", "on")
|
||||
ub.addQuery("f_spf", if (pageFrom != -1) pageFrom.toString() else "")
|
||||
ub.addQuery("f_spt", if (pageTo != -1) pageTo.toString() else "")
|
||||
}
|
||||
}
|
||||
ub.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(mode)
|
||||
dest.writeInt(mPrevGid)
|
||||
dest.writeInt(mNextGid)
|
||||
dest.writeString(mJumpTo)
|
||||
dest.writeInt(this.category)
|
||||
dest.writeString(mKeyword)
|
||||
dest.writeInt(advanceSearch)
|
||||
dest.writeInt(minRating)
|
||||
dest.writeInt(pageFrom)
|
||||
dest.writeInt(pageTo)
|
||||
dest.writeString(imagePath)
|
||||
dest.writeByte(if (isUseSimilarityScan) 1.toByte() else 0.toByte())
|
||||
dest.writeByte(if (isOnlySearchCovers) 1.toByte() else 0.toByte())
|
||||
dest.writeByte(if (isShowExpunged) 1.toByte() else 0.toByte())
|
||||
dest.writeString(mSHash)
|
||||
}
|
||||
|
||||
@IntDef(
|
||||
MODE_NORMAL,
|
||||
MODE_UPLOADER,
|
||||
MODE_TAG,
|
||||
MODE_WHATS_HOT,
|
||||
MODE_IMAGE_SEARCH,
|
||||
MODE_SUBSCRIPTION,
|
||||
MODE_TOPLIST
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class Mode
|
||||
companion object {
|
||||
const val MODE_NORMAL = 0x0
|
||||
const val MODE_UPLOADER = 0x1
|
||||
const val MODE_TAG = 0x2
|
||||
const val MODE_WHATS_HOT = 0x3
|
||||
const val MODE_IMAGE_SEARCH = 0x4
|
||||
const val MODE_SUBSCRIPTION = 0x5
|
||||
const val MODE_TOPLIST = 0x6
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Creator<ListUrlBuilder> = object : Creator<ListUrlBuilder> {
|
||||
override fun createFromParcel(source: Parcel): ListUrlBuilder {
|
||||
return ListUrlBuilder(source)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<ListUrlBuilder?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,18 +31,22 @@ import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FavoritesParser {
|
||||
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+(-\\d+)?)");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+(-\\d+)?)");
|
||||
|
||||
public static Result parse(String body) throws Exception {
|
||||
if (body.contains("This page requires you to log on.</p>")) {
|
||||
throw new EhException(GetText.getString(R.string.need_sign_in));
|
||||
}
|
||||
Document d = Jsoup.parse(body);
|
||||
|
||||
String[] catArray = new String[10];
|
||||
int[] countArray = new int[10];
|
||||
|
||||
try {
|
||||
Document d = Jsoup.parse(body);
|
||||
Element ido = JsoupUtils.getElementByClass(d, "ido");
|
||||
//noinspection ConstantConditions
|
||||
Elements fps = ido.getElementsByClass("fp");
|
||||
@@ -60,13 +64,24 @@ public class FavoritesParser {
|
||||
throw new ParseException("Parse favorites error", body);
|
||||
}
|
||||
|
||||
Result re = new Result();
|
||||
try {
|
||||
Element prev = d.getElementById("uprev");
|
||||
Element next = d.getElementById("unext");
|
||||
assert prev != null;
|
||||
assert next != null;
|
||||
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
|
||||
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
|
||||
if (matcherPrev.find()) re.prevPage = matcherPrev.group(1);
|
||||
if (matcherNext.find()) re.nextPage = matcherNext.group(1);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
GalleryListParser.Result result = GalleryListParser.parse(body);
|
||||
|
||||
Result re = new Result();
|
||||
re.catArray = catArray;
|
||||
re.countArray = countArray;
|
||||
re.pages = result.pages;
|
||||
re.nextPage = result.nextPage;
|
||||
re.galleryInfoList = result.galleryInfoList;
|
||||
|
||||
return re;
|
||||
@@ -75,8 +90,8 @@ public class FavoritesParser {
|
||||
public static class Result {
|
||||
public String[] catArray; // Size 10
|
||||
public int[] countArray; // Size 10
|
||||
public int pages;
|
||||
public int nextPage;
|
||||
public String prevPage;
|
||||
public String nextPage;
|
||||
public List<GalleryInfo> galleryInfoList;
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,8 @@ public class GalleryListParser {
|
||||
private static final Pattern PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px");
|
||||
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("background-color:rgba\\((\\d+),(\\d+),(\\d+),");
|
||||
private static final Pattern PATTERN_PAGES = Pattern.compile("(\\d+) page");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("page=(\\d+)");
|
||||
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+)");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+)");
|
||||
|
||||
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
|
||||
new String[]{"0", "0", "0"},
|
||||
@@ -298,37 +299,31 @@ public class GalleryListParser {
|
||||
}
|
||||
|
||||
public static Result parse(@NonNull String body) throws Exception {
|
||||
|
||||
Result result = new Result();
|
||||
Document d = Jsoup.parse(body);
|
||||
|
||||
try {
|
||||
Element ptt = d.getElementsByClass("ptt").first();
|
||||
Elements es = ptt.child(0).child(0).children();
|
||||
result.pages = Integer.parseInt(es.get(es.size() - 2).text().trim());
|
||||
|
||||
Element e = es.get(es.size() - 1);
|
||||
if (e != null) {
|
||||
e = e.children().first();
|
||||
if (e != null) {
|
||||
String href = e.attr("href");
|
||||
Matcher matcher = PATTERN_NEXT_PAGE.matcher(href);
|
||||
if (matcher.find()) {
|
||||
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Element prev = d.getElementById("uprev");
|
||||
Element next = d.getElementById("unext");
|
||||
assert prev != null;
|
||||
assert next != null;
|
||||
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
|
||||
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
|
||||
if (matcherPrev.find()) result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
|
||||
if (matcherNext.find()) result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
|
||||
if (body.contains("No hits found</p>")) {
|
||||
result.pages = 0;
|
||||
result.founds = 0;
|
||||
//noinspection unchecked
|
||||
result.galleryInfoList = Collections.EMPTY_LIST;
|
||||
return result;
|
||||
} else if (d.getElementsByClass("ptt").isEmpty()) {
|
||||
result.pages = 1;
|
||||
result.founds = 1;
|
||||
} else {
|
||||
result.pages = Integer.MAX_VALUE;
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +345,7 @@ public class GalleryListParser {
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
if (es.size() < 2 || !NO_UNFILTERED_TEXT.equals(es.get(1).text())) {
|
||||
throw new ParseException("No gallery", body);
|
||||
Log.d(TAG, "No gallery found");
|
||||
}
|
||||
}
|
||||
result.galleryInfoList = list;
|
||||
@@ -364,8 +359,9 @@ public class GalleryListParser {
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public int pages;
|
||||
public int nextPage;
|
||||
public int founds;
|
||||
public int prevGid;
|
||||
public int nextGid;
|
||||
public boolean noWatchedTags;
|
||||
public List<GalleryInfo> galleryInfoList;
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder;
|
||||
import com.hippo.yorozuya.Utilities;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class GalleryListUrlParser {
|
||||
|
||||
@@ -91,7 +91,11 @@ public final class GalleryListUrlParser {
|
||||
uploader = path.substring(prefixLength, index);
|
||||
}
|
||||
|
||||
uploader = URLDecoder.decode(uploader, StandardCharsets.UTF_8);
|
||||
try {
|
||||
uploader = URLDecoder.decode(uploader, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(uploader)) {
|
||||
return null;
|
||||
@@ -116,7 +120,11 @@ public final class GalleryListUrlParser {
|
||||
tag = path.substring(prefixLength, index);
|
||||
}
|
||||
|
||||
tag = URLDecoder.decode(tag, StandardCharsets.UTF_8);
|
||||
try {
|
||||
tag = URLDecoder.decode(tag, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(tag)) {
|
||||
return null;
|
||||
|
||||
@@ -4,6 +4,5 @@ import java.util.List;
|
||||
|
||||
public interface BasicDao<T> {
|
||||
List<T> list();
|
||||
|
||||
long insert(T t);
|
||||
}
|
||||
@@ -24,15 +24,19 @@ import android.os.Process;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.Native;
|
||||
import com.hippo.UriArchiveAccessor;
|
||||
import com.hippo.ehviewer.GetText;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.thread.PVLock;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -48,13 +52,14 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
private final UriArchiveAccessor archiveAccessor;
|
||||
private final Stack<Integer> requests = new Stack<>();
|
||||
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
|
||||
private final Thread[] decodeThread = new Thread[]{
|
||||
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 archiveThread;
|
||||
|
||||
private volatile int size = STATE_WAIT;
|
||||
private String error;
|
||||
|
||||
@@ -142,32 +147,47 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
@NonNull
|
||||
@Override
|
||||
public String getImageFilename(int index) {
|
||||
// TODO
|
||||
return Integer.toString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(int index, @NonNull UniFile file) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
|
||||
// TODO
|
||||
return null;
|
||||
return FileUtils.getNameFromFilename(getImageFilenameWithExtension(index));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getImageFilenameWithExtension(int index) {
|
||||
// TODO
|
||||
return Integer.toString(index);
|
||||
return FileUtils.sanitizeFilename(archiveAccessor.getFilename(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(int index, @NonNull UniFile file) {
|
||||
int fd;
|
||||
FileOutputStream stream;
|
||||
try {
|
||||
stream = (FileOutputStream)file.openOutputStream();
|
||||
fd = Native.getFd(stream.getFD());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
archiveAccessor.extractToFd(index, fd);
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
|
||||
String extension = FileUtils.getExtensionFromFilename(getImageFilenameWithExtension(index));
|
||||
UniFile dst = dir.subFile(null != extension ? filename + "." + extension : filename);
|
||||
save(index, dst);
|
||||
return dst;
|
||||
}
|
||||
|
||||
private class ArchiveHostTask implements Runnable {
|
||||
public final Thread[] archiveThreads = new Thread[]{
|
||||
public final Thread[] archiveThreads = new Thread[] {
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask()),
|
||||
@@ -278,7 +298,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
}
|
||||
}
|
||||
|
||||
long addr = archiveAccessor.extracttoOutputStream(index);
|
||||
long addr = archiveAccessor.extractToAddr(index);
|
||||
|
||||
synchronized (streams) {
|
||||
streams.put(index, addr);
|
||||
|
||||
+11
-22
@@ -16,8 +16,6 @@
|
||||
package com.hippo.ehviewer.preference
|
||||
|
||||
import android.content.*
|
||||
import android.os.Build
|
||||
import android.os.PersistableBundle
|
||||
import android.text.Html
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@@ -25,14 +23,17 @@ 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 IdentityCookiePreference @JvmOverloads constructor(
|
||||
class AccountPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : MessagePreference(context, attrs) {
|
||||
private val mActivity = context as SettingsActivity
|
||||
@@ -79,26 +80,14 @@ class IdentityCookiePreference @JvmOverloads constructor(
|
||||
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
|
||||
super.onPrepareDialogBuilder(builder)
|
||||
if (message != null) {
|
||||
builder.setPositiveButton(R.string.settings_eh_identity_cookies_copy) { dialog: DialogInterface?, which: Int ->
|
||||
val clipboardManager =
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
|
||||
val clipData = ClipData.newPlainText(null, message)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
clipData.apply {
|
||||
description.extras = PersistableBundle().apply {
|
||||
putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
|
||||
}
|
||||
}
|
||||
clipboardManager?.setPrimaryClip(clipData)
|
||||
|
||||
// There is no need to notify user by toast since Tiramisu have its clipboard own logic
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
mActivity.showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
|
||||
this@IdentityCookiePreference.onClick(dialog, which)
|
||||
builder.setNeutralButton(R.string.settings_eh_identity_cookies_copy) { dialog: DialogInterface?, which: Int ->
|
||||
mActivity.addTextToClipboard(message, true)
|
||||
this@AccountPreference.onClick(dialog, which)
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
} else {
|
||||
builder.setPositiveButton(android.R.string.ok, null)
|
||||
}
|
||||
builder.setPositiveButton(R.string.settings_eh_sign_out) { _: DialogInterface?, _: Int ->
|
||||
EhUtils.signOut(context)
|
||||
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.preference;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.ui.SettingsActivity;
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene;
|
||||
import com.hippo.preference.MessagePreference;
|
||||
|
||||
public class SignOutPreference extends MessagePreference {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private final SettingsActivity mActivity;
|
||||
|
||||
public SignOutPreference(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
mActivity = (SettingsActivity) context;
|
||||
}
|
||||
|
||||
public SignOutPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
mActivity = (SettingsActivity) context;
|
||||
}
|
||||
|
||||
public SignOutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
mActivity = (SettingsActivity) context;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setDialogMessage(getContext().getString(R.string.settings_eh_sign_out_warning));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
builder.setPositiveButton(R.string.settings_eh_sign_out_yes, this);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
if (positiveResult) {
|
||||
EhUtils.signOut(getContext());
|
||||
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,12 @@ package com.hippo.ehviewer.preference
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.hippo.Native
|
||||
import androidx.preference.Preference
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.preference.MessagePreference
|
||||
|
||||
class VersionPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : MessagePreference(context, attrs) {
|
||||
) : Preference(context, attrs) {
|
||||
init {
|
||||
setTitle(R.string.settings_about_version)
|
||||
@Suppress("DEPRECATION") val version: String = try {
|
||||
@@ -37,31 +35,4 @@ class VersionPreference @JvmOverloads constructor(
|
||||
}
|
||||
summary = version
|
||||
}
|
||||
|
||||
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
|
||||
super.onPrepareDialogBuilder(builder)
|
||||
builder.setTitle(R.string.show_library_version)
|
||||
val info = NativeLibInfo().addLib(Native.getlibarchiveVersion())
|
||||
.addLib("lzma(xz-utils)", Native.getliblzmaVersion())
|
||||
.addLib("zlib", Native.getzlibVersion())
|
||||
.addLib(Native.getnettleVersion())
|
||||
builder.setMessage(info.getMessage())
|
||||
}
|
||||
|
||||
inner class NativeLibInfo {
|
||||
private var message: String = ""
|
||||
fun addLib(libname: String, libversion: String): NativeLibInfo {
|
||||
message = "$message$libname $libversion\n"
|
||||
return this
|
||||
}
|
||||
|
||||
fun getMessage(): String {
|
||||
return message
|
||||
}
|
||||
|
||||
fun addLib(msg: String): NativeLibInfo {
|
||||
message = "$message$msg\n"
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,9 @@ import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.EhApplication.locked_last_leave_time
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
|
||||
import rikka.core.res.resolveColor
|
||||
import rikka.insets.WindowInsetsHelper
|
||||
import rikka.layoutinflater.view.LayoutInflaterFactory
|
||||
@@ -71,7 +71,6 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
WindowInsetsHelper.LISTENER
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
val window = window
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
window.decorView.post {
|
||||
val rootWindowInsets = window.decorView.rootWindowInsets
|
||||
@@ -100,7 +99,11 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (Settings.getSecurity() && isAuthenticationSupported(this) && EhApplication.locked) {
|
||||
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()
|
||||
|
||||
@@ -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;
|
||||
@@ -858,7 +858,8 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, EhUrl.getGalleryDetailUrl(mGalleryInfo.gid, mGalleryInfo.token));
|
||||
if (mGalleryInfo != null)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, EhUrl.getGalleryDetailUrl(mGalleryInfo.gid, mGalleryInfo.token));
|
||||
intent.setDataAndType(uri, mimeType);
|
||||
|
||||
try {
|
||||
@@ -990,7 +991,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
|
||||
private void showPageDialog(final int page) {
|
||||
Resources resources = GalleryActivity.this.getResources();
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(GalleryActivity.this);
|
||||
builder.setTitle(resources.getString(R.string.page_menu_title, page + 1));
|
||||
|
||||
final CharSequence[] items;
|
||||
@@ -1286,7 +1287,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
|
||||
private void onTapMenuArea() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(GalleryActivity.this);
|
||||
GalleryMenuHelper helper = new GalleryMenuHelper(builder.getContext());
|
||||
builder.setTitle(R.string.gallery_menu_title)
|
||||
.setView(helper.getView())
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
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.addTextToClipboard
|
||||
import com.hippo.util.getClipboardManager
|
||||
|
||||
class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment() {
|
||||
private var mKeys: ArrayList<String> = arrayListOf()
|
||||
private var mValues: ArrayList<String> = arrayListOf()
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
private val mDetail: GalleryDetail = detail
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
parseDetail()
|
||||
val view = inflater.inflate(R.layout.scene_gallery_info, container, false)
|
||||
dialog?.window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
val recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView
|
||||
val adapter = InfoAdapter()
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.setHasFixedSize(true)
|
||||
mRecyclerView = recyclerView
|
||||
return view
|
||||
}
|
||||
|
||||
private fun parseDetail() {
|
||||
mKeys.add(getString(R.string.header_key))
|
||||
mValues.add(getString(R.string.header_value))
|
||||
mKeys.add(getString(R.string.key_gid))
|
||||
mValues.add(mDetail.gid.toString())
|
||||
mKeys.add(getString(R.string.key_token))
|
||||
mValues.add(mDetail.token)
|
||||
mKeys.add(getString(R.string.key_url))
|
||||
mValues.add(EhUrl.getGalleryDetailUrl(mDetail.gid, mDetail.token))
|
||||
mKeys.add(getString(R.string.key_title))
|
||||
mValues.add(mDetail.title)
|
||||
mKeys.add(getString(R.string.key_title_jpn))
|
||||
mValues.add(mDetail.titleJpn)
|
||||
mKeys.add(getString(R.string.key_thumb))
|
||||
mValues.add(mDetail.thumb)
|
||||
mKeys.add(getString(R.string.key_category))
|
||||
mValues.add(EhUtils.getCategory(mDetail.category))
|
||||
mKeys.add(getString(R.string.key_uploader))
|
||||
mValues.add(mDetail.uploader)
|
||||
mKeys.add(getString(R.string.key_posted))
|
||||
mValues.add(mDetail.posted)
|
||||
mKeys.add(getString(R.string.key_parent))
|
||||
mValues.add(mDetail.parent)
|
||||
mKeys.add(getString(R.string.key_visible))
|
||||
mValues.add(mDetail.visible)
|
||||
mKeys.add(getString(R.string.key_language))
|
||||
mValues.add(mDetail.language)
|
||||
mKeys.add(getString(R.string.key_pages))
|
||||
mValues.add(mDetail.pages.toString())
|
||||
mKeys.add(getString(R.string.key_size))
|
||||
mValues.add(mDetail.size)
|
||||
mKeys.add(getString(R.string.key_favorite_count))
|
||||
mValues.add(mDetail.favoriteCount.toString())
|
||||
mKeys.add(getString(R.string.key_favorited))
|
||||
mValues.add(java.lang.Boolean.toString(mDetail.isFavorited))
|
||||
mKeys.add(getString(R.string.key_rating_count))
|
||||
mValues.add(mDetail.ratingCount.toString())
|
||||
mKeys.add(getString(R.string.key_rating))
|
||||
mValues.add(mDetail.rating.toString())
|
||||
mKeys.add(getString(R.string.key_torrents))
|
||||
mValues.add(mDetail.torrentCount.toString())
|
||||
mKeys.add(getString(R.string.key_torrent_url))
|
||||
mValues.add(mDetail.torrentUrl)
|
||||
mKeys.add(getString(R.string.favorite_name))
|
||||
mValues.add(mDetail.favoriteName)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(view.findViewById(R.id.title) as TextView).setText(R.string.gallery_info)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mRecyclerView?.stopScroll()
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
val context = context
|
||||
return if (null != context && 0 != position) {
|
||||
if (position == INDEX_PARENT) {
|
||||
UrlOpener.openUrl(context, mValues[position], true)
|
||||
} else {
|
||||
activity?.addTextToClipboard(
|
||||
mValues[position],
|
||||
false,
|
||||
)
|
||||
if (position == INDEX_URL) {
|
||||
// Save it to avoid detect the gallery
|
||||
Settings.putClipboardTextHashCode(mValues[position].hashCode())
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private class InfoHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val key: TextView
|
||||
val value: TextView
|
||||
|
||||
init {
|
||||
key = itemView.findViewById(R.id.key)
|
||||
value = itemView.findViewById(R.id.value)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class InfoAdapter : RecyclerView.Adapter<InfoHolder>() {
|
||||
private val mInflater: LayoutInflater = layoutInflater
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position == 0) {
|
||||
TYPE_HEADER
|
||||
} else {
|
||||
TYPE_DATA
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InfoHolder {
|
||||
return InfoHolder(
|
||||
mInflater.inflate(
|
||||
if (viewType == TYPE_HEADER) R.layout.item_gallery_info_header else R.layout.item_gallery_info_data,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: InfoHolder, position: Int) {
|
||||
holder.key.text = mKeys[position]
|
||||
holder.value.text = mValues[position]
|
||||
holder.itemView.isEnabled = position != 0
|
||||
holder.itemView.setOnClickListener { onItemClick(position) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mKeys.size.coerceAtMost(mValues.size)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "GalleryInfoBottomSheet"
|
||||
private const val INDEX_URL = 3
|
||||
private const val INDEX_PARENT = 10
|
||||
private const val TYPE_HEADER = 0
|
||||
private const val TYPE_DATA = 1
|
||||
}
|
||||
}
|
||||
@@ -42,9 +42,9 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.R;
|
||||
@@ -61,7 +61,6 @@ import com.hippo.ehviewer.ui.scene.DownloadsScene;
|
||||
import com.hippo.ehviewer.ui.scene.FavoritesScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryCommentsScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryDetailScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryInfoScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryPreviewsScene;
|
||||
import com.hippo.ehviewer.ui.scene.HistoryScene;
|
||||
@@ -77,7 +76,7 @@ import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.BitmapUtils;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.util.ClipboardUtilKt;
|
||||
import com.hippo.widget.DrawerView;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
@@ -101,7 +100,6 @@ public final class MainActivity extends StageActivity
|
||||
registerLaunchMode(SelectSiteScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(GalleryListScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TOP);
|
||||
registerLaunchMode(GalleryDetailScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||
registerLaunchMode(GalleryInfoScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||
registerLaunchMode(GalleryCommentsScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||
registerLaunchMode(GalleryPreviewsScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||
registerLaunchMode(DownloadsScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
@@ -232,7 +230,6 @@ public final class MainActivity extends StageActivity
|
||||
builder.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
||||
builder.setImagePath(temp.getPath());
|
||||
builder.setUseSimilarityScan(true);
|
||||
builder.setShowExpunged(true);
|
||||
startScene(processAnnouncer(GalleryListScene.getStartAnnouncer(builder)));
|
||||
return true;
|
||||
}
|
||||
@@ -259,7 +256,7 @@ public final class MainActivity extends StageActivity
|
||||
String url = intent.getData().toString();
|
||||
new EditTextDialogBuilder(this, url, "")
|
||||
.setTitle(R.string.error_cannot_parse_the_url)
|
||||
.setPositiveButton(android.R.string.copy, (dialog, which) -> ClipboardUtil.addTextToClipboard(url))
|
||||
.setPositiveButton(android.R.string.copy, (dialog, which) -> ClipboardUtilKt.addTextToClipboard(this, url, false))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -337,7 +334,7 @@ public final class MainActivity extends StageActivity
|
||||
break;
|
||||
}
|
||||
if (hasUnverified) {
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
new BaseDialogBuilder(this)
|
||||
.setTitle(R.string.app_link_not_verified_title)
|
||||
.setMessage(R.string.app_link_not_verified_message)
|
||||
.setPositiveButton(R.string.open_settings, (dialogInterface, i) -> {
|
||||
@@ -363,7 +360,7 @@ public final class MainActivity extends StageActivity
|
||||
if (null == uniFile || uniFile.ensureDir()) {
|
||||
return;
|
||||
}
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
new BaseDialogBuilder(this)
|
||||
.setTitle(R.string.waring)
|
||||
.setMessage(R.string.invalid_download_location)
|
||||
.setPositiveButton(R.string.get_it, null)
|
||||
@@ -456,7 +453,7 @@ public final class MainActivity extends StageActivity
|
||||
}
|
||||
|
||||
private void checkClipboardUrlInternal() {
|
||||
String text = ClipboardUtil.getUrlFromClipboard();
|
||||
String text = ClipboardUtilKt.getUrlFromClipboard(ClipboardUtilKt.getClipboardManager(this), this);
|
||||
int hashCode = text != null ? text.hashCode() : 0;
|
||||
|
||||
if (text != null && hashCode != 0 && Settings.getClipboardTextHashCode() != hashCode) {
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
@@ -31,10 +30,16 @@ 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 SecurityActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (isAuthenticationSupported(this))
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isAuthenticationSupported())
|
||||
startAuthentication(getString(R.string.settings_privacy_require_unlock))
|
||||
else
|
||||
onSuccess()
|
||||
@@ -71,14 +76,6 @@ class SecurityActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
finish()
|
||||
|
||||
@@ -57,7 +57,7 @@ class SettingsActivity : EhActivity() {
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
@@ -31,13 +33,18 @@ import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.BuildConfig;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.GetText;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
import com.hippo.ehviewer.client.EhRequest;
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
|
||||
import com.hippo.ehviewer.client.parser.FavoritesParser;
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
@@ -52,17 +59,18 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
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";
|
||||
private static final String KEY_OPEN_BY_DEFAULT = "open_by_default";
|
||||
private static final String KEY_BACKUP_FAVORITE = "backup_favorite";
|
||||
|
||||
ActivityResultLauncher<String> exportLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.CreateDocument(),
|
||||
@@ -76,7 +84,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
AlertDialog alertDialog = new BaseDialogBuilder(requireActivity())
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.preference_dialog_task)
|
||||
.show();
|
||||
@@ -193,7 +201,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
AlertDialog alertDialog = new BaseDialogBuilder(requireActivity())
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.preference_dialog_task)
|
||||
.show();
|
||||
@@ -225,13 +233,13 @@ 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);
|
||||
Preference backupFavorite = findPreference(KEY_BACKUP_FAVORITE);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
openByDefault.setVisible(false);
|
||||
@@ -240,9 +248,9 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
}
|
||||
|
||||
dumpLogcat.setOnPreferenceClickListener(this);
|
||||
clearMemoryCache.setOnPreferenceClickListener(this);
|
||||
importData.setOnPreferenceClickListener(this);
|
||||
exportData.setOnPreferenceClickListener(this);
|
||||
backupFavorite.setOnPreferenceClickListener(this);
|
||||
|
||||
appLanguage.setOnPreferenceChangeListener(this);
|
||||
}
|
||||
@@ -258,10 +266,6 @@ 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[]{"*/*"});
|
||||
@@ -289,10 +293,62 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
startActivity(intent);
|
||||
}
|
||||
return true;
|
||||
} else if (KEY_BACKUP_FAVORITE.equals(key)) {
|
||||
try {
|
||||
showTip(R.string.backup_favorite_start, BaseScene.LENGTH_SHORT);
|
||||
backupFavorite();
|
||||
} catch (Exception e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void backupFavorite() {
|
||||
AtomicInteger page = new AtomicInteger(0);
|
||||
Context context = requireContext();
|
||||
Activity activity = requireActivity();
|
||||
EhClient mClient = EhApplication.getEhClient(context);
|
||||
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
|
||||
// favListUrlBuilder.setNext(page.get());
|
||||
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||
request.setCallback(new EhClient.Callback() {
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
try {
|
||||
FavoritesParser.Result result1 = (FavoritesParser.Result) result;
|
||||
EhDB.putLocalFavorites(result1.galleryInfoList);
|
||||
Log.d("LocalFavorites", "now backup page " + page.get());
|
||||
if (!result1.galleryInfoList.isEmpty()) {
|
||||
// favListUrlBuilder.setNext(page.incrementAndGet());
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
} else {
|
||||
showTip(R.string.backup_favorite_success, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
});
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
String key = preference.getKey();
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
@@ -99,7 +99,7 @@ public class DownloadFragment extends BasePreferenceFragment {
|
||||
if (KEY_DOWNLOAD_LOCATION.equals(key)) {
|
||||
UniFile file = Settings.getDownloadLocation();
|
||||
if (file != null && !UniFile.isFileUri(Settings.getDownloadLocation().getUri())) {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
new BaseDialogBuilder(requireContext())
|
||||
.setTitle(R.string.settings_download_download_location)
|
||||
.setMessage(file.getUri().toString())
|
||||
.setPositiveButton(R.string.pick_new_download_location, (dialogInterface, i) -> openDirPickerL())
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhTagDatabase;
|
||||
|
||||
public class EhFragment extends BasePreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.eh_settings);
|
||||
|
||||
Preference theme = findPreference(Settings.KEY_THEME);
|
||||
Preference blackDarkTheme = findPreference(Settings.KEY_BLACK_DARK_THEME);
|
||||
Preference gallerySite = findPreference(Settings.KEY_GALLERY_SITE);
|
||||
Preference listMode = findPreference(Settings.KEY_LIST_MODE);
|
||||
Preference 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.fragment
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.Preference
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
|
||||
class EhFragment : BasePreferenceFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.eh_settings)
|
||||
val account = findPreference<Preference>(Settings.KEY_ACCOUNT)
|
||||
val theme = findPreference<Preference>(Settings.KEY_THEME)
|
||||
val blackDarkTheme = findPreference<Preference>(Settings.KEY_BLACK_DARK_THEME)
|
||||
val gallerySite = findPreference<Preference>(Settings.KEY_GALLERY_SITE)
|
||||
val listMode = findPreference<Preference>(Settings.KEY_LIST_MODE)
|
||||
val listThumbSize = findPreference<Preference>(Settings.KEY_LIST_THUMB_SIZE)
|
||||
val detailSize = findPreference<Preference>(Settings.KEY_DETAIL_SIZE)
|
||||
val thumbSize = findPreference<Preference>(Settings.KEY_THUMB_SIZE)
|
||||
val showTagTranslations = findPreference<Preference>(Settings.KEY_SHOW_TAG_TRANSLATIONS)
|
||||
val tagTranslationsSource = findPreference<Preference>("tag_translations_source")
|
||||
Settings.getDisplayName()?.let { account?.summary = it }
|
||||
theme!!.onPreferenceChangeListener = this
|
||||
gallerySite!!.onPreferenceChangeListener = this
|
||||
listMode!!.onPreferenceChangeListener = this
|
||||
listThumbSize!!.onPreferenceChangeListener = this
|
||||
detailSize!!.onPreferenceChangeListener = this
|
||||
thumbSize!!.onPreferenceChangeListener = this
|
||||
showTagTranslations!!.onPreferenceChangeListener = this
|
||||
blackDarkTheme!!.onPreferenceChangeListener = this
|
||||
if (!EhTagDatabase.isPossible(requireActivity())) {
|
||||
preferenceScreen.removePreference(showTagTranslations)
|
||||
preferenceScreen.removePreference(tagTranslationsSource!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
|
||||
val key = preference.key
|
||||
if (Settings.KEY_THEME == key) {
|
||||
AppCompatDelegate.setDefaultNightMode((newValue as String).toInt())
|
||||
requireActivity().recreate()
|
||||
return true
|
||||
} else if (Settings.KEY_GALLERY_SITE == key) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
return true
|
||||
} else if (Settings.KEY_LIST_MODE == key) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
return true
|
||||
} else if (Settings.KEY_LIST_THUMB_SIZE == key) {
|
||||
Settings.LIST_THUMB_SIZE_INITED = false
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
return true
|
||||
} else if (Settings.KEY_DETAIL_SIZE == key) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
} else if (Settings.KEY_THUMB_SIZE == key) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
} else if (Settings.KEY_SHOW_TAG_TRANSLATIONS == key) {
|
||||
if (java.lang.Boolean.TRUE == newValue) {
|
||||
EhTagDatabase.update(requireActivity())
|
||||
}
|
||||
} else if (Settings.KEY_BLACK_DARK_THEME == key) {
|
||||
if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) {
|
||||
requireActivity().recreate()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getFragmentTitle(): Int {
|
||||
return R.string.settings_eh
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,9 @@ import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.checkbox.MaterialCheckBox
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhFilter
|
||||
@@ -112,7 +112,7 @@ class FilterFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
private fun showTipDialog() {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
BaseDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.filter)
|
||||
.setMessage(R.string.filter_tip)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
@@ -120,7 +120,7 @@ class FilterFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
private fun showAddFilterDialog() {
|
||||
val dialog = MaterialAlertDialogBuilder(requireActivity())
|
||||
val dialog = BaseDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.add_filter)
|
||||
.setView(R.layout.dialog_add_filter)
|
||||
.setPositiveButton(R.string.add, null)
|
||||
@@ -131,7 +131,7 @@ class FilterFragment : BaseFragment() {
|
||||
|
||||
private fun showDeleteFilterDialog(filter: Filter) {
|
||||
val message = getString(R.string.delete_filter, filter.text)
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
BaseDialogBuilder(requireActivity())
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.delete) { _: DialogInterface?, which: Int ->
|
||||
if (DialogInterface.BUTTON_POSITIVE != which) {
|
||||
|
||||
@@ -34,9 +34,9 @@ import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.Hosts;
|
||||
import com.hippo.ehviewer.R;
|
||||
@@ -149,7 +149,8 @@ public class HostsFragment extends BaseFragment
|
||||
ip.setText(arguments.getString(KEY_IP));
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()).setView(view);
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(requireContext());
|
||||
builder.setView(view);
|
||||
onCreateDialogBuilder(builder);
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
|
||||
|
||||
@@ -19,29 +19,55 @@
|
||||
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.SecurityActivity.Companion.isAuthenticationSupported
|
||||
import com.hippo.ehviewer.ui.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 (!isAuthenticationSupported(requireContext())) {
|
||||
if (!requireContext().isAuthenticationSupported()) {
|
||||
Settings.putSecurity(false)
|
||||
findPreference<SwitchPreferenceCompat>("require_unlock")?.isEnabled = false
|
||||
findPreference<SwitchPreferenceCompat>("require_unlock")?.isChecked = false
|
||||
requireUnlock.isEnabled = false
|
||||
requireUnlock.isChecked = false
|
||||
unlockDelay.isEnabled = false
|
||||
} else {
|
||||
unlockDelay.isEnabled = requireUnlock.isChecked == true
|
||||
setUnlockDelaySummary(unlockDelay.value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFragmentTitle(): Int {
|
||||
return R.string.settings_privacy
|
||||
}
|
||||
|
||||
private fun setUnlockDelaySummary(value: Int) {
|
||||
unlockDelay.summary = if (value == 0) {
|
||||
getString(R.string.settings_privacy_require_unlock_delay_summary_immediately)
|
||||
} else {
|
||||
getString(R.string.settings_privacy_require_unlock_delay_summary, value.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@ import androidx.fragment.app.FragmentActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
public abstract class BaseScene extends SceneFragment {
|
||||
|
||||
public static final int LENGTH_SHORT = 0;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
abstract class CollapsingToolbarScene : ToolbarScene() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.scene_collapsing_toolbar, container, false) as ViewGroup
|
||||
mToolbar = view.findViewById(R.id.toolbar)
|
||||
mAppBarLayout = view.findViewById(R.id.appbar)
|
||||
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
|
||||
return view.apply { addView(contentView, 0) }
|
||||
}
|
||||
}
|
||||
@@ -31,14 +31,14 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhCookieStore;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.util.ClipboardUtilKt;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
@@ -217,7 +217,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
hideSoftInput();
|
||||
|
||||
if (!checkIpbMemberId(ipbMemberId) || !(checkIpbPassHash(ipbPassHash))) {
|
||||
new MaterialAlertDialogBuilder(context).setTitle(R.string.waring)
|
||||
new BaseDialogBuilder(context).setTitle(R.string.waring)
|
||||
.setMessage(R.string.wrong_cookie_warning)
|
||||
.setPositiveButton(R.string.i_will_check_it, null)
|
||||
.setNegativeButton(R.string.i_dont_think_so, (dialog, which) -> {
|
||||
@@ -253,7 +253,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
|
||||
private void fillCookiesFromClipboard() {
|
||||
hideSoftInput();
|
||||
String text = ClipboardUtil.getTextFromClipboard();
|
||||
String text = ClipboardUtilKt.getTextFromClipboard(ClipboardUtilKt.getClipboardManager(getContext()), getContext());
|
||||
if (text == null) {
|
||||
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
|
||||
return;
|
||||
|
||||
@@ -51,12 +51,10 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.CheckBoxDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.conaco.DataContainer;
|
||||
import com.hippo.conaco.ProgressNotifier;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.FastScroller;
|
||||
import com.hippo.easyrecyclerview.HandlerDrawable;
|
||||
@@ -74,9 +72,7 @@ import com.hippo.ehviewer.download.DownloadService;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView;
|
||||
import com.hippo.io.UniFileInputStreamPipe;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.view.ViewTransition;
|
||||
@@ -85,14 +81,10 @@ import com.hippo.widget.LoadImageView;
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
import com.hippo.yorozuya.collect.LongList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -319,6 +311,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
View content = ViewUtils.$$(view, R.id.content);
|
||||
mRecyclerView = (EasyRecyclerView) ViewUtils.$$(content, R.id.recycler_view);
|
||||
setLiftOnScrollTargetView(mRecyclerView);
|
||||
mFastScroller = (FastScroller) ViewUtils.$$(content, R.id.fast_scroller);
|
||||
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
|
||||
mTip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
@@ -425,7 +418,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_filter) {
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
new BaseDialogBuilder(requireActivity())
|
||||
.setSingleChoiceItems(R.array.download_state, mType + 1, (dialog, which) -> {
|
||||
mType = which - 1;
|
||||
updateForLabel();
|
||||
@@ -448,7 +441,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
openDrawer(Gravity.RIGHT);
|
||||
return true;
|
||||
} else if (id == R.id.action_reset_reading_progress) {
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
new BaseDialogBuilder(getContext())
|
||||
.setMessage(R.string.reset_reading_progress_message)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
@@ -521,7 +514,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
items[i + 2] = list.get(i).getLabel();
|
||||
}
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.default_download_label)
|
||||
.setItems(items, (dialog1, which) -> {
|
||||
if (which == 0) {
|
||||
@@ -708,7 +701,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
MoveDialogHelper helper = new MoveDialogHelper(labels, downloadInfoList);
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show();
|
||||
@@ -885,73 +878,6 @@ public class DownloadsScene extends ToolbarScene
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThumbDataContainer implements DataContainer {
|
||||
|
||||
private final DownloadInfo mInfo;
|
||||
@Nullable
|
||||
private UniFile mFile;
|
||||
|
||||
public ThumbDataContainer(@NonNull DownloadInfo info) {
|
||||
mInfo = info;
|
||||
}
|
||||
|
||||
private void ensureFile() {
|
||||
if (mFile == null) {
|
||||
UniFile dir = SpiderDen.getGalleryDownloadDir(mInfo);
|
||||
if (dir != null && dir.isDirectory()) {
|
||||
mFile = dir.createFile(".thumb");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
ensureFile();
|
||||
return mFile != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUrlMoved(String requestUrl, String responseUrl) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(InputStream is, long length, String mediaType, ProgressNotifier notify) {
|
||||
ensureFile();
|
||||
if (mFile == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = mFile.openOutputStream();
|
||||
IOUtils.copy(is, os);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(os);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStreamPipe get() {
|
||||
ensureFile();
|
||||
if (mFile != null) {
|
||||
return new UniFileInputStreamPipe(mFile);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (mFile != null) {
|
||||
mFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
@@ -1230,8 +1156,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
return;
|
||||
}
|
||||
DownloadInfo info = mList.get(position);
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb,
|
||||
new ThumbDataContainer(info), true);
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb, true);
|
||||
holder.title.setText(EhUtils.getSuitableTitle(info));
|
||||
holder.uploader.setText(info.uploader);
|
||||
holder.rating.setRating(info.rating);
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.Gravity;
|
||||
@@ -33,7 +33,6 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -42,10 +41,10 @@ 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.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.annotation.Implemented;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.drawable.AddDeleteDrawable;
|
||||
import com.hippo.drawable.DrawerArrowDrawable;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
@@ -342,6 +341,8 @@ public class FavoritesScene extends BaseScene implements
|
||||
mSearchBar.setEditTextHint(getString(R.string.favorites_search_bar_hint, favCatName));
|
||||
}
|
||||
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, favCat != FavListUrlBuilder.FAV_CAT_LOCAL);
|
||||
|
||||
mOldFavCat = favCatName;
|
||||
mOldKeyword = keyword;
|
||||
|
||||
@@ -398,7 +399,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
items[1] = getString(R.string.local_favorites);
|
||||
String[] favCat = Settings.getFavCat();
|
||||
System.arraycopy(favCat, 0, items, 2, 10);
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.default_favorites_collection)
|
||||
.setItems(items, (dialog, which) -> Settings.putDefaultFavSlot(which - 2)).show();
|
||||
return true;
|
||||
@@ -644,35 +645,13 @@ public class FavoritesScene extends BaseScene implements
|
||||
return;
|
||||
}
|
||||
|
||||
final int page = mHelper.getPageForTop();
|
||||
final int pages = mHelper.getPages();
|
||||
String hint = getString(R.string.go_to_hint, page + 1, pages);
|
||||
final EditTextDialogBuilder builder = new EditTextDialogBuilder(context, null, hint);
|
||||
builder.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
final AlertDialog dialog = builder.setTitle(R.string.go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (null == mHelper) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
String text = builder.getText().trim();
|
||||
int goTo;
|
||||
try {
|
||||
goTo = Integer.parseInt(text) - 1;
|
||||
} catch (NumberFormatException e) {
|
||||
builder.setError(getString(R.string.error_invalid_number));
|
||||
return;
|
||||
}
|
||||
if (goTo < 0 || goTo >= pages) {
|
||||
builder.setError(getString(R.string.error_out_of_range));
|
||||
return;
|
||||
}
|
||||
builder.setError(null);
|
||||
mHelper.goTo(goTo);
|
||||
dialog.dismiss();
|
||||
var datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setTitleText(R.string.go_to)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
mHelper.goTo(v);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -727,7 +706,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
case 4: { // Delete
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper();
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.delete_favorites_dialog_title)
|
||||
.setMessage(getString(R.string.delete_favorites_dialog_message, mModifyGiList.size()))
|
||||
.setPositiveButton(android.R.string.ok, helper)
|
||||
@@ -741,7 +720,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
String[] array = new String[11];
|
||||
array[0] = getString(R.string.local_favorites);
|
||||
System.arraycopy(Settings.getFavCat(), 0, array, 1, 10);
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.move_favorites_dialog_title)
|
||||
.setItems(array, helper)
|
||||
.setOnCancelListener(helper)
|
||||
@@ -850,7 +829,29 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
updateSearchBar();
|
||||
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
|
||||
assert mUrlBuilder != null;
|
||||
|
||||
int pages = 0;
|
||||
if (result.nextPage == null)
|
||||
pages = mHelper.pgCounter + 1;
|
||||
else
|
||||
pages = Integer.MAX_VALUE;
|
||||
|
||||
String prev = result.prevPage, next = result.nextPage;
|
||||
switch (mHelper.operation) {
|
||||
case -1:
|
||||
if (prev != null) mHelper.upperPage = prev;
|
||||
break;
|
||||
case 1:
|
||||
if (next != null) mHelper.lowerPage = next;
|
||||
break;
|
||||
default:
|
||||
mHelper.upperPage = prev;
|
||||
mHelper.lowerPage = next;
|
||||
break;
|
||||
}
|
||||
|
||||
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
|
||||
|
||||
if (mDrawerAdapter != null) {
|
||||
mDrawerAdapter.notifyDataSetChanged();
|
||||
@@ -1169,6 +1170,10 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
private class FavoritesHelper extends GalleryInfoContentHelper {
|
||||
public String upperPage = null;
|
||||
public String lowerPage = null;
|
||||
public int operation = 0;
|
||||
public int pgCounter = 0;
|
||||
|
||||
@Override
|
||||
protected void getPageData(final int taskId, int type, int page) {
|
||||
@@ -1176,6 +1181,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
if (null == activity || null == mUrlBuilder || null == mClient) {
|
||||
return;
|
||||
}
|
||||
pgCounter = page;
|
||||
|
||||
if (mEnableModify) {
|
||||
mEnableModify = false;
|
||||
@@ -1215,7 +1221,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
url = mUrlBuilder.build();
|
||||
}
|
||||
|
||||
mUrlBuilder.setIndex(page);
|
||||
mUrlBuilder.setNext(lowerPage);
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_MODIFY_FAVORITES);
|
||||
request.setCallback(new GetFavoritesListener(getContext(),
|
||||
@@ -1228,7 +1234,25 @@ public class FavoritesScene extends BaseScene implements
|
||||
final String keyword = mUrlBuilder.getKeyword();
|
||||
SimpleHandler.getInstance().post(() -> onGetFavoritesLocal(keyword, taskId));
|
||||
} else {
|
||||
mUrlBuilder.setIndex(page);
|
||||
String prev = null, next = null;
|
||||
if (jumpTo != null) {
|
||||
next = Integer.toString(minGid);
|
||||
operation = 0;
|
||||
} else if (page != 0) {
|
||||
if (page >= mHelper.getPageForTop()) {
|
||||
next = lowerPage;
|
||||
operation = 1;
|
||||
} else {
|
||||
prev = upperPage;
|
||||
operation = -1;
|
||||
}
|
||||
}
|
||||
mUrlBuilder.setPrev(prev);
|
||||
mUrlBuilder.setNext(next);
|
||||
|
||||
mUrlBuilder.setJumpTo(jumpTo);
|
||||
jumpTo = null;
|
||||
|
||||
String url = mUrlBuilder.build();
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||
@@ -1291,5 +1315,34 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClearData() {
|
||||
super.onClearData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeRefresh() {
|
||||
super.beforeRefresh();
|
||||
upperPage = null;
|
||||
lowerPage = null;
|
||||
operation = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable saveInstanceState(Parcelable superState) {
|
||||
Bundle bundle = (Bundle) super.saveInstanceState(superState);
|
||||
bundle.putString(KEY_UPPER_PAGE, upperPage);
|
||||
bundle.putString(KEY_LOWER_PAGE, lowerPage);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable restoreInstanceState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
upperPage = bundle.getString(KEY_UPPER_PAGE);
|
||||
lowerPage = bundle.getString(KEY_LOWER_PAGE);
|
||||
return super.restoreInstanceState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
@@ -72,7 +72,7 @@ import com.hippo.ehviewer.client.parser.VoteCommentParser;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.text.URLImageGetter;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.util.ClipboardUtilKt;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.util.TextUrl;
|
||||
@@ -170,8 +170,9 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
@Override
|
||||
public View onCreateViewWithToolbar(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_gallery_comments, container, false);
|
||||
ViewGroup view = (ViewGroup) inflater.inflate(R.layout.scene_gallery_comments, container, false);
|
||||
mRecyclerView = (EasyRecyclerView) ViewUtils.$$(view, R.id.recycler_view);
|
||||
setLiftOnScrollTargetView(mRecyclerView);
|
||||
TextView tip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
mEditPanel = ViewUtils.$$(view, R.id.edit_panel);
|
||||
mSendImage = (ImageView) ViewUtils.$$(mEditPanel, R.id.send);
|
||||
@@ -179,19 +180,21 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
|
||||
mFab = (FloatingActionButton) ViewUtils.$$(view, R.id.fab);
|
||||
mRefreshLayout = (SwipeRefreshLayout) ViewUtils.$$(view, R.id.refresh_layout);
|
||||
|
||||
|
||||
// Workaround for fab and edittext render out of screen
|
||||
view.removeView(mFabLayout);
|
||||
view.removeView((View) mEditPanel.getParent());
|
||||
assert container != null;
|
||||
container.addView(mFabLayout);
|
||||
container.addView((View) mEditPanel.getParent());
|
||||
|
||||
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
|
||||
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
|
||||
mEditPanel,
|
||||
mFabLayout
|
||||
));
|
||||
|
||||
mRefreshLayout.setColorSchemeResources(
|
||||
R.color.loading_indicator_red,
|
||||
R.color.loading_indicator_purple,
|
||||
R.color.loading_indicator_blue,
|
||||
R.color.loading_indicator_cyan,
|
||||
R.color.loading_indicator_green,
|
||||
R.color.loading_indicator_yellow);
|
||||
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(getTheme(), androidx.appcompat.R.attr.colorPrimary));
|
||||
mRefreshLayout.setOnRefreshListener(this);
|
||||
|
||||
Context context = requireContext();
|
||||
@@ -297,7 +300,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(context);
|
||||
context = builder.getContext();
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
EasyRecyclerView rv = (EasyRecyclerView) inflater.inflate(R.layout.dialog_recycler_view, null);
|
||||
@@ -354,15 +357,14 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
menuId.add(R.id.check_vote_status);
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
|
||||
if (which < 0 || which >= menuId.size()) {
|
||||
return;
|
||||
}
|
||||
int id = menuId.get(which);
|
||||
if (id == R.id.copy) {
|
||||
ClipboardUtil.addTextToClipboard(comment.comment);
|
||||
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
|
||||
ClipboardUtilKt.addTextToClipboard(requireActivity(), comment.comment, false);
|
||||
} else if (id == R.id.vote_up) {
|
||||
voteComment(comment.id, 1);
|
||||
} else if (id == R.id.vote_down) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
@@ -53,7 +54,6 @@ 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,10 +61,12 @@ import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
|
||||
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.CheckBoxDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
@@ -94,17 +96,21 @@ import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.Filter;
|
||||
import com.hippo.ehviewer.gallery.EhGalleryProvider;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.GalleryInfoBottomSheet;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
|
||||
import com.hippo.ehviewer.widget.GalleryRatingBar;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.text.URLImageGetter;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.AppHelper;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.util.ClipboardUtilKt;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.widget.AutoWrapLayout;
|
||||
@@ -115,7 +121,6 @@ import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
@@ -130,11 +135,9 @@ import java.util.List;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
public class GalleryDetailScene extends ToolbarScene implements View.OnClickListener,
|
||||
public class GalleryDetailScene extends CollapsingToolbarScene implements View.OnClickListener,
|
||||
com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener,
|
||||
View.OnLongClickListener {
|
||||
|
||||
public final static String KEY_ACTION = "action";
|
||||
public static final String ACTION_GALLERY_INFO = "action_gallery_info";
|
||||
public static final String ACTION_GID_TOKEN = "action_gid_token";
|
||||
@@ -160,14 +163,10 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
private ViewTransition mViewTransition;
|
||||
// Header
|
||||
@Nullable
|
||||
private FrameLayout mHeader;
|
||||
@Nullable
|
||||
private View mColorBg;
|
||||
private LinearLayout mHeader;
|
||||
@Nullable
|
||||
private LoadImageView mThumb;
|
||||
@Nullable
|
||||
private TextView mTitle;
|
||||
@Nullable
|
||||
private TextView mUploader;
|
||||
@Nullable
|
||||
private TextView mCategory;
|
||||
@@ -203,8 +202,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
@Nullable
|
||||
private RatingBar mRating;
|
||||
@Nullable
|
||||
private View mHeartGroup;
|
||||
@Nullable
|
||||
private TextView mHeart;
|
||||
@Nullable
|
||||
private TextView mHeartOutline;
|
||||
@@ -260,7 +257,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
result -> {
|
||||
if (result && mGalleryDetail != null) {
|
||||
TorrentListDialogHelper helper = new TorrentListDialogHelper();
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
Dialog dialog = new BaseDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.torrents)
|
||||
.setView(R.layout.dialog_torrent_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -427,6 +424,21 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteFileAsync(UniFile... files) {
|
||||
//noinspection deprecation
|
||||
new AsyncTask<UniFile, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(UniFile... params) {
|
||||
for (UniFile file : params) {
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance(), files);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -558,7 +570,8 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
View view = inflater.inflate(R.layout.scene_gallery_detail, container, false);
|
||||
|
||||
ViewGroup main = (ViewGroup) ViewUtils.$$(view, R.id.main);
|
||||
ScrollView mainView = (ScrollView) ViewUtils.$$(main, R.id.scroll_view);
|
||||
NestedScrollView mainView = (NestedScrollView) ViewUtils.$$(main, R.id.scroll_view);
|
||||
setLiftOnScrollTargetView(mainView);
|
||||
View progressView = ViewUtils.$$(main, R.id.progress_view);
|
||||
mTip = (TextView) ViewUtils.$$(main, R.id.tip);
|
||||
mViewTransition = new ViewTransition(mainView, progressView, mTip);
|
||||
@@ -571,10 +584,8 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mTip.setCompoundDrawables(null, drawable, null, null);
|
||||
mTip.setOnClickListener(this);
|
||||
|
||||
mHeader = (FrameLayout) ViewUtils.$$(mainView, R.id.header);
|
||||
mColorBg = ViewUtils.$$(mHeader, R.id.color_bg);
|
||||
mHeader = (LinearLayout) ViewUtils.$$(mainView, R.id.header);
|
||||
mThumb = (LoadImageView) ViewUtils.$$(mHeader, R.id.thumb);
|
||||
mTitle = (TextView) ViewUtils.$$(mHeader, R.id.title);
|
||||
mUploader = (TextView) ViewUtils.$$(mHeader, R.id.uploader);
|
||||
mCategory = (TextView) ViewUtils.$$(mHeader, R.id.category);
|
||||
mActionGroup = (ViewGroup) ViewUtils.$$(mHeader, R.id.action_card);
|
||||
@@ -604,9 +615,8 @@ 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);
|
||||
mHeartGroup = ViewUtils.$$(mActions, R.id.heart_group);
|
||||
mHeart = (TextView) ViewUtils.$$(mHeartGroup, R.id.heart);
|
||||
mHeartOutline = (TextView) ViewUtils.$$(mHeartGroup, R.id.heart_outline);
|
||||
mHeart = (TextView) ViewUtils.$$(mActions, R.id.heart);
|
||||
mHeartOutline = (TextView) ViewUtils.$$(mActions, 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);
|
||||
@@ -614,15 +624,16 @@ 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);
|
||||
mHeartGroup.setOnClickListener(this);
|
||||
mHeartGroup.setOnLongClickListener(this);
|
||||
mHeart.setOnClickListener(this);
|
||||
mHeart.setOnLongClickListener(this);
|
||||
mHeartOutline.setOnClickListener(this);
|
||||
mHeartOutline.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);
|
||||
@@ -643,11 +654,9 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
if (prepareData()) {
|
||||
if (mGalleryDetail != null) {
|
||||
bindViewSecond();
|
||||
setTransitionName();
|
||||
adjustViewVisibility(STATE_NORMAL, false);
|
||||
} else if (mGalleryInfo != null) {
|
||||
bindViewFirst();
|
||||
setTransitionName();
|
||||
adjustViewVisibility(STATE_REFRESH_HEADER, false);
|
||||
} else {
|
||||
adjustViewVisibility(STATE_REFRESH, false);
|
||||
@@ -674,9 +683,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mViewTransition = null;
|
||||
|
||||
mHeader = null;
|
||||
mColorBg = null;
|
||||
mThumb = null;
|
||||
mTitle = null;
|
||||
mUploader = null;
|
||||
mCategory = null;
|
||||
mActionGroup = null;
|
||||
@@ -695,7 +702,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mNewerVersion = null;
|
||||
mRatingText = null;
|
||||
mRating = null;
|
||||
mHeartGroup = null;
|
||||
mHeart = null;
|
||||
mHeartOutline = null;
|
||||
mTorrent = null;
|
||||
@@ -769,52 +775,6 @@ 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;
|
||||
}
|
||||
|
||||
int w = mColorBg.getWidth();
|
||||
int h = mColorBg.getHeight();
|
||||
if (ViewCompat.isAttachedToWindow(mColorBg) && w != 0 && h != 0) {
|
||||
Resources resources = getContext().getResources();
|
||||
int keylineMargin = resources.getDimensionPixelSize(R.dimen.keyline_margin);
|
||||
int thumbWidth = resources.getDimensionPixelSize(R.dimen.gallery_detail_thumb_width);
|
||||
int thumbHeight = resources.getDimensionPixelSize(R.dimen.gallery_detail_thumb_height);
|
||||
|
||||
int x = thumbWidth / 2 + keylineMargin;
|
||||
int y = thumbHeight / 2 + keylineMargin;
|
||||
|
||||
int radiusX = Math.max(Math.abs(x), Math.abs(w - x));
|
||||
int radiusY = Math.max(Math.abs(y), Math.abs(h - y));
|
||||
float radius = (float) Math.hypot(radiusX, radiusY);
|
||||
|
||||
ViewAnimationUtils.createCircularReveal(mColorBg, x, y, 0, radius).setDuration(300).start();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustViewVisibility(int state, boolean animation) {
|
||||
if (state == mState) {
|
||||
return;
|
||||
@@ -852,27 +812,20 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mViewTransition.showView(2, animation);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((oldState == STATE_INIT || oldState == STATE_FAILED || oldState == STATE_REFRESH) &&
|
||||
(state == STATE_NORMAL || state == STATE_REFRESH_HEADER) && ResourcesKt.resolveBoolean(getTheme(), androidx.appcompat.R.attr.isLightTheme, false)) {
|
||||
if (!createCircularReveal()) {
|
||||
SimpleHandler.getInstance().post(this::createCircularReveal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bindViewFirst() {
|
||||
if (mGalleryDetail != null) {
|
||||
return;
|
||||
}
|
||||
if (mThumb == null || mTitle == null || mUploader == null || mCategory == null) {
|
||||
if (mThumb == null || mUploader == null || mCategory == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ACTION_GALLERY_INFO.equals(mAction) && mGalleryInfo != null) {
|
||||
GalleryInfo gi = mGalleryInfo;
|
||||
mThumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
|
||||
mTitle.setText(EhUtils.getSuitableTitle(gi));
|
||||
setTitle(EhUtils.getSuitableTitle(gi));
|
||||
mUploader.setText(gi.uploader);
|
||||
mUploader.setAlpha(gi.disowned ? .5f : 1f);
|
||||
mCategory.setText(EhUtils.getCategory(gi.category));
|
||||
@@ -920,7 +873,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
})
|
||||
.show();
|
||||
}
|
||||
if (mThumb == null || mTitle == null || mUploader == null || mCategory == null ||
|
||||
if (mThumb == null || mUploader == null || mCategory == null ||
|
||||
mLanguage == null || mPages == null || mSize == null || mPosted == null ||
|
||||
mFavoredTimes == null || mRatingText == null || mRating == null || mTorrent == null || mNewerVersion == null) {
|
||||
return;
|
||||
@@ -929,7 +882,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
Resources resources = getResources();
|
||||
|
||||
mThumb.load(EhCacheKeyFactory.getThumbKey(gd.gid), gd.thumb);
|
||||
mTitle.setText(EhUtils.getSuitableTitle(gd));
|
||||
setTitle(EhUtils.getSuitableTitle(gd));
|
||||
mUploader.setText(gd.uploader);
|
||||
mUploader.setAlpha(gd.disowned ? .5f : 1f);
|
||||
mCategory.setText(EhUtils.getCategory(gd.category));
|
||||
@@ -1097,10 +1050,8 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
private void setTransitionName() {
|
||||
long gid = getGid();
|
||||
|
||||
if (gid != -1 && mThumb != null &&
|
||||
mTitle != null && mUploader != null && mCategory != null) {
|
||||
if (gid != -1 && mThumb != null && mUploader != null && mCategory != null) {
|
||||
ViewCompat.setTransitionName(mThumb, TransitionNameFactory.getThumbTransitionName(gid));
|
||||
ViewCompat.setTransitionName(mTitle, TransitionNameFactory.getTitleTransitionName(gid));
|
||||
ViewCompat.setTransitionName(mUploader, TransitionNameFactory.getUploaderTransitionName(gid));
|
||||
ViewCompat.setTransitionName(mCategory, TransitionNameFactory.getCategoryTransitionName(gid));
|
||||
}
|
||||
@@ -1158,7 +1109,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
lub.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
||||
lub.setImagePath(temp.getPath());
|
||||
lub.setUseSimilarityScan(true);
|
||||
lub.setShowExpunged(true);
|
||||
GalleryListScene.startScene(this, lub);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
@@ -1203,10 +1153,14 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
if (EhApplication.getDownloadManager(context).getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
|
||||
CommonOperations.startDownload(activity, galleryInfo, false);
|
||||
} else {
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.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))
|
||||
CheckBoxDialogBuilder builder = new CheckBoxDialogBuilder(context,
|
||||
getString(R.string.download_remove_dialog_message, galleryInfo.title),
|
||||
getString(R.string.download_remove_dialog_check_text),
|
||||
Settings.getRemoveImageFiles());
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper(
|
||||
EhApplication.getDownloadManager(context), galleryInfo, builder);
|
||||
builder.setTitle(R.string.download_remove_dialog_title)
|
||||
.setPositiveButton(android.R.string.ok, helper)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -1229,7 +1183,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
for (GalleryInfo newerVersion : mGalleryDetail.newerVersions) {
|
||||
titles.add(getString(R.string.newer_version_title, newerVersion.title, newerVersion.posted));
|
||||
}
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
new BaseDialogBuilder(requireContext())
|
||||
.setItems(titles.toArray(new CharSequence[0]), (dialog, which) -> {
|
||||
GalleryInfo newerVersion = mGalleryDetail.newerVersions.get(which);
|
||||
Bundle args = new Bundle();
|
||||
@@ -1241,10 +1195,10 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
.show();
|
||||
}
|
||||
} else if (mInfo == v) {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(GalleryInfoScene.KEY_GALLERY_DETAIL, mGalleryDetail);
|
||||
startScene(new Announcer(GalleryInfoScene.class).setArgs(args));
|
||||
} else if (mHeartGroup == v) {
|
||||
assert mGalleryDetail != null;
|
||||
var galleryInfoBottomSheet = new GalleryInfoBottomSheet(mGalleryDetail);
|
||||
galleryInfoBottomSheet.show(requireActivity().getSupportFragmentManager(), GalleryInfoBottomSheet.TAG);
|
||||
} else if (mHeart == v || mHeartOutline == v) {
|
||||
if (mGalleryDetail != null && !mModifingFavorites) {
|
||||
boolean remove = false;
|
||||
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
|
||||
@@ -1275,7 +1229,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
requestStoragePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
} else {
|
||||
TorrentListDialogHelper helper = new TorrentListDialogHelper();
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
Dialog dialog = new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.torrents)
|
||||
.setView(R.layout.dialog_torrent_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -1292,7 +1246,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
ArchiveListDialogHelper helper = new ArchiveListDialogHelper();
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
Dialog dialog = new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.dialog_archive_title)
|
||||
.setView(R.layout.dialog_archive_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -1307,7 +1261,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
RateDialogHelper helper = new RateDialogHelper();
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
Dialog dialog = new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.rate)
|
||||
.setView(R.layout.dialog_rate)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
@@ -1369,7 +1323,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setMessage(getString(R.string.filter_the_uploader, uploader))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
Filter filter = new Filter();
|
||||
@@ -1389,7 +1343,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setMessage(getString(R.string.filter_the_tag, tag))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
Filter filter = new Filter();
|
||||
@@ -1438,7 +1392,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
menuId.add(R.id.vote_down);
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(tag)
|
||||
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
|
||||
if (which < 0 || which >= menuId.size()) {
|
||||
@@ -1454,11 +1408,9 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
} else if (id == R.id.add_filter) {
|
||||
showFilterTagDialog(tag);
|
||||
} else if (id == R.id.copy) {
|
||||
ClipboardUtil.addTextToClipboard(tag);
|
||||
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
|
||||
ClipboardUtilKt.addTextToClipboard(requireActivity(), tag, false);
|
||||
} else if (id == R.id.copy_trans) {
|
||||
ClipboardUtil.addTextToClipboard(tv.getText().toString());
|
||||
showTip(R.string.copied_to_clipboard, LENGTH_SHORT);
|
||||
ClipboardUtilKt.addTextToClipboard(requireActivity(), tv.getText().toString(), false);
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
@@ -1493,7 +1445,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
CommonOperations.startDownload(activity, galleryInfo, true);
|
||||
}
|
||||
return true;
|
||||
} else if (mHeartGroup == v) {
|
||||
} else if (mHeart == v || mHeartOutline == v) {
|
||||
if (mGalleryDetail != null && !mModifingFavorites) {
|
||||
boolean remove = false;
|
||||
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
|
||||
@@ -2161,4 +2113,37 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
}
|
||||
}
|
||||
private class DeleteDialogHelper implements DialogInterface.OnClickListener{
|
||||
private final com.hippo.ehviewer.download.DownloadManager mDownloadManager;
|
||||
private final GalleryInfo mGalleryInfo;
|
||||
private final CheckBoxDialogBuilder mBuilder;
|
||||
|
||||
public DeleteDialogHelper(com.hippo.ehviewer.download.DownloadManager downloadManager,
|
||||
GalleryInfo galleryInfo, CheckBoxDialogBuilder builder) {
|
||||
mDownloadManager = downloadManager;
|
||||
mGalleryInfo = galleryInfo;
|
||||
mBuilder = builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which != DialogInterface.BUTTON_POSITIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete
|
||||
if (null != mDownloadManager) {
|
||||
mDownloadManager.deleteDownload(mGalleryInfo.gid);
|
||||
}
|
||||
|
||||
// Delete image files
|
||||
boolean checked = mBuilder.isChecked();
|
||||
Settings.putRemoveImageFiles(checked);
|
||||
if (checked) {
|
||||
EhDB.removeDownloadDirname(mGalleryInfo.gid);
|
||||
UniFile file = SpiderDen.getGalleryDownloadDir(mGalleryInfo);
|
||||
deleteFileAsync(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.ehviewer.ui.scene;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.UrlOpener;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class GalleryInfoScene extends ToolbarScene {
|
||||
|
||||
public static final String KEY_GALLERY_DETAIL = "gallery_detail";
|
||||
public static final String KEY_KEYS = "keys";
|
||||
public static final String KEY_VALUES = "values";
|
||||
|
||||
private static final int INDEX_URL = 3;
|
||||
private static final int INDEX_PARENT = 10;
|
||||
|
||||
/*---------------
|
||||
Whole life cycle
|
||||
---------------*/
|
||||
@Nullable
|
||||
private ArrayList<String> mKeys;
|
||||
@Nullable
|
||||
private ArrayList<String> mValues;
|
||||
|
||||
@Nullable
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
onInit();
|
||||
} else {
|
||||
onRestore(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlerArgs(Bundle args) {
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
GalleryDetail gd = args.getParcelable(KEY_GALLERY_DETAIL);
|
||||
if (gd == null) {
|
||||
return;
|
||||
}
|
||||
if (mKeys == null || mValues == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mKeys.add(getString(R.string.header_key));
|
||||
mValues.add(getString(R.string.header_value));
|
||||
mKeys.add(getString(R.string.key_gid));
|
||||
mValues.add(Long.toString(gd.gid));
|
||||
mKeys.add(getString(R.string.key_token));
|
||||
mValues.add(gd.token);
|
||||
mKeys.add(getString(R.string.key_url));
|
||||
mValues.add(EhUrl.getGalleryDetailUrl(gd.gid, gd.token));
|
||||
mKeys.add(getString(R.string.key_title));
|
||||
mValues.add(gd.title);
|
||||
mKeys.add(getString(R.string.key_title_jpn));
|
||||
mValues.add(gd.titleJpn);
|
||||
mKeys.add(getString(R.string.key_thumb));
|
||||
mValues.add(gd.thumb);
|
||||
mKeys.add(getString(R.string.key_category));
|
||||
mValues.add(EhUtils.getCategory(gd.category));
|
||||
mKeys.add(getString(R.string.key_uploader));
|
||||
mValues.add(gd.uploader);
|
||||
mKeys.add(getString(R.string.key_posted));
|
||||
mValues.add(gd.posted);
|
||||
mKeys.add(getString(R.string.key_parent));
|
||||
mValues.add(gd.parent);
|
||||
mKeys.add(getString(R.string.key_visible));
|
||||
mValues.add(gd.visible);
|
||||
mKeys.add(getString(R.string.key_language));
|
||||
mValues.add(gd.language);
|
||||
mKeys.add(getString(R.string.key_pages));
|
||||
mValues.add(Integer.toString(gd.pages));
|
||||
mKeys.add(getString(R.string.key_size));
|
||||
mValues.add(gd.size);
|
||||
mKeys.add(getString(R.string.key_favorite_count));
|
||||
mValues.add(Integer.toString(gd.favoriteCount));
|
||||
mKeys.add(getString(R.string.key_favorited));
|
||||
mValues.add(Boolean.toString(gd.isFavorited));
|
||||
mKeys.add(getString(R.string.key_rating_count));
|
||||
mValues.add(Integer.toString(gd.ratingCount));
|
||||
mKeys.add(getString(R.string.key_rating));
|
||||
mValues.add(Float.toString(gd.rating));
|
||||
mKeys.add(getString(R.string.key_torrents));
|
||||
mValues.add(Integer.toString(gd.torrentCount));
|
||||
mKeys.add(getString(R.string.key_torrent_url));
|
||||
mValues.add(gd.torrentUrl);
|
||||
mKeys.add(getString(R.string.favorite_name));
|
||||
mValues.add(gd.favoriteName);
|
||||
}
|
||||
|
||||
protected void onInit() {
|
||||
mKeys = new ArrayList<>();
|
||||
mValues = new ArrayList<>();
|
||||
handlerArgs(getArguments());
|
||||
}
|
||||
|
||||
protected void onRestore(@NonNull Bundle savedInstanceState) {
|
||||
mKeys = savedInstanceState.getStringArrayList(KEY_KEYS);
|
||||
mValues = savedInstanceState.getStringArrayList(KEY_VALUES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putStringArrayList(KEY_KEYS, mKeys);
|
||||
outState.putStringArrayList(KEY_VALUES, mValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateViewWithToolbar(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_gallery_info, container, false);
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
|
||||
mRecyclerView = (EasyRecyclerView) ViewUtils.$$(view, R.id.recycler_view);
|
||||
InfoAdapter adapter = new InfoAdapter();
|
||||
mRecyclerView.setAdapter(adapter);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.VERTICAL, false));
|
||||
mRecyclerView.setClipToPadding(false);
|
||||
mRecyclerView.setHasFixedSize(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setTitle(R.string.gallery_info);
|
||||
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView.stopScroll();
|
||||
mRecyclerView = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onItemClick(int position) {
|
||||
Context context = getContext();
|
||||
if (null != context && 0 != position && null != mValues) {
|
||||
if (position == INDEX_PARENT) {
|
||||
UrlOpener.openUrl(context, mValues.get(position), true);
|
||||
} else {
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigationClick() {
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
private static class InfoHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final TextView key;
|
||||
private final TextView value;
|
||||
|
||||
public InfoHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
key = itemView.findViewById(R.id.key);
|
||||
value = itemView.findViewById(R.id.value);
|
||||
}
|
||||
}
|
||||
|
||||
private class InfoAdapter extends RecyclerView.Adapter<InfoHolder> {
|
||||
|
||||
private static final int TYPE_HEADER = 0;
|
||||
private static final int TYPE_DATA = 1;
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public InfoAdapter() {
|
||||
mInflater = getLayoutInflater();
|
||||
AssertUtils.assertNotNull(mInflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == 0) {
|
||||
return TYPE_HEADER;
|
||||
} else {
|
||||
return TYPE_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public InfoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new InfoHolder(mInflater.inflate(viewType == TYPE_HEADER ?
|
||||
R.layout.item_gallery_info_header : R.layout.item_gallery_info_data, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull InfoHolder holder, int position) {
|
||||
if (mKeys != null && mValues != null) {
|
||||
holder.key.setText(mKeys.get(position));
|
||||
holder.value.setText(mValues.get(position));
|
||||
holder.itemView.setEnabled(position != 0);
|
||||
holder.itemView.setOnClickListener(v -> onItemClick(position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mKeys == null || mValues == null ? 0 : Math.min(mKeys.size(), mValues.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,8 +55,9 @@ 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.datepicker.MaterialDatePicker;
|
||||
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,6 +92,7 @@ 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;
|
||||
@@ -134,8 +136,6 @@ public final class GalleryListScene extends BaseScene
|
||||
private final static int STATE_SEARCH = 2;
|
||||
private final static int STATE_SEARCH_SHOW_LIST = 3;
|
||||
private static final long ANIMATE_TIME = 300L;
|
||||
// Double click back exit
|
||||
private final long mPressBackTime = 0;
|
||||
/*---------------
|
||||
Whole life cycle
|
||||
---------------*/
|
||||
@@ -216,6 +216,8 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
};
|
||||
// Double click back exit
|
||||
private final long mPressBackTime = 0;
|
||||
private boolean mHasFirstRefresh = false;
|
||||
private int mNavCheckedId = 0;
|
||||
private DownloadManager mDownloadManager;
|
||||
@@ -491,6 +493,8 @@ public final class GalleryListScene extends BaseScene
|
||||
mFabLayout.getSecondaryFabAt(0).setImageResource(isTopList ? R.drawable.ic_baseline_format_list_numbered_24 : R.drawable.v_magnify_x24);
|
||||
}
|
||||
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, ListUrlBuilder.MODE_WHATS_HOT != builder.getMode());
|
||||
|
||||
// Update normal search mode
|
||||
mSearchLayout.setNormalSearchMode(builder.getMode() == ListUrlBuilder.MODE_SUBSCRIPTION
|
||||
? R.id.search_subscription_search
|
||||
@@ -663,7 +667,7 @@ public final class GalleryListScene extends BaseScene
|
||||
if (null == context) {
|
||||
return;
|
||||
}
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||
final BaseDialogBuilder builder = new BaseDialogBuilder(context);
|
||||
builder.setMessage(R.string.add_quick_search_tip);
|
||||
builder.setTitle(R.string.readme);
|
||||
builder.show();
|
||||
@@ -778,6 +782,21 @@ 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()) {
|
||||
@@ -841,36 +860,47 @@ public final class GalleryListScene extends BaseScene
|
||||
return;
|
||||
}
|
||||
|
||||
final int page = mHelper.getPageForTop();
|
||||
final int pages = mHelper.getPages();
|
||||
String hint = getString(R.string.go_to_hint, page + 1, pages);
|
||||
final EditTextDialogBuilder builder = new EditTextDialogBuilder(context, null, hint);
|
||||
builder.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
final AlertDialog dialog = builder.setTitle(R.string.go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (null == mHelper) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
if (ListUrlBuilder.MODE_TOPLIST == mUrlBuilder.getMode()) {
|
||||
final int page = mHelper.getPageForTop() + 1;
|
||||
final int pages = 200;
|
||||
String hint = getString(R.string.go_to_hint, page, pages);
|
||||
final EditTextDialogBuilder builder = new EditTextDialogBuilder(context, null, hint);
|
||||
builder.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
final AlertDialog dialog = builder.setTitle(R.string.go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (null == mHelper) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
String text = builder.getText().trim();
|
||||
int goTo;
|
||||
try {
|
||||
goTo = Integer.parseInt(text) - 1;
|
||||
} catch (NumberFormatException e) {
|
||||
builder.setError(getString(R.string.error_invalid_number));
|
||||
return;
|
||||
}
|
||||
if (goTo < 0 || goTo >= pages) {
|
||||
builder.setError(getString(R.string.error_out_of_range));
|
||||
return;
|
||||
}
|
||||
builder.setError(null);
|
||||
mHelper.goTo(goTo);
|
||||
dialog.dismiss();
|
||||
});
|
||||
String text = builder.getText().trim();
|
||||
int goTo;
|
||||
try {
|
||||
goTo = Integer.parseInt(text) - 1;
|
||||
} catch (NumberFormatException e) {
|
||||
builder.setError(getString(R.string.error_invalid_number));
|
||||
return;
|
||||
}
|
||||
if (goTo < 0 || goTo >= pages) {
|
||||
builder.setError(getString(R.string.error_out_of_range));
|
||||
return;
|
||||
}
|
||||
builder.setError(null);
|
||||
mHelper.goToPage(goTo);
|
||||
dialog.dismiss();
|
||||
});
|
||||
} else {
|
||||
var datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setTitleText(R.string.go_to)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
mHelper.goTo(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -950,7 +980,7 @@ public final class GalleryListScene extends BaseScene
|
||||
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
|
||||
};
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(EhUtils.getSuitableTitle(gi))
|
||||
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
|
||||
switch (which) {
|
||||
@@ -962,7 +992,7 @@ public final class GalleryListScene extends BaseScene
|
||||
break;
|
||||
case 1: // Download
|
||||
if (downloaded) {
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
.setMessage(getString(R.string.download_remove_dialog_message, gi.title))
|
||||
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> mDownloadManager.deleteDownload(gi.gid))
|
||||
@@ -989,7 +1019,7 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show();
|
||||
@@ -1184,6 +1214,7 @@ public final class GalleryListScene extends BaseScene
|
||||
break;
|
||||
}
|
||||
}
|
||||
((StageActivity)requireActivity()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1374,7 +1405,16 @@ public final class GalleryListScene extends BaseScene
|
||||
? R.string.gallery_list_empty_hit_subscription
|
||||
: R.string.gallery_list_empty_hit);
|
||||
mHelper.setEmptyString(emptyString);
|
||||
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
|
||||
|
||||
int pages = 0;
|
||||
if (mIsTopList)
|
||||
pages = 200;
|
||||
else if (result.nextGid == 0)
|
||||
pages = mHelper.pgCounter + 1;
|
||||
else
|
||||
pages = result.founds;
|
||||
|
||||
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,7 +1587,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
mUrlBuilder.set(mQuickSearchList.get(position));
|
||||
mUrlBuilder.setPageIndex(0);
|
||||
mUrlBuilder.setNextGid(0);
|
||||
onUpdateUrlBuilder();
|
||||
mHelper.refresh();
|
||||
setState(STATE_NORMAL);
|
||||
@@ -1564,7 +1604,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
mUrlBuilder.setKeyword(String.valueOf(keywords[position]));
|
||||
mUrlBuilder.setPageIndex(0);
|
||||
mUrlBuilder.setNextGid(0);
|
||||
onUpdateUrlBuilder();
|
||||
mHelper.refresh();
|
||||
setState(STATE_NORMAL);
|
||||
@@ -1694,15 +1734,37 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
private class GalleryListHelper extends GalleryInfoContentHelper {
|
||||
public int pgCounter = 0;
|
||||
|
||||
@Override
|
||||
protected void getPageData(int taskId, int type, int page) {
|
||||
pgCounter = page;
|
||||
MainActivity activity = getMainActivity();
|
||||
if (null == activity || null == mClient || null == mUrlBuilder) {
|
||||
return;
|
||||
}
|
||||
|
||||
mUrlBuilder.setPageIndex(page);
|
||||
if (mIsTopList) {
|
||||
mUrlBuilder.setNextGid(page);
|
||||
if (jumpTo != null) {
|
||||
pgCounter = Integer.parseInt(jumpTo);
|
||||
mUrlBuilder.setNextGid(pgCounter);
|
||||
}
|
||||
} else {
|
||||
int prevGid = 0, nextGid = 0;
|
||||
if (page != 0) {
|
||||
if (page >= mHelper.getPageForTop())
|
||||
nextGid = minGid;
|
||||
else
|
||||
prevGid = maxGid;
|
||||
}
|
||||
mUrlBuilder.setPrevGid(prevGid);
|
||||
mUrlBuilder.setNextGid(nextGid);
|
||||
}
|
||||
|
||||
mUrlBuilder.setJumpTo(jumpTo);
|
||||
jumpTo = null;
|
||||
|
||||
if (ListUrlBuilder.MODE_IMAGE_SEARCH == mUrlBuilder.getMode()) {
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_IMAGE_SEARCH);
|
||||
@@ -1815,4 +1877,4 @@ public final class GalleryListScene extends BaseScene
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -45,6 +45,7 @@ import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
import com.hippo.ehviewer.client.EhRequest;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview;
|
||||
import com.hippo.ehviewer.client.data.PreviewSet;
|
||||
@@ -139,6 +140,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
R.layout.scene_gallery_previews, container, false);
|
||||
mContentLayout.hideFastScroll();
|
||||
mRecyclerView = mContentLayout.getRecyclerView();
|
||||
setLiftOnScrollTargetView(mRecyclerView);
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
@@ -189,11 +191,8 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setTitle(R.string.gallery_previews);
|
||||
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuResId() {
|
||||
return R.menu.scene_gallery_previews;
|
||||
if (((GalleryDetail)mGalleryInfo).previewPages > 2)
|
||||
showMenu(R.menu.scene_gallery_previews);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -211,7 +210,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
int pages = mHelper.getPages();
|
||||
if (pages > 1 && mHelper.canGoTo()) {
|
||||
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setTitle(R.string.go_to)
|
||||
AlertDialog dialog = new BaseDialogBuilder(context).setTitle(R.string.go_to)
|
||||
.setView(R.layout.dialog_go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
|
||||
@@ -25,7 +25,6 @@ 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
|
||||
@@ -35,11 +34,9 @@ import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
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
|
||||
@@ -63,18 +60,58 @@ 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
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
private val mAdapter: HistoryAdapter by lazy {
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
private val mDownloadManager: DownloadManager by lazy {
|
||||
EhApplication.getDownloadManager(requireContext())
|
||||
}
|
||||
private val mDownloadInfoListener: DownloadInfoListener by lazy {
|
||||
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() {}
|
||||
}
|
||||
}
|
||||
private val mFavouriteStatusRouter: FavouriteStatusRouter by lazy {
|
||||
EhApplication.getFavouriteStatusRouter(requireContext())
|
||||
}
|
||||
private val mFavouriteStatusRouterListener: FavouriteStatusRouter.Listener by lazy {
|
||||
FavouriteStatusRouter.Listener { _: Long, _: Int ->
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mDownloadManager!!.removeDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouter!!.removeListener(mFavouriteStatusRouterListener)
|
||||
mDownloadManager.removeDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener)
|
||||
}
|
||||
|
||||
override fun getNavCheckedItem(): Int {
|
||||
@@ -84,48 +121,21 @@ class HistoryScene : ToolbarScene() {
|
||||
@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)
|
||||
mDownloadManager.addDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouter.addListener(mFavouriteStatusRouterListener)
|
||||
}
|
||||
|
||||
override fun onCreateViewWithToolbar(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
): 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 recyclerView = ViewUtils.`$$`(content, R.id.recycler_view) as RecyclerView
|
||||
setLiftOnScrollTargetView(recyclerView)
|
||||
val mFastScroller = ViewUtils.`$$`(content, R.id.fast_scroller) as FastScroller
|
||||
val mTip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
mViewTransition = ViewTransition(content, mTip)
|
||||
ViewTransition(content, mTip)
|
||||
val resources = requireContext().resources
|
||||
val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.big_history)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
@@ -135,15 +145,6 @@ class HistoryScene : ToolbarScene() {
|
||||
) {
|
||||
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
|
||||
@@ -153,11 +154,6 @@ class HistoryScene : ToolbarScene() {
|
||||
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)
|
||||
@@ -167,7 +163,7 @@ class HistoryScene : ToolbarScene() {
|
||||
mFastScroller.setHandlerDrawable(handlerDrawable)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
historyData.collectLatest { value ->
|
||||
mAdapter!!.submitData(
|
||||
mAdapter.submitData(
|
||||
value
|
||||
)
|
||||
}
|
||||
@@ -185,8 +181,6 @@ class HistoryScene : ToolbarScene() {
|
||||
super.onDestroyView()
|
||||
mRecyclerView?.stopScroll()
|
||||
mRecyclerView = null
|
||||
mViewTransition = null
|
||||
mAdapter = null
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
@@ -200,23 +194,21 @@ class HistoryScene : ToolbarScene() {
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun showClearAllDialog() {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
BaseDialogBuilder(requireContext())
|
||||
.setMessage(R.string.clear_all_history)
|
||||
.setPositiveButton(R.string.clear_all) { _: DialogInterface?, which: Int ->
|
||||
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
|
||||
if (DialogInterface.BUTTON_POSITIVE != which) {
|
||||
return@setPositiveButton
|
||||
}
|
||||
EhDB.clearHistoryInfo()
|
||||
mAdapter!!.refresh()
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
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()
|
||||
@@ -225,20 +217,19 @@ class HistoryScene : ToolbarScene() {
|
||||
return false
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
fun onItemClick(gi: GalleryInfo): Boolean {
|
||||
val args = Bundle()
|
||||
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO)
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, mAdapter?.peek(position))
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, gi)
|
||||
val announcer = Announcer(GalleryDetailScene::class.java).setArgs(args)
|
||||
startScene(announcer)
|
||||
return true
|
||||
}
|
||||
|
||||
fun onItemLongClick(position: Int): Boolean {
|
||||
fun onItemLongClick(gi: GalleryInfo): 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 downloaded = mDownloadManager.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID
|
||||
val favourited = gi.favoriteSlot != -2
|
||||
val items = if (downloaded) arrayOf<CharSequence>(
|
||||
context.getString(R.string.read),
|
||||
@@ -260,7 +251,7 @@ class HistoryScene : ToolbarScene() {
|
||||
R.drawable.v_download_x24,
|
||||
if (favourited) R.drawable.v_heart_broken_x24 else R.drawable.v_heart_x24,
|
||||
)
|
||||
MaterialAlertDialogBuilder(context)
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(EhUtils.getSuitableTitle(gi))
|
||||
.setAdapter(
|
||||
SelectItemWithIconAdapter(
|
||||
@@ -276,8 +267,9 @@ class HistoryScene : ToolbarScene() {
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
1 -> if (downloaded) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
.setMessage(
|
||||
getString(
|
||||
@@ -286,7 +278,7 @@ class HistoryScene : ToolbarScene() {
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
mDownloadManager!!.deleteDownload(
|
||||
mDownloadManager.deleteDownload(
|
||||
gi.gid
|
||||
)
|
||||
}
|
||||
@@ -294,6 +286,7 @@ class HistoryScene : ToolbarScene() {
|
||||
} else {
|
||||
CommonOperations.startDownload(activity, gi, false)
|
||||
}
|
||||
|
||||
2 -> if (favourited) {
|
||||
CommonOperations.removeFromFavorites(
|
||||
activity,
|
||||
@@ -307,6 +300,7 @@ class HistoryScene : ToolbarScene() {
|
||||
AddToFavoriteListener(context, activity.stageId, tag)
|
||||
)
|
||||
}
|
||||
|
||||
3 -> {
|
||||
val labelRawList = EhApplication.getDownloadManager(context).labelList
|
||||
val labelList: MutableList<String> = ArrayList(labelRawList.size + 1)
|
||||
@@ -319,7 +313,7 @@ class HistoryScene : ToolbarScene() {
|
||||
}
|
||||
val labels = labelList.toTypedArray()
|
||||
val helper = MoveDialogHelper(labels, gi)
|
||||
MaterialAlertDialogBuilder(context)
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show()
|
||||
@@ -362,29 +356,16 @@ class HistoryScene : ToolbarScene() {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
val card: View = itemView.findViewById(R.id.card)
|
||||
val thumb: LoadImageView = itemView.findViewById(R.id.thumb)
|
||||
val title: TextView = itemView.findViewById(R.id.title)
|
||||
val uploader: TextView = itemView.findViewById(R.id.uploader)
|
||||
val rating: SimpleRatingView = itemView.findViewById(R.id.rating)
|
||||
val category: TextView = itemView.findViewById(R.id.category)
|
||||
val posted: TextView = itemView.findViewById(R.id.posted)
|
||||
val simpleLanguage: TextView = itemView.findViewById(R.id.simple_language)
|
||||
val pages: TextView = itemView.findViewById(R.id.pages)
|
||||
val downloaded: ImageView = itemView.findViewById(R.id.downloaded)
|
||||
}
|
||||
|
||||
private inner class MoveDialogHelper(
|
||||
@@ -392,7 +373,6 @@ class HistoryScene : ToolbarScene() {
|
||||
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]
|
||||
@@ -432,7 +412,7 @@ class HistoryScene : ToolbarScene() {
|
||||
holder.rating.rating = gi.rating
|
||||
val category = holder.category
|
||||
val newCategoryText = EhUtils.getCategory(gi.category)
|
||||
if (!newCategoryText!!.contentEquals(category.text)) {
|
||||
if (!newCategoryText.contentEquals(category.text)) {
|
||||
category.text = newCategoryText
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category))
|
||||
}
|
||||
@@ -447,16 +427,9 @@ class HistoryScene : ToolbarScene() {
|
||||
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) }
|
||||
if (mDownloadManager.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
|
||||
holder.card.setOnClickListener { onItemClick(gi) }
|
||||
holder.card.setOnLongClickListener { onItemLongClick(gi) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,12 +451,9 @@ class HistoryScene : ToolbarScene() {
|
||||
|
||||
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()
|
||||
val info: HistoryInfo? = mAdapter.peek(mPosition)
|
||||
info?.let { EhDB.deleteHistoryInfo(info) }
|
||||
mAdapter.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,8 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
@@ -333,7 +333,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
return;
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.sign_in_failed)
|
||||
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.sign_in_failed_tip))
|
||||
.setPositiveButton(R.string.get_it, null)
|
||||
|
||||
@@ -1,124 +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.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public abstract class ToolbarScene extends BaseScene {
|
||||
|
||||
@Nullable
|
||||
private Toolbar mToolbar;
|
||||
|
||||
private CharSequence mTempTitle;
|
||||
|
||||
@Nullable
|
||||
public View onCreateViewWithToolbar(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public final View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_toolbar, container, false);
|
||||
Toolbar toolbar = view.findViewById(R.id.toolbar);
|
||||
FrameLayout contentPanel = view.findViewById(R.id.content_panel);
|
||||
|
||||
View contentView = onCreateViewWithToolbar(inflater, contentPanel, savedInstanceState);
|
||||
if (contentView == null) {
|
||||
return null;
|
||||
} else {
|
||||
mToolbar = toolbar;
|
||||
contentPanel.addView(contentView, 0);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mToolbar = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (mToolbar != null) {
|
||||
if (mTempTitle != null) {
|
||||
mToolbar.setTitle(mTempTitle);
|
||||
mTempTitle = null;
|
||||
}
|
||||
|
||||
int menuResId = getMenuResId();
|
||||
if (menuResId != 0) {
|
||||
mToolbar.inflateMenu(menuResId);
|
||||
mToolbar.setOnMenuItemClickListener(ToolbarScene.this::onMenuItemClick);
|
||||
}
|
||||
mToolbar.setNavigationOnClickListener(v -> onNavigationClick());
|
||||
}
|
||||
}
|
||||
|
||||
public int getMenuResId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onNavigationClick() {
|
||||
}
|
||||
|
||||
public void setNavigationIcon(@DrawableRes int resId) {
|
||||
if (mToolbar != null) {
|
||||
mToolbar.setNavigationIcon(resId);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNavigationIcon(@Nullable Drawable icon) {
|
||||
if (mToolbar != null) {
|
||||
mToolbar.setNavigationIcon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTitle(@StringRes int resId) {
|
||||
setTitle(getString(resId));
|
||||
}
|
||||
|
||||
public void setTitle(CharSequence title) {
|
||||
if (mToolbar != null) {
|
||||
mToolbar.setTitle(title);
|
||||
} else {
|
||||
mTempTitle = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.size
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
abstract class ToolbarScene : BaseScene() {
|
||||
protected var mToolbar: Toolbar? = null
|
||||
protected var mAppBarLayout: AppBarLayout? = null
|
||||
private var mTempTitle: CharSequence? = null
|
||||
|
||||
abstract fun onCreateViewWithToolbar(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.scene_toolbar, container, false) as ViewGroup
|
||||
mToolbar = view.findViewById(R.id.toolbar)
|
||||
mAppBarLayout = view.findViewById(R.id.appbar)
|
||||
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
|
||||
return view.apply { addView(contentView, 0) }
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mToolbar = null
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
mToolbar?.apply {
|
||||
mTempTitle?.let { title = it }
|
||||
val menuResId = getMenuResId()
|
||||
if (menuResId != 0) {
|
||||
inflateMenu(menuResId)
|
||||
setOnMenuItemClickListener { item: MenuItem -> onMenuItemClick(item) }
|
||||
}
|
||||
setNavigationOnClickListener { onNavigationClick() }
|
||||
}
|
||||
}
|
||||
|
||||
fun hideMenu() {
|
||||
mToolbar?.menu?.clear()
|
||||
}
|
||||
|
||||
fun showMenu(menuResId: Int) {
|
||||
if (mToolbar?.menu?.size != 0) return
|
||||
mToolbar?.apply {
|
||||
inflateMenu(menuResId)
|
||||
setOnMenuItemClickListener { item: MenuItem -> onMenuItemClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
open fun getMenuResId(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
open fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
open fun onNavigationClick() {}
|
||||
|
||||
fun setNavigationIcon(@DrawableRes resId: Int) {
|
||||
mToolbar?.setNavigationIcon(resId)
|
||||
}
|
||||
|
||||
fun setTitle(@StringRes resId: Int) {
|
||||
setTitle(getString(resId))
|
||||
}
|
||||
|
||||
fun setTitle(title: CharSequence?) {
|
||||
mToolbar?.title = title
|
||||
mTempTitle = title
|
||||
}
|
||||
|
||||
fun setLiftOnScrollTargetView(view: View?) {
|
||||
mAppBarLayout?.setLiftOnScrollTargetView(view)
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,8 @@ import com.hippo.yorozuya.NumberUtils
|
||||
class AdvanceSearchTable @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : LinearLayout(context, attrs) {
|
||||
private var mSname: CheckBox
|
||||
private var mStags: CheckBox
|
||||
private var mSdesc: CheckBox
|
||||
private var mStorr: CheckBox
|
||||
private var mSto: CheckBox
|
||||
private var mSdt1: CheckBox
|
||||
private var mSdt2: CheckBox
|
||||
private var mSh: CheckBox
|
||||
private var mSto: CheckBox
|
||||
private var mSr: CheckBox
|
||||
private var mMinRating: TextInputLayout
|
||||
private var mSp: CheckBox
|
||||
@@ -51,28 +45,19 @@ class AdvanceSearchTable @JvmOverloads constructor(
|
||||
val inflater = LayoutInflater.from(context)
|
||||
inflater.inflate(R.layout.widget_advance_search_table, this)
|
||||
val row0 = getChildAt(0) as ViewGroup
|
||||
mSname = row0.getChildAt(0) as CheckBox
|
||||
mStags = row0.getChildAt(1) as CheckBox
|
||||
mSh = row0.getChildAt(0) as CheckBox
|
||||
mSto = row0.getChildAt(1) as CheckBox
|
||||
val row1 = getChildAt(1) as ViewGroup
|
||||
mSdesc = row1.getChildAt(0) as CheckBox
|
||||
mStorr = row1.getChildAt(1) as CheckBox
|
||||
mSr = row1.getChildAt(0) as CheckBox
|
||||
mMinRating = row1.getChildAt(1) as TextInputLayout
|
||||
val row2 = getChildAt(2) as ViewGroup
|
||||
mSto = row2.getChildAt(0) as CheckBox
|
||||
mSdt1 = row2.getChildAt(1) as CheckBox
|
||||
val row3 = getChildAt(3) as ViewGroup
|
||||
mSdt2 = row3.getChildAt(0) as CheckBox
|
||||
mSh = row3.getChildAt(1) as CheckBox
|
||||
mSp = row2.getChildAt(0) as CheckBox
|
||||
mSpf = row2.getChildAt(1) as EditText
|
||||
mSpt = row2.getChildAt(3) as EditText
|
||||
val row4 = getChildAt(4) as ViewGroup
|
||||
mSr = row4.getChildAt(0) as CheckBox
|
||||
mMinRating = row4.getChildAt(1) as TextInputLayout
|
||||
val row5 = getChildAt(5) as ViewGroup
|
||||
mSp = row5.getChildAt(0) as CheckBox
|
||||
mSpf = row5.getChildAt(1) as EditText
|
||||
mSpt = row5.getChildAt(3) as EditText
|
||||
val row7 = getChildAt(7) as ViewGroup
|
||||
mSfl = row7.getChildAt(0) as CheckBox
|
||||
mSfu = row7.getChildAt(1) as CheckBox
|
||||
mSft = row7.getChildAt(2) as CheckBox
|
||||
mSfl = row4.getChildAt(0) as CheckBox
|
||||
mSfu = row4.getChildAt(1) as CheckBox
|
||||
mSft = row4.getChildAt(2) as CheckBox
|
||||
|
||||
mSpt.setOnEditorActionListener { v: TextView, _: Int, _: KeyEvent? ->
|
||||
val nextView = v.focusSearch(
|
||||
@@ -98,31 +83,16 @@ class AdvanceSearchTable @JvmOverloads constructor(
|
||||
var advanceSearch: Int
|
||||
get() {
|
||||
var advanceSearch = 0
|
||||
if (mSname.isChecked) advanceSearch = advanceSearch or SNAME
|
||||
if (mStags.isChecked) advanceSearch = advanceSearch or STAGS
|
||||
if (mSdesc.isChecked) advanceSearch = advanceSearch or SDESC
|
||||
if (mStorr.isChecked) advanceSearch = advanceSearch or STORR
|
||||
if (mSto.isChecked) advanceSearch = advanceSearch or STO
|
||||
if (mSdt1.isChecked) advanceSearch = advanceSearch or SDT1
|
||||
if (mSdt2.isChecked) advanceSearch = advanceSearch or SDT2
|
||||
if (mSh.isChecked) advanceSearch = advanceSearch or SH
|
||||
if (mSto.isChecked) advanceSearch = advanceSearch or STO
|
||||
if (mSfl.isChecked) advanceSearch = advanceSearch or SFL
|
||||
if (mSfu.isChecked) advanceSearch = advanceSearch or SFU
|
||||
if (mSft.isChecked) advanceSearch = advanceSearch or SFT
|
||||
return advanceSearch
|
||||
}
|
||||
set(advanceSearch) {
|
||||
mSname.isChecked =
|
||||
NumberUtils.int2boolean(advanceSearch and SNAME)
|
||||
mStags.isChecked =
|
||||
NumberUtils.int2boolean(advanceSearch and STAGS)
|
||||
mSdesc.isChecked =
|
||||
NumberUtils.int2boolean(advanceSearch and SDESC)
|
||||
mStorr.isChecked = NumberUtils.int2boolean(advanceSearch and STORR)
|
||||
mSto.isChecked = NumberUtils.int2boolean(advanceSearch and STO)
|
||||
mSdt1.isChecked = NumberUtils.int2boolean(advanceSearch and SDT1)
|
||||
mSdt2.isChecked = NumberUtils.int2boolean(advanceSearch and SDT2)
|
||||
mSh.isChecked = NumberUtils.int2boolean(advanceSearch and SH)
|
||||
mSto.isChecked = NumberUtils.int2boolean(advanceSearch and STO)
|
||||
mSfl.isChecked = NumberUtils.int2boolean(advanceSearch and SFL)
|
||||
mSfu.isChecked = NumberUtils.int2boolean(advanceSearch and SFU)
|
||||
mSft.isChecked = NumberUtils.int2boolean(advanceSearch and SFT)
|
||||
@@ -174,14 +144,8 @@ class AdvanceSearchTable @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SNAME = 0x1
|
||||
const val STAGS = 0x2
|
||||
const val SDESC = 0x4
|
||||
const val STORR = 0x8
|
||||
const val STO = 0x10
|
||||
const val SDT1 = 0x20
|
||||
const val SDT2 = 0x40
|
||||
const val SH = 0x80
|
||||
const val SH = 0x1
|
||||
const val STO = 0x2
|
||||
const val SFL = 0x100
|
||||
const val SFU = 0x200
|
||||
const val SFT = 0x400
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.webkit.WebView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public class DialogWebChromeClient extends WebChromeClient {
|
||||
@@ -39,7 +39,7 @@ public class DialogWebChromeClient extends WebChromeClient {
|
||||
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
|
||||
new MaterialAlertDialogBuilder(view.getContext())
|
||||
new BaseDialogBuilder(view.getContext())
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
|
||||
.setOnCancelListener(dialog -> result.cancel())
|
||||
@@ -49,7 +49,7 @@ public class DialogWebChromeClient extends WebChromeClient {
|
||||
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
|
||||
new MaterialAlertDialogBuilder(view.getContext())
|
||||
new BaseDialogBuilder(view.getContext())
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel())
|
||||
@@ -67,7 +67,7 @@ public class DialogWebChromeClient extends WebChromeClient {
|
||||
final EditText valueView = promptView.findViewById(R.id.value);
|
||||
valueView.setText(defaultValue);
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
new BaseDialogBuilder(context)
|
||||
.setView(promptView)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm(valueView.getText().toString()))
|
||||
.setOnCancelListener(dialog -> result.cancel())
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.FavouriteStatusRouter;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.widget.ContentLayout;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelper<GalleryInfo> {
|
||||
|
||||
private static final String KEY_DATA_MAP = "data_map";
|
||||
private final FavouriteStatusRouter.Listener listener;
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private Map<Long, GalleryInfo> map = new HashMap<>();
|
||||
|
||||
public GalleryInfoContentHelper() {
|
||||
listener = (gid, slot) -> {
|
||||
GalleryInfo info = map.get(gid);
|
||||
if (info != null) {
|
||||
info.favoriteSlot = slot;
|
||||
}
|
||||
};
|
||||
EhApplication.getFavouriteStatusRouter().addListener(listener);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
EhApplication.getFavouriteStatusRouter().removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddData(GalleryInfo data) {
|
||||
map.put(data.gid, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddData(List<GalleryInfo> data) {
|
||||
for (GalleryInfo info : data) {
|
||||
map.put(info.gid, info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveData(GalleryInfo data) {
|
||||
map.remove(data.gid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveData(List<GalleryInfo> data) {
|
||||
for (GalleryInfo info : data) {
|
||||
map.remove(info.gid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClearData() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable saveInstanceState(Parcelable superState) {
|
||||
Bundle bundle = (Bundle) super.saveInstanceState(superState);
|
||||
|
||||
// TODO It's a bad design
|
||||
FavouriteStatusRouter router = EhApplication.getFavouriteStatusRouter();
|
||||
int id = router.saveDataMap(map);
|
||||
bundle.putInt(KEY_DATA_MAP, id);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable restoreInstanceState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
|
||||
int id = bundle.getInt(KEY_DATA_MAP, IntIdGenerator.INVALID_ID);
|
||||
if (id != IntIdGenerator.INVALID_ID) {
|
||||
FavouriteStatusRouter router = EhApplication.getFavouriteStatusRouter();
|
||||
Map<Long, GalleryInfo> map = router.restoreDataMap(id);
|
||||
if (map != null) {
|
||||
this.map = map;
|
||||
}
|
||||
}
|
||||
|
||||
return super.restoreInstanceState(state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.Calendar
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.widget.ContentLayout.ContentHelper
|
||||
import com.hippo.yorozuya.IntIdGenerator
|
||||
import java.util.Locale
|
||||
|
||||
abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
@JvmField
|
||||
var maxGid = -1
|
||||
@JvmField
|
||||
var minGid = -1
|
||||
@JvmField
|
||||
var jumpTo : String? = null
|
||||
private val listener: FavouriteStatusRouter.Listener
|
||||
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private var map: MutableMap<Long, GalleryInfo> = HashMap()
|
||||
|
||||
init {
|
||||
listener = FavouriteStatusRouter.Listener { gid: Long, slot: Int ->
|
||||
val info = map[gid]
|
||||
if (info != null) {
|
||||
info.favoriteSlot = slot
|
||||
}
|
||||
}
|
||||
EhApplication.getFavouriteStatusRouter().addListener(listener)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
EhApplication.getFavouriteStatusRouter().removeListener(listener)
|
||||
}
|
||||
|
||||
override fun onAddData(data: GalleryInfo?) {
|
||||
data?.let { map[data.gid] = data }
|
||||
}
|
||||
|
||||
override fun onAddData(data: List<GalleryInfo?>) {
|
||||
for (info in data) {
|
||||
info?.let {
|
||||
if (maxGid == -1) maxGid = info.gid.toInt()
|
||||
if (minGid == -1) minGid = info.gid.toInt()
|
||||
maxGid = Math.max(maxGid.toLong(), info.gid).toInt()
|
||||
minGid = Math.min(minGid.toLong(), info.gid).toInt()
|
||||
map[info.gid] = info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRemoveData(data: GalleryInfo?) {
|
||||
data?.let { map.remove(data.gid) }
|
||||
}
|
||||
|
||||
override fun onRemoveData(data: List<GalleryInfo?>) {
|
||||
for (info in data) {
|
||||
info?.let { map.remove(info.gid) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClearData() {
|
||||
map.clear()
|
||||
maxGid = -1
|
||||
minGid = -1
|
||||
}
|
||||
|
||||
override fun beforeRefresh() {
|
||||
super.beforeRefresh()
|
||||
maxGid = -1
|
||||
minGid = -1
|
||||
}
|
||||
|
||||
override fun saveInstanceState(superState: Parcelable): Parcelable {
|
||||
val bundle = super.saveInstanceState(superState) as Bundle
|
||||
|
||||
// TODO It's a bad design
|
||||
val router = EhApplication.getFavouriteStatusRouter()
|
||||
val id = router.saveDataMap(map)
|
||||
bundle.putInt(KEY_DATA_MAP, id)
|
||||
bundle.putInt(KEY_MIN_GID, minGid)
|
||||
bundle.putInt(KEY_MAX_GID, maxGid)
|
||||
return bundle
|
||||
}
|
||||
|
||||
override fun restoreInstanceState(state: Parcelable): Parcelable {
|
||||
val bundle = state as Bundle
|
||||
val id = bundle.getInt(KEY_DATA_MAP, IntIdGenerator.INVALID_ID)
|
||||
if (id != IntIdGenerator.INVALID_ID) {
|
||||
val router = EhApplication.getFavouriteStatusRouter()
|
||||
val map = router.restoreDataMap(id)
|
||||
if (map != null) {
|
||||
this.map = map
|
||||
}
|
||||
}
|
||||
minGid = bundle.getInt(KEY_MIN_GID)
|
||||
maxGid = bundle.getInt(KEY_MAX_GID)
|
||||
return super.restoreInstanceState(state)
|
||||
}
|
||||
|
||||
fun goTo(time: Long) {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = time
|
||||
val date = calendar.time
|
||||
val format = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
jumpTo = format.format(date)
|
||||
doGoToPage(Int.MAX_VALUE / 2)
|
||||
}
|
||||
|
||||
fun goToPage(page: Int) {
|
||||
jumpTo = page.toString()
|
||||
doGoToPage(page)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_DATA_MAP = "data_map"
|
||||
private const val KEY_MAX_GID = "max_gid"
|
||||
private const val KEY_MIN_GID = "min_gid"
|
||||
const val KEY_UPPER_PAGE = "upper_page"
|
||||
const val KEY_LOWER_PAGE = "lower_page"
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
|
||||
private View mSelectImage;
|
||||
private CheckBox mSearchUSS;
|
||||
private CheckBox mSearchOSC;
|
||||
private CheckBox mSearchSE;
|
||||
|
||||
private Helper mHelper;
|
||||
|
||||
@@ -93,7 +92,6 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
|
||||
mSelectImage = ViewUtils.$$(this, R.id.select_image);
|
||||
mSearchUSS = (CheckBox) ViewUtils.$$(this, R.id.search_uss);
|
||||
mSearchOSC = (CheckBox) ViewUtils.$$(this, R.id.search_osc);
|
||||
mSearchSE = (CheckBox) ViewUtils.$$(this, R.id.search_se);
|
||||
|
||||
mSelectImage.setOnClickListener(this);
|
||||
}
|
||||
@@ -182,7 +180,6 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
|
||||
builder.setImagePath(mImagePath);
|
||||
builder.setUseSimilarityScan(mSearchUSS.isChecked());
|
||||
builder.setOnlySearchCovers(mSearchOSC.isChecked());
|
||||
builder.setShowExpunged(mSearchSE.isChecked());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -46,7 +46,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhTagDatabase;
|
||||
@@ -668,7 +668,7 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
|
||||
|
||||
@Override
|
||||
public boolean onLongClick() {
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
new BaseDialogBuilder(getContext())
|
||||
.setMessage(getContext().getString(R.string.delete_search_history, mKeyword))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
|
||||
@@ -34,9 +34,9 @@ import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration
|
||||
import com.hippo.easyrecyclerview.SimpleHolder
|
||||
@@ -217,7 +217,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (mNormalSearchModeHelp === v) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
BaseDialogBuilder(context)
|
||||
.setMessage(R.string.search_tip)
|
||||
.show()
|
||||
}
|
||||
@@ -242,7 +242,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||
|
||||
@IntDef(SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class SearchMode
|
||||
interface Helper {
|
||||
fun onChangeSearchMode()
|
||||
|
||||
@@ -983,13 +983,15 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste
|
||||
|
||||
private final Context mContext;
|
||||
private final Adapter mAdapter;
|
||||
private final int mEdgeColor = Color.WHITE;
|
||||
private Listener mListener;
|
||||
|
||||
private int mLayoutMode = LAYOUT_LEFT_TO_RIGHT;
|
||||
private int mScaleMode = SCALE_FIT;
|
||||
private int mStartPosition = START_POSITION_TOP_LEFT;
|
||||
private int mStartPage = 0;
|
||||
|
||||
private int mBackgroundColor = Color.BLACK;
|
||||
private int mEdgeColor = Color.WHITE;
|
||||
private int mPagerInterval = 48;
|
||||
private int mScrollInterval = 24;
|
||||
private int mPageMinHeight = 256;
|
||||
|
||||
@@ -62,8 +62,8 @@ public class GLES11Canvas implements GLCanvas {
|
||||
private static final int MSKEW_Y = 1;
|
||||
private static final int MSCALE_X = 0;
|
||||
private static final int MSCALE_Y = 5;
|
||||
private static final float[] sCropRect = new float[4];
|
||||
private static final GLId mGLId = new GLES11IdImpl();
|
||||
private static float[] sCropRect = new float[4];
|
||||
private static GLId mGLId = new GLES11IdImpl();
|
||||
|
||||
static {
|
||||
float[] temp = {
|
||||
@@ -98,12 +98,12 @@ public class GLES11Canvas implements GLCanvas {
|
||||
}
|
||||
}
|
||||
|
||||
private final float[] mMatrixValues = new float[16];
|
||||
private final float[] mTextureMatrixValues = new float[16];
|
||||
private final float mMatrixValues[] = new float[16];
|
||||
private final float mTextureMatrixValues[] = new float[16];
|
||||
// The results of mapPoints are stored in this buffer, and the order is
|
||||
// x1, y1, x2, y2.
|
||||
private final float[] mMapPointsBuffer = new float[4];
|
||||
private final float[] mTextureColor = new float[4];
|
||||
private final float mMapPointsBuffer[] = new float[4];
|
||||
private final float mTextureColor[] = new float[4];
|
||||
private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
|
||||
private final ArrayList<ConfigState> mRestoreStack = new ArrayList<ConfigState>();
|
||||
private final RectF mDrawTextureSourceRect = new RectF();
|
||||
@@ -111,21 +111,21 @@ public class GLES11Canvas implements GLCanvas {
|
||||
private final float[] mTempMatrix = new float[32];
|
||||
private final IntList mUnboundTextures = new IntList();
|
||||
private final IntList mDeleteBuffers = new IntList();
|
||||
private final GL11 mGL;
|
||||
private final int mBoxCoords;
|
||||
private final GLState mGLState;
|
||||
private final boolean mBlendEnabled = true;
|
||||
private final int[] mFrameBuffer = new int[1];
|
||||
// Drawing statistics
|
||||
int mCountDrawLine;
|
||||
int mCountFillRect;
|
||||
int mCountDrawMesh;
|
||||
int mCountTextureRect;
|
||||
int mCountTextureOES;
|
||||
private GL11 mGL;
|
||||
private int mBoxCoords;
|
||||
private GLState mGLState;
|
||||
private float mAlpha;
|
||||
private ConfigState mRecycledRestoreAction;
|
||||
private int mScreenWidth;
|
||||
private int mScreenHeight;
|
||||
private boolean mBlendEnabled = true;
|
||||
private int mFrameBuffer[] = new int[1];
|
||||
private RawTexture mTargetTexture;
|
||||
|
||||
public GLES11Canvas(GL11 gl) {
|
||||
@@ -191,7 +191,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMatrixRotatedOrFlipped(float[] matrix) {
|
||||
private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
|
||||
final float eps = 1e-5f;
|
||||
return Math.abs(matrix[MSKEW_X]) > eps
|
||||
|| Math.abs(matrix[MSKEW_Y]) > eps
|
||||
@@ -247,7 +247,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
gl.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
gl.glLoadIdentity();
|
||||
|
||||
float[] matrix = mMatrixValues;
|
||||
float matrix[] = mMatrixValues;
|
||||
Matrix.setIdentityM(matrix, 0);
|
||||
// to match the graphic coordinate system in android, we flip it vertically.
|
||||
if (mTargetTexture == null) {
|
||||
@@ -408,7 +408,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multiplyMatrix(float[] matrix, int offset) {
|
||||
public void multiplyMatrix(float matrix[], int offset) {
|
||||
float[] temp = mTempMatrix;
|
||||
Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset);
|
||||
System.arraycopy(temp, 0, mMatrixValues, 0, 16);
|
||||
@@ -467,7 +467,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
|
||||
// Transforms two points by the given matrix m. The result
|
||||
// {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned.
|
||||
private float[] mapPoints(float[] m, int x1, int y1, int x2, int y2) {
|
||||
private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) {
|
||||
float[] r = mMapPointsBuffer;
|
||||
|
||||
// Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused.
|
||||
@@ -506,7 +506,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
textureRect(x, y, width, height);
|
||||
} else {
|
||||
// draw the rect from bottom-left to top-right
|
||||
float[] points = mapPoints(
|
||||
float points[] = mapPoints(
|
||||
mMatrixValues, x, y + height, x + width, y);
|
||||
x = (int) (points[0] + 0.5f);
|
||||
y = (int) (points[1] + 0.5f);
|
||||
@@ -958,12 +958,13 @@ public class GLES11Canvas implements GLCanvas {
|
||||
private static class GLState {
|
||||
|
||||
private final GL11 mGL;
|
||||
private final boolean mLineSmooth = false;
|
||||
|
||||
private int mTexEnvMode = GL11.GL_REPLACE;
|
||||
private float mTextureAlpha = 1.0f;
|
||||
private int mTextureTarget = GL11.GL_TEXTURE_2D;
|
||||
private boolean mBlendEnabled = true;
|
||||
private float mLineWidth = 1.0f;
|
||||
private boolean mLineSmooth = false;
|
||||
|
||||
public GLState(GL11 gl) {
|
||||
mGL = gl;
|
||||
@@ -1058,7 +1059,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
|
||||
private static class ConfigState {
|
||||
float mAlpha;
|
||||
float[] mMatrix = new float[16];
|
||||
float mMatrix[] = new float[16];
|
||||
ConfigState mNextFree;
|
||||
|
||||
public void restore(GLES11Canvas canvas) {
|
||||
|
||||
@@ -30,9 +30,9 @@ class NinePatchChunk {
|
||||
|
||||
public Rect mPaddings = new Rect();
|
||||
|
||||
public int[] mDivX;
|
||||
public int[] mDivY;
|
||||
public int[] mColor;
|
||||
public int mDivX[];
|
||||
public int mDivY[];
|
||||
public int mColor[];
|
||||
|
||||
private static void readIntArray(int[] data, ByteBuffer buffer) {
|
||||
for (int i = 0, n = data.length; i < n; ++i) {
|
||||
|
||||
@@ -35,9 +35,9 @@ import java.nio.FloatBuffer;
|
||||
public class NinePatchTexture extends ResourceTexture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "NinePatchTexture";
|
||||
private final SmallCache<NinePatchInstance> mInstanceCache
|
||||
= new SmallCache<NinePatchInstance>();
|
||||
private NinePatchChunk mChunk;
|
||||
private SmallCache<NinePatchInstance> mInstanceCache
|
||||
= new SmallCache<NinePatchInstance>();
|
||||
|
||||
public NinePatchTexture(Context context, int resId) {
|
||||
super(context, resId);
|
||||
@@ -120,8 +120,8 @@ public class NinePatchTexture extends ResourceTexture {
|
||||
private static class SmallCache<V> {
|
||||
private static final int CACHE_SIZE = 16;
|
||||
private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
|
||||
private final int[] mKey = new int[CACHE_SIZE];
|
||||
private final V[] mValue = (V[]) new Object[CACHE_SIZE];
|
||||
private int[] mKey = new int[CACHE_SIZE];
|
||||
private V[] mValue = (V[]) new Object[CACHE_SIZE];
|
||||
private int mCount; // number of items in this cache
|
||||
|
||||
// Puts a value into the cache. If the cache is full, also returns
|
||||
@@ -217,10 +217,10 @@ class NinePatchInstance {
|
||||
throw new RuntimeException("unsupported nine patch");
|
||||
}
|
||||
|
||||
float[] divX = new float[4];
|
||||
float[] divY = new float[4];
|
||||
float[] divU = new float[4];
|
||||
float[] divV = new float[4];
|
||||
float divX[] = new float[4];
|
||||
float divY[] = new float[4];
|
||||
float divU[] = new float[4];
|
||||
float divV[] = new float[4];
|
||||
|
||||
int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
|
||||
int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
|
||||
@@ -261,7 +261,7 @@ class NinePatchInstance {
|
||||
* @return the number of these dividers.
|
||||
*/
|
||||
private static int stretch(
|
||||
float[] x, float[] u, int[] div, int source, int target) {
|
||||
float x[], float u[], int div[], int source, int target) {
|
||||
int textureSize = MathUtils.nextPowerOf2(source);
|
||||
float textureBound = (float) source / textureSize;
|
||||
|
||||
@@ -313,7 +313,7 @@ class NinePatchInstance {
|
||||
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
private void prepareVertexData(float[] x, float[] y, float[] u, float[] v,
|
||||
private void prepareVertexData(float x[], float y[], float u[], float v[],
|
||||
int nx, int ny, int[] color) {
|
||||
/*
|
||||
* Given a 3x3 nine-patch image, the vertex order is defined as the
|
||||
@@ -335,8 +335,8 @@ class NinePatchInstance {
|
||||
* index: 04152637B6A5948C9DAEBF
|
||||
*/
|
||||
int pntCount = 0;
|
||||
float[] xy = new float[VERTEX_BUFFER_SIZE];
|
||||
float[] uv = new float[VERTEX_BUFFER_SIZE];
|
||||
float xy[] = new float[VERTEX_BUFFER_SIZE];
|
||||
float uv[] = new float[VERTEX_BUFFER_SIZE];
|
||||
for (int j = 0; j < ny; ++j) {
|
||||
for (int i = 0; i < nx; ++i) {
|
||||
int xIndex = (pntCount++) << 1;
|
||||
@@ -350,7 +350,7 @@ class NinePatchInstance {
|
||||
|
||||
int idxCount = 1;
|
||||
boolean isForward = false;
|
||||
byte[] index = new byte[INDEX_BUFFER_SIZE];
|
||||
byte index[] = new byte[INDEX_BUFFER_SIZE];
|
||||
for (int row = 0; row < ny - 1; row++) {
|
||||
--idxCount;
|
||||
isForward = !isForward;
|
||||
|
||||
@@ -54,17 +54,21 @@ public class GLProgressView extends GLView {
|
||||
}
|
||||
|
||||
private final GLPaint mGLPaint;
|
||||
private final List<Animation> mAnimations;
|
||||
|
||||
private float mCx;
|
||||
private float mCy;
|
||||
private float mRadiusX;
|
||||
private float mRadiusY;
|
||||
|
||||
private float mTrimStart = 0.0f;
|
||||
private float mTrimEnd = 0.0f;
|
||||
private float mTrimOffset = 0.0f;
|
||||
private float mTrimRotation = 0.0f;
|
||||
|
||||
private boolean mIndeterminate = false;
|
||||
|
||||
private List<Animation> mAnimations;
|
||||
|
||||
public GLProgressView() {
|
||||
mGLPaint = new GLPaint();
|
||||
mGLPaint.setColor(Color.WHITE);
|
||||
|
||||
@@ -1,246 +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 androidx.annotation.Nullable;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* The {@code Image} is a image which stored pixel data in native heap
|
||||
*/
|
||||
public final class Image {
|
||||
public static final int FORMAT_NORMAL = 0;
|
||||
public static final int FORMAT_ANIMATED = 1;
|
||||
private static final ArrayList<Image> imageList = new ArrayList<>();
|
||||
private static boolean needEvictAll = false;
|
||||
private long mNativePtr;
|
||||
|
||||
private boolean needRecycle = false;
|
||||
|
||||
private Integer nativeProcessCnt = 0;
|
||||
|
||||
private Image(long nativePtr) {
|
||||
if (needEvictAll)
|
||||
evictAll();
|
||||
mNativePtr = nativePtr;
|
||||
imageList.add(this);
|
||||
}
|
||||
|
||||
private static Image newFromAddr(long addr) {
|
||||
if (addr == 0)
|
||||
return null;
|
||||
return new Image(addr);
|
||||
}
|
||||
|
||||
public static void lazyEvictAll() {
|
||||
needEvictAll = true;
|
||||
}
|
||||
|
||||
private static void evictAll() {
|
||||
(new ArrayList<>(imageList)).forEach(Image::recycle);
|
||||
needEvictAll = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(FileDescriptor fd, boolean partially) {
|
||||
return newFromAddr(nativeDecode(fd));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(Integer fd, boolean partially) {
|
||||
return newFromAddr(nativeDecodeFdInt(fd));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decodeAddr(Long addr, boolean partially) {
|
||||
return newFromAddr(nativeDecodeAddr(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@Nullable
|
||||
public static Image create(Bitmap bitmap) {
|
||||
return newFromAddr(nativeCreate(bitmap));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled {@code Image} instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
public static int getImageCount() {
|
||||
return imageList.size();
|
||||
}
|
||||
|
||||
private static native long nativeDecode(FileDescriptor fd);
|
||||
|
||||
private static native long nativeDecodeFdInt(int fd);
|
||||
|
||||
private static native long nativeDecodeAddr(long addr);
|
||||
|
||||
private static native long nativeCreate(Bitmap bitmap);
|
||||
|
||||
private static native void nativeRender(long nativePtr,
|
||||
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height);
|
||||
|
||||
private static native void nativeTexImage(long nativePtr, boolean init, int offsetX, int offsetY, int width, int height);
|
||||
|
||||
private static native void nativeAdvance(long nativePtr);
|
||||
|
||||
private static native int nativeGetDelay(long nativePtr);
|
||||
|
||||
private static native boolean nativeIsOpaque(long nativePtr);
|
||||
|
||||
private static native void nativeRecycle(long nativePtr);
|
||||
|
||||
private static native int nativeGetFormat(long nativePtr);
|
||||
|
||||
private static native int nativeGetWidth(long nativePtr);
|
||||
|
||||
private static native int nativeGetHeight(long nativePtr);
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
public int getFormat() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetFormat(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the image
|
||||
*/
|
||||
public int getWidth() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetWidth(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the image
|
||||
*/
|
||||
public int getHeight() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetHeight(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private synchronized void checkRecycledAndLock() {
|
||||
if (mNativePtr == 0) {
|
||||
throw new IllegalStateException("The image is recycled.");
|
||||
}
|
||||
nativeProcessCnt++;
|
||||
}
|
||||
|
||||
private synchronized void releaseNativeLock() {
|
||||
nativeProcessCnt--;
|
||||
if (needRecycle)
|
||||
recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the image to {@code Bitmap}
|
||||
*/
|
||||
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height, boolean fillBlank, int defaultColor) {
|
||||
checkRecycledAndLock();
|
||||
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@code glTexImage2D} for init is true and
|
||||
* call {@code glTexSubImage2D} for init is false.
|
||||
* width * height must <= 512 * 512 or do nothing
|
||||
*/
|
||||
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
|
||||
checkRecycledAndLock();
|
||||
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to next frame. Do nothing for non-animation image
|
||||
*/
|
||||
public void advance() {
|
||||
checkRecycledAndLock();
|
||||
nativeAdvance(mNativePtr);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current frame delay. 0 for non-animation image
|
||||
*/
|
||||
public int getDelay() {
|
||||
checkRecycledAndLock();
|
||||
int delay = nativeGetDelay(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return delay <= 10 ? 100 : delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return is the image opaque
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
checkRecycledAndLock();
|
||||
boolean 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.
|
||||
*/
|
||||
public synchronized void recycle() {
|
||||
if (mNativePtr != 0) {
|
||||
if (nativeProcessCnt != 0) {
|
||||
needRecycle = true;
|
||||
return;
|
||||
}
|
||||
nativeRecycle(mNativePtr);
|
||||
mNativePtr = 0;
|
||||
imageList.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this image has been recycled.
|
||||
*/
|
||||
public boolean isRecycled() {
|
||||
return mNativePtr == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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,329 +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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A image with {@link Image} for data and {@link Bitmap} for render.
|
||||
*/
|
||||
public final class ImageBitmap implements Animatable, Runnable {
|
||||
|
||||
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
|
||||
@NonNull
|
||||
private final Bitmap mBitmap;
|
||||
private final int mFormat;
|
||||
private final boolean mIsOpaque;
|
||||
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
|
||||
@Nullable
|
||||
private Image mImage;
|
||||
private int mReferences;
|
||||
private int mAnimationReferences;
|
||||
private boolean mRunning;
|
||||
|
||||
private ImageBitmap(@NonNull Image image) throws OutOfMemoryError {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
image.render(0, 0, mBitmap, 0, 0, width, height, false, 0);
|
||||
mFormat = image.getFormat();
|
||||
mIsOpaque = image.isOpaque();
|
||||
|
||||
if (mFormat == 1) {
|
||||
// For animated image, save image object
|
||||
mImage = image;
|
||||
} else {
|
||||
// Free the image
|
||||
image.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private ImageBitmap(@NonNull Bitmap bitmap) {
|
||||
mFormat = Image.FORMAT_NORMAL;
|
||||
mBitmap = bitmap;
|
||||
mIsOpaque = !bitmap.hasAlpha();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode {@code InputStream}, then create image.
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap decode(@NonNull FileDescriptor fd) {
|
||||
Image image = Image.decode(fd, false);
|
||||
if (image != null) {
|
||||
return create(image);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@code ImageBitmap} from {@code Image}.
|
||||
* It is not recommended. Use {@link #decode(InputStream)} 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.
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap create(@NonNull Image image) {
|
||||
if (!image.isRecycled()) {
|
||||
try {
|
||||
return new ImageBitmap(image);
|
||||
} catch (OutOfMemoryError e) {
|
||||
image.recycle();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@code ImageBitmap} from {@code Bitmap}.
|
||||
*
|
||||
* @param bitmap the bitmap should not be recycled
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap create(@NonNull Bitmap bitmap) {
|
||||
if (!bitmap.isRecycled()) {
|
||||
return new ImageBitmap(bitmap);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the image bitmap
|
||||
*
|
||||
* @return false for the image is recycled and obtain failed
|
||||
*/
|
||||
public synchronized boolean obtain() {
|
||||
if (mBitmap.isRecycled()) {
|
||||
return false;
|
||||
} else {
|
||||
++mReferences;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the image bitmap
|
||||
*/
|
||||
public synchronized void release() {
|
||||
--mReferences;
|
||||
if (mReferences <= 0 && !mBitmap.isRecycled()) {
|
||||
mBitmap.recycle();
|
||||
if (mImage != null) {
|
||||
mImage.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback for invalidating
|
||||
*/
|
||||
public void addCallback(@NonNull Callback callback) {
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
Callback c;
|
||||
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(new WeakReference<>(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback
|
||||
*/
|
||||
public void removeCallback(@NonNull Callback callback) {
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
Callback c;
|
||||
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 the format of the image
|
||||
*/
|
||||
public int getFormat() {
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image width
|
||||
*/
|
||||
public int getWidth() {
|
||||
return mBitmap.getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image height
|
||||
*/
|
||||
public int getHeight() {
|
||||
return mBitmap.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image is opaque
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
return mIsOpaque;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image is animated
|
||||
*/
|
||||
public boolean isAnimated() {
|
||||
return mImage != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, float left, float top, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, left, top, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, @Nullable Rect src, @NonNull Rect dst, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, @Nullable Rect src, @NonNull RectF dst, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code start()} and {@code stop()} is a pair
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
mAnimationReferences++;
|
||||
if (mBitmap.isRecycled() || mImage == null || mRunning) {
|
||||
return;
|
||||
}
|
||||
mRunning = true;
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage.getDelay()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code start()} and {@code stop()} is a pair
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
mAnimationReferences--;
|
||||
if (mAnimationReferences <= 0) {
|
||||
mRunning = false;
|
||||
HANDLER.removeCallbacks(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mRunning;
|
||||
}
|
||||
|
||||
private boolean notifyUpdate() {
|
||||
boolean hasCallback = false;
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
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.getWidth(), mImage.getHeight(), 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
|
||||
public void run() {
|
||||
// Check recycled
|
||||
if (mBitmap.isRecycled() || mImage == null) {
|
||||
mRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mImage.advance();
|
||||
|
||||
if (notifyUpdate()) {
|
||||
if (mRunning) {
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage.getDelay()));
|
||||
}
|
||||
} else {
|
||||
mRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void invalidateImage(ImageBitmap who);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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,100 +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 {@link ImageBitmap}
|
||||
*/
|
||||
public class ImageDrawable extends Drawable implements Animatable, ImageBitmap.Callback {
|
||||
|
||||
private final ImageBitmap mImageBitmap;
|
||||
private final Paint mPaint;
|
||||
|
||||
public ImageDrawable(ImageBitmap imageBitmap) throws Exception {
|
||||
if (!imageBitmap.obtain()) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
mImageBitmap = imageBitmap;
|
||||
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
|
||||
// Add callback
|
||||
imageBitmap.addCallback(this);
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
mImageBitmap.removeCallback(this);
|
||||
mImageBitmap.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
mImageBitmap.draw(canvas, null, getBounds(), mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return mImageBitmap.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mImageBitmap.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mImageBitmap.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
mImageBitmap.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
mImageBitmap.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mImageBitmap.isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateImage(ImageBitmap who) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
/**
|
||||
@@ -255,7 +255,7 @@ public abstract class DialogPreference extends Preference implements
|
||||
|
||||
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
|
||||
|
||||
mBuilder = new MaterialAlertDialogBuilder(context)
|
||||
mBuilder = new BaseDialogBuilder(context)
|
||||
.setTitle(mDialogTitle)
|
||||
.setIcon(mDialogIcon)
|
||||
.setPositiveButton(mPositiveButtonText, this)
|
||||
|
||||
@@ -61,6 +61,7 @@ public class SceneFragment extends Fragment {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity instanceof StageActivity) {
|
||||
((StageActivity) activity).startScene(announcer, horizontal);
|
||||
((StageActivity) activity).updateBackPressCallBackStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ 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;
|
||||
@@ -31,6 +32,7 @@ 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;
|
||||
|
||||
@@ -58,6 +60,8 @@ 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 &&
|
||||
@@ -140,6 +144,8 @@ 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);
|
||||
@@ -247,6 +253,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
|
||||
public void startScene(Announcer announcer) {
|
||||
startScene(announcer, false);
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
public void startScene(Announcer announcer, boolean horizontal) {
|
||||
@@ -461,6 +468,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
// TODO Call onNewArguments when view created ?
|
||||
scene.onNewArguments(args);
|
||||
}
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
int getSceneIndex(SceneFragment scene) {
|
||||
@@ -477,6 +485,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
|
||||
public void finishScene(SceneFragment scene) {
|
||||
finishScene(scene.getTag());
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
private void finishScene(String tag) {
|
||||
@@ -558,25 +567,51 @@ public abstract class StageActivity extends EhActivity {
|
||||
} else {
|
||||
fragmentManager.beginTransaction().detach(fragment).attach(fragment).commitAllowingStateLoss();
|
||||
}
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@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;
|
||||
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);
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onBackPressed();
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -584,19 +619,17 @@ 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)) {
|
||||
Log.e(TAG, "onProvideAssistContent: The fragment is not SceneFragment");
|
||||
return;
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onProvideAssistContent(outContent);
|
||||
if (fragment instanceof SceneFragment scene) {
|
||||
scene.onProvideAssistContent(outContent);
|
||||
} else {
|
||||
Log.e(TAG, "onProvideAssistContent: The fragment is not SceneFragment");
|
||||
}
|
||||
}
|
||||
|
||||
public SceneFragment findSceneByTag(String tag) {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.text;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Html;
|
||||
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.drawable.UnikeryDrawable;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
|
||||
public class URLImageGetter implements Html.ImageGetter {
|
||||
|
||||
private final ObservedTextView mTextView;
|
||||
private final Conaco<ImageBitmap> mConaco;
|
||||
|
||||
public URLImageGetter(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
||||
mTextView = textView;
|
||||
mConaco = conaco;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
UnikeryDrawable drawable = new UnikeryDrawable(mTextView, mConaco);
|
||||
drawable.load(source);
|
||||
return drawable;
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,15 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.text
|
||||
|
||||
package com.hippo.scene;
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Html.ImageGetter
|
||||
import com.hippo.conaco.Conaco
|
||||
import com.hippo.drawable.UnikeryDrawable
|
||||
import com.hippo.image.ImageBitmap
|
||||
import com.hippo.widget.ObservedTextView
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
public interface TransitionHelper {
|
||||
|
||||
boolean onTransition(Context context, FragmentTransaction transaction, Fragment exit, Fragment enter);
|
||||
}
|
||||
class URLImageGetter(
|
||||
private val mTextView: ObservedTextView,
|
||||
private val mConaco: Conaco<ImageBitmap>
|
||||
) : ImageGetter {
|
||||
override fun getDrawable(source: String): Drawable {
|
||||
return UnikeryDrawable(mTextView, mConaco).apply { load(source) }
|
||||
}
|
||||
}
|
||||
@@ -104,13 +104,17 @@ final class DocumentsContractApi19 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Writable normal files considered writable
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(type)
|
||||
&& (flags & DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
|
||||
// Directories that allow create considered writable
|
||||
return true;
|
||||
} else return !TextUtils.isEmpty(type)
|
||||
&& (flags & DocumentsContract.Document.FLAG_SUPPORTS_WRITE) != 0;
|
||||
} else if (!TextUtils.isEmpty(type)
|
||||
&& (flags & DocumentsContract.Document.FLAG_SUPPORTS_WRITE) != 0) {
|
||||
// Writable normal files considered writable
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean delete(Context context, Uri self) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.io.RandomAccessFile;
|
||||
|
||||
class RawRandomAccessFile implements UniRandomAccessFile {
|
||||
|
||||
private final RandomAccessFile mFile;
|
||||
private RandomAccessFile mFile;
|
||||
|
||||
RawRandomAccessFile(RandomAccessFile file) {
|
||||
mFile = file;
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.io.OutputStream;
|
||||
// TODO need more test
|
||||
class UriOutputStream extends FileOutputStream {
|
||||
|
||||
private final ParcelFileDescriptor mPfd;
|
||||
private ParcelFileDescriptor mPfd;
|
||||
|
||||
private UriOutputStream(ParcelFileDescriptor pfd, FileDescriptor fd) {
|
||||
super(fd);
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.textclassifier.TextClassifier;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class ClipboardUtil {
|
||||
private static final String TAG = "ClipboardUtil";
|
||||
@Nullable
|
||||
private static ClipboardManager clipboardManager;
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static Context sContext;
|
||||
|
||||
public static void initialize(Context context) {
|
||||
sContext = context;
|
||||
clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipboardManager == null) {
|
||||
Log.e(TAG, "This device has no clipboard!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void addTextToClipboard(String text) {
|
||||
if (clipboardManager != null) {
|
||||
try {
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTextFromClipboard() {
|
||||
if (clipboardManager != null) {
|
||||
try {
|
||||
if (!clipboardManager.hasPrimaryClip()) {
|
||||
return null;
|
||||
}
|
||||
ClipData primaryClip = clipboardManager.getPrimaryClip();
|
||||
if (primaryClip == null) {
|
||||
return null;
|
||||
}
|
||||
ClipData.Item item = primaryClip.getItemAt(0);
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
String string = String.valueOf(item.coerceToText(sContext));
|
||||
if (!TextUtils.isEmpty(string)) {
|
||||
return string;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getUrlFromClipboard() {
|
||||
if (clipboardManager != null) {
|
||||
try {
|
||||
if (!clipboardManager.hasPrimaryClip()) {
|
||||
return null;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
ClipDescription primaryClipDescription = clipboardManager.getPrimaryClipDescription();
|
||||
if (primaryClipDescription != null) {
|
||||
if (primaryClipDescription.getClassificationStatus() == ClipDescription.CLASSIFICATION_COMPLETE) {
|
||||
if (primaryClipDescription.getConfidenceScore(TextClassifier.TYPE_URL) <= 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ClipData primaryClip = clipboardManager.getPrimaryClip();
|
||||
if (primaryClip == null) {
|
||||
return null;
|
||||
}
|
||||
ClipData.Item item = primaryClip.getItemAt(0);
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
String string = String.valueOf(item.coerceToText(sContext));
|
||||
if (!TextUtils.isEmpty(string)) {
|
||||
return string;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.hippo.util
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipDescription
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.PersistableBundle
|
||||
import android.text.TextUtils
|
||||
import android.view.textclassifier.TextClassifier
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.SettingsActivity
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
|
||||
fun Context.getClipboardManager(): ClipboardManager {
|
||||
return getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
}
|
||||
|
||||
fun Context.addTextToClipboard(text: String?, isSensitive: Boolean) {
|
||||
getClipboardManager().apply {
|
||||
setPrimaryClip(ClipData.newPlainText(null, text).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && isSensitive)
|
||||
description.extras = PersistableBundle().apply {
|
||||
putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
// Avoid double notify user since system have done that on Tiramisu above
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
if (this is MainActivity) {
|
||||
showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
|
||||
} else if (this is SettingsActivity) {
|
||||
showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
fun ClipboardManager.getTextFromClipboard(context: Context): String? {
|
||||
val item = primaryClip?.getItemAt(0)
|
||||
val string = item?.coerceToText(context).toString()
|
||||
return if (!TextUtils.isEmpty(string)) string else null
|
||||
}
|
||||
|
||||
fun ClipboardManager.getUrlFromClipboard(context: Context): String? {
|
||||
if (primaryClipDescription?.classificationStatus == ClipDescription.CLASSIFICATION_COMPLETE) {
|
||||
if ((primaryClipDescription?.getConfidenceScore(TextClassifier.TYPE_URL)
|
||||
?.let { it <= 0 }) == true
|
||||
) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
val item = primaryClip?.getItemAt(0)
|
||||
val string = item?.coerceToText(context).toString()
|
||||
return if (!TextUtils.isEmpty(string)) string else null
|
||||
}
|
||||
@@ -89,13 +89,6 @@ public class BatteryView extends AppCompatTextView {
|
||||
getHandler().post(mCharger);
|
||||
mIsChargerWorking = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopCharger() {
|
||||
if (mIsChargerWorking) {
|
||||
getHandler().removeCallbacks(mCharger);
|
||||
mIsChargerWorking = false;
|
||||
}
|
||||
} private final Runnable mCharger = new Runnable() {
|
||||
|
||||
private int level = 0;
|
||||
@@ -111,6 +104,13 @@ public class BatteryView extends AppCompatTextView {
|
||||
}
|
||||
};
|
||||
|
||||
private void stopCharger() {
|
||||
if (mIsChargerWorking) {
|
||||
getHandler().removeCallbacks(mCharger);
|
||||
mIsChargerWorking = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
@@ -88,8 +88,12 @@ public class ContentLayout extends FrameLayout {
|
||||
init(context);
|
||||
}
|
||||
|
||||
protected int getLayout() {
|
||||
return R.layout.widget_content_layout;
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
((Activity) context).getLayoutInflater().inflate(R.layout.widget_content_layout, this);
|
||||
((Activity) context).getLayoutInflater().inflate(getLayout(), this);
|
||||
setClipChildren(false);
|
||||
setClipToPadding(false);
|
||||
|
||||
@@ -102,27 +106,11 @@ public class ContentLayout extends FrameLayout {
|
||||
mFastScroller = mContentView.findViewById(R.id.fast_scroller);
|
||||
mRecyclerView = mRefreshLayout.findViewById(R.id.recycler_view);
|
||||
|
||||
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(context.getTheme(), androidx.appcompat.R.attr.colorPrimary));
|
||||
mFastScroller.attachToRecyclerView(mRecyclerView);
|
||||
HandlerDrawable drawable = new HandlerDrawable();
|
||||
drawable.setColor(ResourcesKt.resolveColor(context.getTheme(), androidx.appcompat.R.attr.colorPrimary));
|
||||
mFastScroller.setHandlerDrawable(drawable);
|
||||
|
||||
mRefreshLayout.setColorSchemeResources(
|
||||
R.color.loading_indicator_red,
|
||||
R.color.loading_indicator_purple,
|
||||
R.color.loading_indicator_blue,
|
||||
R.color.loading_indicator_cyan,
|
||||
R.color.loading_indicator_green,
|
||||
R.color.loading_indicator_yellow);
|
||||
|
||||
Resources resources = getResources();
|
||||
mBottomProgress.setIndicatorColor(
|
||||
resources.getColor(R.color.loading_indicator_red),
|
||||
resources.getColor(R.color.loading_indicator_blue),
|
||||
resources.getColor(R.color.loading_indicator_green),
|
||||
resources.getColor(R.color.loading_indicator_orange));
|
||||
mBottomProgress.setIndeterminateAnimationType(LinearProgressIndicator.INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS);
|
||||
|
||||
mRecyclerViewOriginBottom = mRecyclerView.getPaddingBottom();
|
||||
mFastScrollerOriginBottom = mFastScroller.getPaddingBottom();
|
||||
}
|
||||
@@ -479,23 +467,11 @@ public class ContentLayout extends FrameLayout {
|
||||
onClearData();
|
||||
notifyDataSetChanged();
|
||||
|
||||
if (true || mEndPage >= mPages) { // Not found
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Ui change, show progress bar
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showProgressBar();
|
||||
|
||||
// Get next page
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS;
|
||||
mCurrentTaskPage = mEndPage;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
}
|
||||
// Not found
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
mData.clear();
|
||||
onClearData();
|
||||
@@ -529,33 +505,24 @@ public class ContentLayout extends FrameLayout {
|
||||
// assert mStartPage >= 0
|
||||
|
||||
if (data.isEmpty()) {
|
||||
if (true || mStartPage <= 0) { // OK, that's all
|
||||
if (mData.isEmpty()) {
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
if (mCurrentTaskType == TYPE_PRE_PAGE && mRecyclerView.isAttachedToWindow()) {
|
||||
// RecyclerView scroll, to top
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), 0, 0);
|
||||
onScrollToPosition(0);
|
||||
}
|
||||
}
|
||||
// OK, that's all
|
||||
if (mData.isEmpty()) {
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Keep UI
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
// Get previous
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
// Keep mCurrentTaskType
|
||||
mCurrentTaskPage = mStartPage - 1;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
if (mCurrentTaskType == TYPE_PRE_PAGE && mRecyclerView.isAttachedToWindow()) {
|
||||
// RecyclerView scroll, to top
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), 0, 0);
|
||||
onScrollToPosition(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mData.addAll(0, data);
|
||||
@@ -592,33 +559,24 @@ public class ContentLayout extends FrameLayout {
|
||||
mPages = Math.max(mEndPage, pages);
|
||||
|
||||
if (data.isEmpty()) {
|
||||
if (true || mEndPage >= mPages) { // OK, that's all
|
||||
if (mData.isEmpty()) {
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
if (mCurrentTaskType == TYPE_NEXT_PAGE && mRecyclerView.isAttachedToWindow()) {
|
||||
// RecyclerView scroll
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), oldDataSize, 0);
|
||||
onScrollToPosition(oldDataSize);
|
||||
}
|
||||
}
|
||||
// OK, that's all
|
||||
if (mData.isEmpty()) {
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Keep UI
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
// Get next page
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
// Keep mCurrentTaskType
|
||||
mCurrentTaskPage = mEndPage;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
if (mCurrentTaskType == TYPE_NEXT_PAGE && mRecyclerView.isAttachedToWindow()) {
|
||||
// RecyclerView scroll
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), oldDataSize, 0);
|
||||
onScrollToPosition(oldDataSize);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mData.addAll(data);
|
||||
@@ -655,23 +613,11 @@ public class ContentLayout extends FrameLayout {
|
||||
onClearData();
|
||||
notifyDataSetChanged();
|
||||
|
||||
if (true || mEndPage >= mPages) { // Not found
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Ui change, show progress bar
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showProgressBar();
|
||||
|
||||
// Get next page
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS;
|
||||
mCurrentTaskPage = mEndPage;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
}
|
||||
// Not found
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
mData.clear();
|
||||
onClearData();
|
||||
@@ -769,6 +715,10 @@ public class ContentLayout extends FrameLayout {
|
||||
mViewTransition.showView(0);
|
||||
}
|
||||
|
||||
protected void beforeRefresh() {
|
||||
|
||||
}
|
||||
|
||||
private boolean isContentShowing() {
|
||||
return mViewTransition.getShownViewIndex() == 0;
|
||||
}
|
||||
@@ -819,7 +769,8 @@ public class ContentLayout extends FrameLayout {
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
}
|
||||
|
||||
private void doRefresh() {
|
||||
protected void doRefresh() {
|
||||
beforeRefresh();
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_REFRESH;
|
||||
mCurrentTaskPage = 0;
|
||||
@@ -930,6 +881,15 @@ public class ContentLayout extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
protected void doGoToPage(int page) {
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_SOMEWHERE;
|
||||
mCurrentTaskPage = page;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
}
|
||||
|
||||
protected Parcelable saveInstanceState(Parcelable superState) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(KEY_SUPER, superState);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.hippo.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
class ContentLayout2 @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : ContentLayout(context, attrs) {
|
||||
override fun getLayout(): Int {
|
||||
return R.layout.widget_content_layout_2
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.SimpleAnimatorListener;
|
||||
|
||||
@@ -298,6 +299,7 @@ public class FabLayout extends ViewGroup implements View.OnClickListener {
|
||||
mOnExpandListener.onExpand(expanded);
|
||||
}
|
||||
}
|
||||
((StageActivity)getContext()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loaded 100 of 161 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user