Compare commits
292
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dbe24ab55 | ||
|
|
25f7b005cb | ||
|
|
23f55ea208 | ||
|
|
5f19fa0f1f | ||
|
|
58fbdcfcdd | ||
|
|
e33cca9fd9 | ||
|
|
b25e3d5454 | ||
|
|
583f272daa | ||
|
|
2a83ad63be | ||
|
|
6fdc908ad1 | ||
|
|
5213caac49 | ||
|
|
ca0518c747 | ||
|
|
298cd29537 | ||
|
|
1a963a3239 | ||
|
|
683c8dc5c5 | ||
|
|
2087970de6 | ||
|
|
5bd270fe6a | ||
|
|
0992fcfcb0 | ||
|
|
fc12395f7e | ||
|
|
57b291a4e8 | ||
|
|
3adf391478 | ||
|
|
a859941845 | ||
|
|
8d1fc85638 | ||
|
|
51ece1c472 | ||
|
|
45f67ec37f | ||
|
|
85b559c989 | ||
|
|
1382050b5f | ||
|
|
fb970752fd | ||
|
|
3d256a402f | ||
|
|
a677d95d34 | ||
|
|
452c0f2846 | ||
|
|
e568e688f5 | ||
|
|
a201c930c2 | ||
|
|
7d927dde6b | ||
|
|
f76fece81d | ||
|
|
069d7e2382 | ||
|
|
e2e0562719 | ||
|
|
347aaadb43 | ||
|
|
6ddd64232f | ||
|
|
e532b3b05b | ||
|
|
d7c17d80e7 | ||
|
|
b35980cf60 | ||
|
|
263853d6a7 | ||
|
|
6a100b8250 | ||
|
|
893560e589 | ||
|
|
6b7ce7de5b | ||
|
|
6082d8cc1a | ||
|
|
ec37c9cab2 | ||
|
|
309e2a715e | ||
|
|
fbb4afa4f7 | ||
|
|
90863b95f4 | ||
|
|
3576eaf3ff | ||
|
|
f14c9741ea | ||
|
|
6d04e51e10 | ||
|
|
6ea1aa6c1a | ||
|
|
c86e1a9409 | ||
|
|
f1845f2d4e | ||
|
|
a20ca4ea2e | ||
|
|
0effc0d181 | ||
|
|
7ce273ec67 | ||
|
|
39e080cdca | ||
|
|
17584ae5fb | ||
|
|
c46c72d66d | ||
|
|
36f00ab568 | ||
|
|
dae7dacf3d | ||
|
|
b69b72cd1b | ||
|
|
68d1e39410 | ||
|
|
d4482dec23 | ||
|
|
8f36cecc7a | ||
|
|
a59fbcc9c8 | ||
|
|
25cddb2d61 | ||
|
|
9a7af235b8 | ||
|
|
c9751ab2a2 | ||
|
|
5e13986cc0 | ||
|
|
35e2cc535e | ||
|
|
81355a0248 | ||
|
|
3e98502e2d | ||
|
|
6973fa4538 | ||
|
|
20191c3f46 | ||
|
|
f084fec54e | ||
|
|
367e3b3a93 | ||
|
|
36383b6f47 | ||
|
|
9ecd14820e | ||
|
|
be552e92a1 | ||
|
|
1bf676fd56 | ||
|
|
154a96b5fd | ||
|
|
443c384f41 | ||
|
|
85c7829636 | ||
|
|
68e3bb8d66 | ||
|
|
63b2e52eaf | ||
|
|
c7492f3faf | ||
|
|
9052343ef7 | ||
|
|
00c09e4a1a | ||
|
|
086c0d9f28 | ||
|
|
3ca0189f28 | ||
|
|
8f5a2ba85d | ||
|
|
1ab9811d7a | ||
|
|
0b720848ba | ||
|
|
eebd95e700 | ||
|
|
913e02f0fa | ||
|
|
10fdab2473 | ||
|
|
26710761ed | ||
|
|
17bf6ac12b | ||
|
|
6a01b7729d | ||
|
|
039e817002 | ||
|
|
3ea1e1914a | ||
|
|
e6c5152123 | ||
|
|
7bdaf186f3 | ||
|
|
b6b4e826cb | ||
|
|
949f3cb246 | ||
|
|
501855a8c0 | ||
|
|
44634be13d | ||
|
|
7c718df1af | ||
|
|
16cca9cbbd | ||
|
|
b715320298 | ||
|
|
d31d9eb137 | ||
|
|
b53c29c035 | ||
|
|
d3bf20bee6 | ||
|
|
cff445ddf5 | ||
|
|
877d96a50a | ||
|
|
12863dfa21 | ||
|
|
959959d880 | ||
|
|
cd676eabfc | ||
|
|
0a3e95c8d4 | ||
|
|
35b483e655 | ||
|
|
7dae7b72f4 | ||
|
|
620f5df241 | ||
|
|
d5ddb6eb8c | ||
|
|
33c9b9d4e4 | ||
|
|
216cafbe54 | ||
|
|
72a644bd3b | ||
|
|
7008f0bc89 | ||
|
|
289d3ea287 | ||
|
|
92ae157f00 | ||
|
|
9800f7c01b | ||
|
|
8281af4590 | ||
|
|
d609e5e113 | ||
|
|
238d769f27 | ||
|
|
1e648cc6b4 | ||
|
|
7ac20d59fc | ||
|
|
1d8ab51a22 | ||
|
|
cdfb3b74b3 | ||
|
|
6f2868f7c3 | ||
|
|
f3061fa497 | ||
|
|
468cce5f3d | ||
|
|
bfaba80d3b | ||
|
|
2ca4a2af86 | ||
|
|
7d0b245d3d | ||
|
|
96fcf69bdd | ||
|
|
63d95b1265 | ||
|
|
d173e83e01 | ||
|
|
54d11dea95 | ||
|
|
fbdab389c4 | ||
|
|
3dd3bad362 | ||
|
|
65a9a4b4f2 | ||
|
|
5f6cf5550d | ||
|
|
673dad4032 | ||
|
|
5e45ec9a9c | ||
|
|
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 |
No files matched your search
@@ -71,7 +71,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: EhViewer 版本号 / EhViewer version code
|
label: EhViewer 版本号 / EhViewer version code
|
||||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
||||||
placeholder: 四位版本号 / 4 digits version code
|
placeholder: 1.6.9.13
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: EhViewer 版本号 / EhViewer version code
|
label: EhViewer 版本号 / EhViewer version code
|
||||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
||||||
placeholder: 四位版本号 / 4 digits version code
|
placeholder: 1.6.9.13
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
|
||||||
- name: Setup Java 11
|
- name: Setup Java 19
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 11
|
java-version: 19
|
||||||
|
|
||||||
- name: Install NDK
|
- name: Install NDK
|
||||||
run: |
|
run: |
|
||||||
@@ -30,6 +30,7 @@ jobs:
|
|||||||
- name: Gradle Build
|
- name: Gradle Build
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
|
gradle-home-cache-cleanup: true
|
||||||
arguments: assembleRelease
|
arguments: assembleRelease
|
||||||
|
|
||||||
- name: Update ARM64
|
- name: Update ARM64
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
|
||||||
- name: Setup Java 11
|
- name: Setup Java 19
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 11
|
java-version: 19
|
||||||
|
|
||||||
- name: Install NDK
|
- name: Install NDK
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
|
||||||
- name: Setup Java 11
|
- name: Setup Java 19
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 11
|
java-version: 19
|
||||||
|
|
||||||
- name: Install NDK
|
- name: Install NDK
|
||||||
run: |
|
run: |
|
||||||
@@ -32,11 +32,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Rename Apks
|
- name: Rename Apks
|
||||||
run: |
|
run: |
|
||||||
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal-A12+.apk
|
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal.apk
|
||||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a-A12+.apk
|
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
||||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64-A12+.apk
|
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64.apk
|
||||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86-A12+.apk
|
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86.apk
|
||||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a-A12+.apk
|
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
||||||
|
|
||||||
- name: Releases
|
- name: Releases
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@@ -44,11 +44,11 @@ jobs:
|
|||||||
body: Bump Version
|
body: Bump Version
|
||||||
discussion_category_name: Announcements
|
discussion_category_name: Announcements
|
||||||
files: |
|
files: |
|
||||||
app/build/outputs/apk/release/EhViewer-universal-A12+.apk
|
app/build/outputs/apk/release/EhViewer-universal.apk
|
||||||
app/build/outputs/apk/release/EhViewer-arm64-v8a-A12+.apk
|
app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
||||||
app/build/outputs/apk/release/EhViewer-x86_64-A12+.apk
|
app/build/outputs/apk/release/EhViewer-x86_64.apk
|
||||||
app/build/outputs/apk/release/EhViewer-x86-A12+.apk
|
app/build/outputs/apk/release/EhViewer-x86.apk
|
||||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a-A12+.apk
|
app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
||||||
|
|
||||||
- name: Collect Apks
|
- name: Collect Apks
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ with Material Design 3 and Dynamic Color Support
|
|||||||
|
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="art/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Git it on F-Droid" src="art/get-it-on-fdroid.svg" width="200px"/></a>
|
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="art/get-it-on-github.svg" width="200px"/></a> <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
|
|
||||||
|
|
||||||
# Screenshot
|
# Screenshot
|
||||||
|
|
||||||

|

|
||||||
@@ -79,7 +77,8 @@ App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 a
|
|||||||
|
|
||||||
Here is the libraries
|
Here is the libraries
|
||||||
|
|
||||||
- [AOSP&AndroidX](http://source.android.com/)
|
- [AOSP & AndroidX](http://source.android.com/)
|
||||||
|
- [Coil](https://coil-kt.github.io/coil/)
|
||||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||||
- [GnuTLS](https://gnutls.org/)
|
- [GnuTLS](https://gnutls.org/)
|
||||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||||
|
|||||||
+46
-31
@@ -6,7 +6,7 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 33
|
compileSdkVersion 33
|
||||||
buildToolsVersion "33.0.0"
|
buildToolsVersion "33.0.1"
|
||||||
ndkVersion "25.1.8937393"
|
ndkVersion "25.1.8937393"
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
@@ -29,10 +29,10 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "moe.tarsin.ehviewer"
|
applicationId "moe.tarsin.ehviewer"
|
||||||
minSdkVersion 31
|
minSdkVersion 28
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 180011
|
versionCode 180027
|
||||||
versionName "1.8.2.2"
|
versionName "1.8.5.0-preview"
|
||||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,12 +43,22 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility JavaVersion.VERSION_19
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility JavaVersion.VERSION_19
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '11'
|
jvmTarget = '19'
|
||||||
|
freeCompilerArgs += [
|
||||||
|
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||||
|
"-progressive",
|
||||||
|
// Generate smaller bytecode by not generating runtime not-null assertions.
|
||||||
|
"-Xno-call-assertions",
|
||||||
|
"-Xno-param-assertions",
|
||||||
|
"-Xno-receiver-assertions",
|
||||||
|
"-P",
|
||||||
|
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
lint {
|
lint {
|
||||||
@@ -82,22 +92,25 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
buildConfig true
|
||||||
compose true
|
compose true
|
||||||
|
dataBinding true
|
||||||
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.3.0"
|
kotlinCompilerExtensionVersion = "1.4.0-alpha02"
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace 'com.hippo.ehviewer'
|
namespace 'com.hippo.ehviewer'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
def activity_version = "1.6.0-rc02"
|
def activity_version = "1.6.1"
|
||||||
implementation "androidx.activity:activity:$activity_version"
|
implementation "androidx.activity:activity:$activity_version"
|
||||||
implementation "androidx.activity:activity-ktx:$activity_version"
|
implementation "androidx.activity:activity-ktx:$activity_version"
|
||||||
|
|
||||||
implementation "androidx.annotation:annotation:1.5.0-rc01"
|
implementation "androidx.annotation:annotation:1.5.0"
|
||||||
implementation "androidx.annotation:annotation-experimental:1.3.0"
|
implementation "androidx.annotation:annotation-experimental:1.3.0"
|
||||||
implementation "androidx.appcompat:appcompat:1.6.0-rc01"
|
implementation "androidx.appcompat:appcompat:1.6.0-rc01"
|
||||||
|
|
||||||
@@ -105,38 +118,37 @@ dependencies {
|
|||||||
implementation "androidx.arch.core:core-common:$arch_core_version"
|
implementation "androidx.arch.core:core-common:$arch_core_version"
|
||||||
implementation "androidx.arch.core:core-runtime:$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"
|
implementation "androidx.browser:browser:1.4.0"
|
||||||
|
|
||||||
def collection_version = "1.3.0-alpha02"
|
def collection_version = "1.3.0-alpha02"
|
||||||
implementation "androidx.collection:collection:$collection_version"
|
implementation "androidx.collection:collection:$collection_version"
|
||||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||||
|
|
||||||
def compose_version = "1.3.0-beta02"
|
implementation platform("androidx.compose:compose-bom:2022.12.00")
|
||||||
implementation "androidx.compose.animation:animation:$compose_version"
|
implementation "androidx.compose.animation:animation"
|
||||||
implementation "androidx.compose.foundation:foundation:$compose_version"
|
implementation "androidx.compose.foundation:foundation"
|
||||||
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
|
implementation "androidx.compose.foundation:foundation-layout"
|
||||||
implementation "androidx.compose.runtime:runtime:$compose_version"
|
implementation "androidx.compose.material3:material3"
|
||||||
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
|
implementation "androidx.compose.runtime:runtime"
|
||||||
implementation "androidx.compose.ui:ui:$compose_version"
|
implementation "androidx.compose.runtime:runtime-saveable"
|
||||||
|
implementation "androidx.compose.ui:ui"
|
||||||
|
|
||||||
implementation "androidx.compose.material3:material3:1.0.0-beta02"
|
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
|
||||||
|
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
|
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||||
|
|
||||||
def core_version = "1.9.0"
|
def core_version = "1.9.0"
|
||||||
implementation "androidx.core:core:$core_version"
|
implementation "androidx.core:core:$core_version"
|
||||||
implementation "androidx.core:core-ktx:$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"
|
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||||
|
|
||||||
def fragment_version = "1.6.0-alpha02"
|
def fragment_version = "1.6.0-alpha04"
|
||||||
implementation "androidx.fragment:fragment:$fragment_version"
|
implementation "androidx.fragment:fragment:$fragment_version"
|
||||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||||
|
|
||||||
def lifecycle_version = "2.6.0-alpha02"
|
def lifecycle_version = "2.6.0-alpha03"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
|
||||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||||
@@ -146,18 +158,18 @@ dependencies {
|
|||||||
|
|
||||||
implementation "androidx.loader:loader:1.1.0"
|
implementation "androidx.loader:loader:1.1.0"
|
||||||
|
|
||||||
def nav_version = "2.5.2"
|
def nav_version = "2.5.3"
|
||||||
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
||||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||||
implementation "androidx.navigation:navigation-ui-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.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-alpha03"
|
def room_version = "2.5.0-rc01"
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||||
implementation "androidx.room:room-paging:$room_version"
|
implementation "androidx.room:room-paging:$room_version"
|
||||||
@@ -169,22 +181,25 @@ dependencies {
|
|||||||
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
|
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
|
||||||
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
|
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
|
||||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||||
implementation 'androidx.webkit:webkit:1.5.0'
|
|
||||||
|
|
||||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||||
implementation 'com.google.android.material:material:1.8.0-alpha01'
|
implementation 'com.google.android.material:material:1.8.0-beta01'
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||||
implementation 'com.squareup.okhttp3:okhttp'
|
implementation 'com.squareup.okhttp3:okhttp'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
|
||||||
|
|
||||||
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
|
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
|
||||||
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
|
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
|
||||||
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
||||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||||
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
||||||
|
implementation 'io.coil-kt:coil:2.2.2'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||||
implementation 'org.jsoup:jsoup:1.15.3'
|
implementation 'org.jsoup:jsoup:1.15.3'
|
||||||
|
|
||||||
|
// Tachiyomi Reader
|
||||||
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
|
implementation 'dev.chrisbanes.insetter:insetter:0.6.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
|
|||||||
Vendored
+1
-3
@@ -14,9 +14,7 @@
|
|||||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||||
|
|
||||||
-keepnames class com.hippo.ehviewer.ui.scene.* { }
|
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
|
||||||
|
|
||||||
-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
-renamesourcefileattribute
|
-renamesourcefileattribute
|
||||||
-repackageclasses
|
-repackageclasses
|
||||||
|
|||||||
@@ -28,6 +28,10 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="28" />
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:glEsVersion="0x00020000"
|
android:glEsVersion="0x00020000"
|
||||||
android:required="true" />
|
android:required="true" />
|
||||||
@@ -37,6 +41,7 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:appCategory="image"
|
android:appCategory="image"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:fullBackupContent="@xml/backup_scheme"
|
android:fullBackupContent="@xml/backup_scheme"
|
||||||
android:hasFragileUserData="true"
|
android:hasFragileUserData="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -97,9 +102,10 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.hippo.ehviewer.ui.GalleryActivity"
|
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
|
||||||
android:configChanges="screenSize"
|
android:configChanges="screenSize"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:colorMode="wideColorGamut"
|
||||||
android:launchMode="singleTask">
|
android:launchMode="singleTask">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
project(ehviewer)
|
project(ehviewer)
|
||||||
cmake_minimum_required(VERSION 3.4.1)
|
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 -funroll-loops -flto=thin \
|
||||||
-mllvm -polly \
|
-mllvm -polly \
|
||||||
-mllvm -polly-run-dce \
|
-mllvm -polly-run-dce \
|
||||||
-mllvm -polly-run-inliner \
|
-mllvm -polly-run-inliner \
|
||||||
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
|
|||||||
add_subdirectory(libarchive)
|
add_subdirectory(libarchive)
|
||||||
|
|
||||||
# Build and link our app's native lib
|
# 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 fdutils.c)
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
|
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
|
||||||
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
|
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
|
||||||
+75
-46
@@ -24,6 +24,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
@@ -32,12 +33,8 @@
|
|||||||
#include <archive_entry.h>
|
#include <archive_entry.h>
|
||||||
|
|
||||||
#define LOG_TAG "libarchive_wrapper"
|
#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__)
|
#include "ehviewer.h"
|
||||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__)
|
|
||||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__)
|
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
|
|
||||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int using;
|
int using;
|
||||||
@@ -53,7 +50,9 @@ static archive_ctx **ctx_pool;
|
|||||||
static bool need_encrypt = false;
|
static bool need_encrypt = false;
|
||||||
static char *passwd = NULL;
|
static char *passwd = NULL;
|
||||||
static void *archiveAddr = NULL;
|
static void *archiveAddr = NULL;
|
||||||
static jlong archiveSize = 0;
|
static size_t archiveSize = 0;
|
||||||
|
|
||||||
|
#define POPULATE_THRESHOLD (512 * 1024 * 1024) // 512 MiB
|
||||||
|
|
||||||
const char supportExt[9][6] = {
|
const char supportExt[9][6] = {
|
||||||
"jpeg",
|
"jpeg",
|
||||||
@@ -108,8 +107,12 @@ static int archive_alloc_ctx(archive_ctx **ctxptr) {
|
|||||||
free(ctx);
|
free(ctx);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
archive_read_support_format_all(ctx->arc);
|
archive_read_support_format_tar(ctx->arc);
|
||||||
archive_read_support_filter_all(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");
|
archive_read_set_option(ctx->arc, "zip", "ignorecrc32", "1");
|
||||||
if (passwd)
|
if (passwd)
|
||||||
archive_read_add_passphrase(ctx->arc, passwd);
|
archive_read_add_passphrase(ctx->arc, passwd);
|
||||||
@@ -195,15 +198,19 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
|||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd, jlong size) {
|
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd, jlong size) {
|
||||||
(void)env; /* UNUSED */
|
EH_UNUSED(env);
|
||||||
(void)thiz; /* UNUSED */
|
EH_UNUSED(thiz);
|
||||||
archive_ctx *ctx = NULL;
|
archive_ctx *ctx = NULL;
|
||||||
archiveAddr = mmap64(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
int mmap_flags = MAP_PRIVATE;
|
||||||
|
if (size <= POPULATE_THRESHOLD)
|
||||||
|
mmap_flags |= MAP_POPULATE;
|
||||||
|
archiveAddr = mmap64(0, size, PROT_READ, mmap_flags, fd, 0);
|
||||||
if (archiveAddr == MAP_FAILED) {
|
if (archiveAddr == MAP_FAILED) {
|
||||||
LOGE("%s%d", "mmap64 failed with errno ", errno);
|
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
archiveSize = size;
|
archiveSize = size;
|
||||||
|
madvise_log_if_error(archiveAddr, archiveSize, MADV_WILLNEED);
|
||||||
pthread_mutex_init(&ctx_lock, 0);
|
pthread_mutex_init(&ctx_lock, 0);
|
||||||
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
||||||
if (!ctx_pool) {
|
if (!ctx_pool) {
|
||||||
@@ -231,66 +238,55 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
|||||||
need_encrypt = false;
|
need_encrypt = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// madvise for readahead optimization
|
|
||||||
int format = archive_format(ctx->arc);
|
int format = archive_format(ctx->arc);
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case ARCHIVE_FORMAT_ZIP:
|
case ARCHIVE_FORMAT_ZIP:
|
||||||
case ARCHIVE_FORMAT_RAR:
|
|
||||||
case ARCHIVE_FORMAT_RAR_V5:
|
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;
|
break;
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
archive_release_ctx(ctx);
|
archive_release_ctx(ctx);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct Memarea {
|
JNIEXPORT jobject JNICALL
|
||||||
void *buffer;
|
Java_com_hippo_UriArchiveAccessor_extractToByteBuffer(JNIEnv *env, jobject thiz, jint index) {
|
||||||
long size;
|
EH_UNUSED(env);
|
||||||
} Memarea;
|
EH_UNUSED(thiz);
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
|
||||||
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *env, jobject thiz, jint index) {
|
|
||||||
(void)env; /* UNUSED */
|
|
||||||
(void)thiz; /* UNUSED */
|
|
||||||
archive_ctx *ctx = NULL;
|
archive_ctx *ctx = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
ret = archive_get_ctx(&ctx, index);
|
ret = archive_get_ctx(&ctx, index);
|
||||||
if (ret)
|
if (ret)
|
||||||
return 0;
|
return 0;
|
||||||
Memarea *memarea = malloc(sizeof(Memarea));
|
long long size = archive_entry_size(ctx->entry);
|
||||||
if (!memarea) {
|
void *buffer = malloc(size);
|
||||||
|
if (!buffer) {
|
||||||
ctx->using = 0;
|
ctx->using = 0;
|
||||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
memarea->size = (long) archive_entry_size(ctx->entry);
|
ret = archive_read_data(ctx->arc, buffer, size);
|
||||||
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);
|
|
||||||
ctx->using = 0;
|
ctx->using = 0;
|
||||||
if (ret == memarea->size)
|
if (ret == size)
|
||||||
return (jlong) memarea;
|
return (*env)->NewDirectByteBuffer(env, buffer, size);
|
||||||
if (ret != memarea->size)
|
if (ret != size)
|
||||||
LOGE("%s", "No enough data read, WTF?");
|
LOGE("%s", "No enough data read, WTF?");
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
||||||
free(memarea->buffer);
|
free(buffer);
|
||||||
free(memarea);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||||
(void)env; /* UNUSED */
|
EH_UNUSED(env);
|
||||||
(void)thiz; /* UNUSED */
|
EH_UNUSED(thiz);
|
||||||
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
||||||
archive_release_ctx(ctx_pool[i]);
|
archive_release_ctx(ctx_pool[i]);
|
||||||
free(passwd);
|
free(passwd);
|
||||||
@@ -303,14 +299,14 @@ Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
|||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *env, jobject thiz) {
|
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *env, jobject thiz) {
|
||||||
(void)env; /* UNUSED */
|
EH_UNUSED(env);
|
||||||
(void)thiz; /* UNUSED */
|
EH_UNUSED(thiz);
|
||||||
return need_encrypt;
|
return need_encrypt;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jstring str) {
|
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jstring str) {
|
||||||
(void)thiz; /* UNUSED */
|
EH_UNUSED(thiz);
|
||||||
struct archive_entry *entry;
|
struct archive_entry *entry;
|
||||||
archive_ctx *ctx;
|
archive_ctx *ctx;
|
||||||
jboolean ret = true;
|
jboolean ret = true;
|
||||||
@@ -337,3 +333,36 @@ Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jst
|
|||||||
archive_release_ctx(ctx);
|
archive_release_ctx(ctx);
|
||||||
return ret;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hippo_UriArchiveAccessor_releaseByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) {
|
||||||
|
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||||
|
free(addr);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,380 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 Tarsin Norbin
|
|
||||||
*
|
|
||||||
* This file is part of EhViewer
|
|
||||||
*
|
|
||||||
* EhViewer is free software: you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* EhViewer is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <android/data_space.h>
|
|
||||||
#include <android/imagedecoder.h>
|
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
#include <GLES2/gl2.h>
|
|
||||||
|
|
||||||
#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__)
|
|
||||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG ,__VA_ARGS__)
|
|
||||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG ,__VA_ARGS__)
|
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG ,__VA_ARGS__)
|
|
||||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG ,__VA_ARGS__)
|
|
||||||
|
|
||||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
|
||||||
|
|
||||||
static 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
AImageDecoder *decoder;
|
|
||||||
void *srcBuffer;
|
|
||||||
size_t stride;
|
|
||||||
int fd;
|
|
||||||
} SOURCE;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int32_t width;
|
|
||||||
int32_t height;
|
|
||||||
void *buffer;
|
|
||||||
int bufferLen;
|
|
||||||
bool isAnimated;
|
|
||||||
bool alpha;
|
|
||||||
int delay;
|
|
||||||
SOURCE *src;
|
|
||||||
} IMAGE;
|
|
||||||
|
|
||||||
static void recycleSource(SOURCE *src) {
|
|
||||||
if (!src)
|
|
||||||
return;
|
|
||||||
AImageDecoder_delete(src->decoder);
|
|
||||||
free(src->srcBuffer);
|
|
||||||
free(src);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void recycle(IMAGE *image) {
|
|
||||||
if (!image)
|
|
||||||
return;
|
|
||||||
recycleSource(image->src);
|
|
||||||
free(image->buffer);
|
|
||||||
free(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
int left;
|
|
||||||
int line;
|
|
||||||
size_t line_stride;
|
|
||||||
int src_stride;
|
|
||||||
int src_pos;
|
|
||||||
int dst_pos;
|
|
||||||
size_t dst_blank_length;
|
|
||||||
|
|
||||||
// Sanitize
|
|
||||||
if (src_x < 0) {
|
|
||||||
width -= src_x;
|
|
||||||
dst_x -= src_x;
|
|
||||||
src_x = 0;
|
|
||||||
}
|
|
||||||
if (dst_x < 0) {
|
|
||||||
width -= dst_x;
|
|
||||||
src_x -= dst_x;
|
|
||||||
dst_x = 0;
|
|
||||||
}
|
|
||||||
if (width <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (src_y < 0) {
|
|
||||||
height -= src_y;
|
|
||||||
dst_y -= src_y;
|
|
||||||
src_y = 0;
|
|
||||||
}
|
|
||||||
if (dst_y < 0) {
|
|
||||||
height -= dst_y;
|
|
||||||
src_y -= dst_y;
|
|
||||||
dst_y = 0;
|
|
||||||
}
|
|
||||||
if (height <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
left = src_x + width - src_w;
|
|
||||||
if (left > 0) {
|
|
||||||
width -= left;
|
|
||||||
}
|
|
||||||
left = dst_x + width - dst_w;
|
|
||||||
if (left > 0) {
|
|
||||||
width -= left;
|
|
||||||
}
|
|
||||||
if (width <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
left = src_y + height - src_h;
|
|
||||||
if (left > 0) {
|
|
||||||
height -= left;
|
|
||||||
}
|
|
||||||
left = dst_y + height - dst_h;
|
|
||||||
if (left > 0) {
|
|
||||||
height -= left;
|
|
||||||
}
|
|
||||||
if (height <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init
|
|
||||||
line_stride = (size_t) (width * 4);
|
|
||||||
src_stride = src_w * 4;
|
|
||||||
src_pos = src_y * src_stride + src_x * 4;
|
|
||||||
dst_pos = 0;
|
|
||||||
|
|
||||||
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
|
|
||||||
|
|
||||||
// First line
|
|
||||||
dst_pos += (int)dst_blank_length;
|
|
||||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
|
||||||
dst_pos += (int)line_stride;
|
|
||||||
src_pos += src_stride;
|
|
||||||
|
|
||||||
// Other lines
|
|
||||||
dst_blank_length = (size_t) ((dst_w - width) * 4);
|
|
||||||
for (line = 1; line < height; line++) {
|
|
||||||
dst_pos += (int)dst_blank_length;
|
|
||||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
|
||||||
dst_pos += (int)line_stride;
|
|
||||||
src_pos += src_stride;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void decodeCommon(IMAGE *image, SOURCE *src) {
|
|
||||||
image->src = src;
|
|
||||||
AImageDecoder *decoder = src->decoder;
|
|
||||||
image->isAnimated = AImageDecoder_isAnimated(decoder);
|
|
||||||
AImageDecoder_setDataSpace(decoder, ADATASPACE_SRGB);
|
|
||||||
AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888);
|
|
||||||
const AImageDecoderHeaderInfo *headerInfo = AImageDecoder_getHeaderInfo(decoder);
|
|
||||||
image->height = AImageDecoderHeaderInfo_getHeight(headerInfo);
|
|
||||||
image->width = AImageDecoderHeaderInfo_getWidth(headerInfo);
|
|
||||||
image->alpha =
|
|
||||||
AImageDecoderHeaderInfo_getAlphaFlags(headerInfo) == ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
|
|
||||||
src->stride = AImageDecoder_getMinimumStride(decoder);
|
|
||||||
image->bufferLen = (int) (image->height * src->stride);
|
|
||||||
image->buffer = malloc(image->bufferLen);
|
|
||||||
if (image->isAnimated) {
|
|
||||||
AImageDecoder_setInternallyHandleDisposePrevious(decoder, false);
|
|
||||||
AImageDecoderFrameInfo *frameInfo = AImageDecoderFrameInfo_create();
|
|
||||||
AImageDecoder_getFrameInfo(decoder, frameInfo);
|
|
||||||
image->delay = (int) AImageDecoderFrameInfo_getDuration(frameInfo) / 1000000;
|
|
||||||
AImageDecoderFrameInfo_delete(frameInfo);
|
|
||||||
}
|
|
||||||
AImageDecoder_decodeImage(decoder, image->buffer, src->stride, image->bufferLen);
|
|
||||||
if (!image->isAnimated) {
|
|
||||||
recycleSource(image->src);
|
|
||||||
image->src = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IMAGE *createFromFd(int fd) {
|
|
||||||
AImageDecoder *decoder = NULL;
|
|
||||||
if (AImageDecoder_createFromFd(fd, &decoder) != ANDROID_IMAGE_DECODER_SUCCESS)
|
|
||||||
return NULL;
|
|
||||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
|
||||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
|
||||||
src->decoder = decoder;
|
|
||||||
src->fd = fd;
|
|
||||||
decodeCommon(image, src);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
IMAGE *createFromAddr(void *addr, long size) {
|
|
||||||
AImageDecoder *decoder;
|
|
||||||
AImageDecoder_createFromBuffer((const void *) addr, size, &decoder);
|
|
||||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
|
||||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
|
||||||
src->decoder = decoder;
|
|
||||||
src->srcBuffer = addr;
|
|
||||||
decodeCommon(image, src);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
IMAGE *create(int32_t width, int32_t height, const void *data) {
|
|
||||||
IMAGE *plain = calloc(1, sizeof(IMAGE));
|
|
||||||
void *buffer = NULL;
|
|
||||||
size_t length = width * height * 4;
|
|
||||||
|
|
||||||
buffer = malloc(length);
|
|
||||||
memcpy(buffer, data, length);
|
|
||||||
|
|
||||||
// plain->isAnimated = false; this is unnecessary since we use calloc
|
|
||||||
plain->width = width;
|
|
||||||
plain->height = height;
|
|
||||||
plain->buffer = buffer;
|
|
||||||
plain->bufferLen = (int) length;
|
|
||||||
|
|
||||||
return plain;
|
|
||||||
}
|
|
||||||
|
|
||||||
void render(IMAGE *image, int src_x, int src_y,
|
|
||||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
|
||||||
int width, int height) {
|
|
||||||
copy_pixels(image->buffer, image->width, image->height, src_x, src_y, dst, dst_w, dst_h, dst_x,
|
|
||||||
dst_y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void advance(IMAGE *image) {
|
|
||||||
if (image->isAnimated) {
|
|
||||||
AImageDecoder_advanceFrame(image->src->decoder);
|
|
||||||
if (AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
|
||||||
image->bufferLen) == ANDROID_IMAGE_DECODER_FINISHED) {
|
|
||||||
AImageDecoder_rewind(image->src->decoder);
|
|
||||||
AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
|
||||||
image->bufferLen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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;
|
|
||||||
void *pixels = NULL;
|
|
||||||
void *image = NULL;
|
|
||||||
|
|
||||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
|
||||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
|
||||||
|
|
||||||
image = create((int)info.width, (int)info.height, pixels);
|
|
||||||
AndroidBitmap_unlockPixels(env, bitmap);
|
|
||||||
return (jlong) image;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jlong ptr, jint src_x,
|
|
||||||
jint src_y, jobject dst, jint dst_x, jint dst_y, jint width,
|
|
||||||
jint height) {
|
|
||||||
AndroidBitmapInfo info;
|
|
||||||
void *pixels = NULL;
|
|
||||||
|
|
||||||
AndroidBitmap_getInfo(env, dst, &info);
|
|
||||||
AndroidBitmap_lockPixels(env, dst, &pixels);
|
|
||||||
|
|
||||||
render((IMAGE *) ptr, src_x, src_y, pixels, (int)info.width, (int)info.height, dst_x, dst_y, width,
|
|
||||||
height);
|
|
||||||
|
|
||||||
AndroidBitmap_unlockPixels(env, dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jlong ptr, jboolean init,
|
|
||||||
jint src_x, jint src_y, jint width, jint height) {
|
|
||||||
if (width * height > IMAGE_TILE_MAX_SIZE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
render((IMAGE *) ptr, src_x, src_y, tile_buffer, width, height, 0, 0, width, height);
|
|
||||||
|
|
||||||
if (init) {
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
||||||
tile_buffer);
|
|
||||||
} else {
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
||||||
tile_buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeAdvance(JNIEnv *env, jclass clazz, jlong ptr) {
|
|
||||||
advance((IMAGE *) ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeGetDelay(JNIEnv *env, jclass clazz, jlong ptr) {
|
|
||||||
return ((IMAGE *) ptr)->delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeIsOpaque(JNIEnv *env, jclass clazz, jlong ptr) {
|
|
||||||
return ((IMAGE *) ptr)->alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeRecycle(JNIEnv *env, jclass clazz, jlong ptr) {
|
|
||||||
recycle((IMAGE *) ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeDecodeAddr(JNIEnv *env, jclass clazz, jlong addr) {
|
|
||||||
Memarea *memarea = (Memarea *) addr;
|
|
||||||
IMAGE *image = createFromAddr(memarea->buffer, memarea->size);
|
|
||||||
free(memarea);
|
|
||||||
return (jlong) image;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeGetFormat(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
|
||||||
return ((IMAGE *) native_ptr)->isAnimated;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeGetWidth(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
|
||||||
return ((IMAGE *) native_ptr)->width;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_hippo_image_Image_nativeGetHeight(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
|
||||||
return ((IMAGE *) native_ptr)->height;
|
|
||||||
}
|
|
||||||
@@ -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 */
|
|
||||||
Submodule app/src/main/cpp/libarchive updated: 37d2cf6063...c9298fd6dc.
+1
-1
Submodule app/src/main/cpp/liblzma updated: 2327a461e1...b35079d589.
@@ -1,45 +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();
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,160 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.net.Uri
|
||||||
|
import com.davemorrissey.labs.subscaleview.provider.AssetInputProvider
|
||||||
|
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||||
|
import com.davemorrissey.labs.subscaleview.provider.OpenStreamProvider
|
||||||
|
import com.davemorrissey.labs.subscaleview.provider.ResourceInputProvider
|
||||||
|
import com.davemorrissey.labs.subscaleview.provider.UriInputProvider
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class used to set the source and additional attributes from a variety of sources. Supports
|
||||||
|
* use of a bitmap, asset, resource, external file or any other URI.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* When you are using a preview image, you must set the dimensions of the full size image on the
|
||||||
|
* ImageSource object for the full size image using the [.dimensions] method.
|
||||||
|
*/
|
||||||
|
class ImageSource {
|
||||||
|
val bitmap: Bitmap?
|
||||||
|
val provider: InputProvider?
|
||||||
|
|
||||||
|
private var sWidth = 0
|
||||||
|
private var sHeight = 0
|
||||||
|
|
||||||
|
var sRegion = Rect(0, 0, 0, 0)
|
||||||
|
private set
|
||||||
|
var isCached = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
private constructor(bitmap: Bitmap, cached: Boolean) {
|
||||||
|
this.bitmap = bitmap
|
||||||
|
provider = null
|
||||||
|
sWidth = bitmap.width
|
||||||
|
sHeight = bitmap.height
|
||||||
|
isCached = cached
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(provider: InputProvider) {
|
||||||
|
bitmap = null
|
||||||
|
this.provider = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a region of the source image. Region must be set independently for the full size image and the preview if
|
||||||
|
* you are using one.
|
||||||
|
*
|
||||||
|
* @param sRegion the region of the source image to be displayed.
|
||||||
|
* @return this instance for chaining.
|
||||||
|
*/
|
||||||
|
fun region(sRegion: Rect): ImageSource {
|
||||||
|
this.sRegion = sRegion
|
||||||
|
setInvariants()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
|
||||||
|
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
|
||||||
|
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
|
||||||
|
*
|
||||||
|
* @param sWidth width of the source image.
|
||||||
|
* @param sHeight height of the source image.
|
||||||
|
* @return this instance for chaining.
|
||||||
|
*/
|
||||||
|
fun dimensions(sWidth: Int, sHeight: Int): ImageSource {
|
||||||
|
if (bitmap == null) {
|
||||||
|
this.sWidth = sWidth
|
||||||
|
this.sHeight = sHeight
|
||||||
|
}
|
||||||
|
setInvariants()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setInvariants() {
|
||||||
|
if (!sRegion.isEmpty) {
|
||||||
|
sWidth = sRegion.width()
|
||||||
|
sHeight = sRegion.height()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Create an instance from a resource. The correct resource for the device screen resolution will be used.
|
||||||
|
*
|
||||||
|
* @param resId resource ID.
|
||||||
|
* @return an [ImageSource] instance.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun resource(context: Context, resId: Int): ImageSource {
|
||||||
|
return ImageSource(ResourceInputProvider(context, resId))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance from an asset name.
|
||||||
|
*
|
||||||
|
* @param assetName asset name.
|
||||||
|
* @return an [ImageSource] instance.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun asset(context: Context, assetName: String): ImageSource {
|
||||||
|
return ImageSource(AssetInputProvider(context, assetName))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance from a URI.
|
||||||
|
*
|
||||||
|
* @param uri image URI.
|
||||||
|
* @return an [ImageSource] instance.
|
||||||
|
*/
|
||||||
|
fun uri(context: Context, uri: Uri): ImageSource {
|
||||||
|
return ImageSource(UriInputProvider(context, uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance from an input provider.
|
||||||
|
*
|
||||||
|
* @param provider input stream provider.
|
||||||
|
* @return an [ImageSource] instance.
|
||||||
|
*/
|
||||||
|
fun provider(provider: InputProvider): ImageSource {
|
||||||
|
return ImageSource(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance from an input stream.
|
||||||
|
*
|
||||||
|
* @param stream open input stream.
|
||||||
|
* @return an [ImageSource] instance.
|
||||||
|
*/
|
||||||
|
fun inputStream(stream: InputStream): ImageSource {
|
||||||
|
return ImageSource(OpenStreamProvider(stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a loaded bitmap for display.
|
||||||
|
*
|
||||||
|
* @param bitmap bitmap to be displayed.
|
||||||
|
* @return an [ImageSource] instance.
|
||||||
|
*/
|
||||||
|
fun bitmap(bitmap: Bitmap): ImageSource {
|
||||||
|
return ImageSource(bitmap, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
|
||||||
|
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
|
||||||
|
* or Volley.
|
||||||
|
*
|
||||||
|
* @param bitmap bitmap to be displayed.
|
||||||
|
* @return an [ImageSource] instance.
|
||||||
|
*/
|
||||||
|
fun cachedBitmap(bitmap: Bitmap): ImageSource {
|
||||||
|
return ImageSource(bitmap, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview
|
||||||
|
|
||||||
|
import android.graphics.PointF
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
|
||||||
|
*/
|
||||||
|
class ImageViewState(val scale: Float, center: PointF) : Serializable {
|
||||||
|
private val centerX: Float = center.x
|
||||||
|
private val centerY: Float = center.y
|
||||||
|
|
||||||
|
val center: PointF
|
||||||
|
get() = PointF(centerX, centerY)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,53 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview.decoder
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.graphics.Rect
|
||||||
|
import kotlin.Throws
|
||||||
|
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for image decoding classes.
|
||||||
|
*/
|
||||||
|
interface Decoder {
|
||||||
|
/**
|
||||||
|
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||||
|
* dimensions of the image must be returned.
|
||||||
|
*
|
||||||
|
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||||
|
* @param provider Provider of the image.
|
||||||
|
* @return Dimensions of the image.
|
||||||
|
* @throws Exception if initialisation fails.
|
||||||
|
*/
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun init(context: Context, provider: InputProvider): Point
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||||
|
* thread so it can safely load the image on the current thread. It is called from
|
||||||
|
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||||
|
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||||
|
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||||
|
* called concurrently.
|
||||||
|
*
|
||||||
|
* @param sRect Source image rectangle to decode.
|
||||||
|
* @param sampleSize Sample size.
|
||||||
|
* @return The decoded region. It is safe to return null if decoding fails.
|
||||||
|
*/
|
||||||
|
fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status check. Should return false before initialisation and after recycle.
|
||||||
|
*
|
||||||
|
* @return true if the decoder is ready to be used.
|
||||||
|
*/
|
||||||
|
val isReady: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called when the decoder is no longer required. It should clean up any
|
||||||
|
* resources still in use.
|
||||||
|
*/
|
||||||
|
fun recycle()
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview.decoder
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.graphics.Rect
|
||||||
|
import kotlin.Throws
|
||||||
|
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
class ImageDecoder @JvmOverloads constructor(
|
||||||
|
private val cropBorders: Boolean,
|
||||||
|
bitmapConfig: Bitmap.Config? = null
|
||||||
|
) : Decoder {
|
||||||
|
init {
|
||||||
|
val globalBitmapConfig = SubsamplingScaleImageView.preferredBitmapConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||||
|
* dimensions of the image must be returned.
|
||||||
|
*
|
||||||
|
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||||
|
* @param provider Provider of the image.
|
||||||
|
* @return Dimensions of the image.
|
||||||
|
* @throws Exception if initialisation fails.
|
||||||
|
*/
|
||||||
|
@Throws(Exception::class)
|
||||||
|
override fun init(context: Context, provider: InputProvider): Point {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||||
|
* thread so it can safely load the image on the current thread. It is called from
|
||||||
|
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||||
|
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||||
|
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||||
|
* called concurrently.
|
||||||
|
*
|
||||||
|
* @param sRect Source image rectangle to decode.
|
||||||
|
* @param sampleSize Sample size.
|
||||||
|
* @return The decoded region. It is safe to return null if decoding fails.
|
||||||
|
*/
|
||||||
|
override fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status check. Should return false before initialisation and after recycle.
|
||||||
|
*
|
||||||
|
* @return true if the decoder is ready to be used.
|
||||||
|
*/
|
||||||
|
override val isReady: Boolean = TODO()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called when the decoder is no longer required. It should clean up any
|
||||||
|
* resources still in use.
|
||||||
|
*/
|
||||||
|
override fun recycle() {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview.provider
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class AssetInputProvider(context: Context, val assetName: String) : InputProvider {
|
||||||
|
private val assets = context.assets
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun openStream() = assets.open(assetName)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview.provider
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import kotlin.Throws
|
||||||
|
|
||||||
|
interface InputProvider {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun openStream(): InputStream?
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview.provider
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import kotlin.Throws
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider from an already open input stream.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Please keep in mind this provider is not reusable.
|
||||||
|
*/
|
||||||
|
class OpenStreamProvider(private val stream: InputStream) : InputProvider {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun openStream() = stream
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview.provider
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class ResourceInputProvider(context: Context, val resource: Int) : InputProvider {
|
||||||
|
private val resources = context.resources
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun openStream() = resources.openRawResource(resource)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.davemorrissey.labs.subscaleview.provider
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import java.io.IOException
|
||||||
|
import kotlin.Throws
|
||||||
|
|
||||||
|
class UriInputProvider(context: Context, val uri: Uri) : InputProvider {
|
||||||
|
private val resolver = context.contentResolver
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun openStream() = resolver.openInputStream(uri)
|
||||||
|
}
|
||||||
@@ -15,11 +15,15 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
package com.hippo
|
||||||
|
|
||||||
package com.hippo;
|
import java.io.FileDescriptor
|
||||||
|
|
||||||
public class Native {
|
object Native {
|
||||||
public static void initialize() {
|
fun initialize() {
|
||||||
System.loadLibrary("ehviewer");
|
System.loadLibrary("ehviewer")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun getFd(fd: FileDescriptor?): Int
|
||||||
|
}
|
||||||
@@ -1,58 +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;
|
|
||||||
import android.system.Os;
|
|
||||||
import android.system.OsConstants;
|
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
|
|
||||||
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,43 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.nio.ByteBuffer
|
||||||
|
|
||||||
|
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 extractToByteBuffer(index: Int): ByteBuffer?
|
||||||
|
|
||||||
|
external fun releaseByteBuffer(buffer: ByteBuffer)
|
||||||
|
external fun extractToFd(index: Int, fd: Int)
|
||||||
|
external fun getFilename(index: Int): String
|
||||||
|
external fun needPassword(): Boolean
|
||||||
|
external fun providePassword(str: String): Boolean
|
||||||
|
private external fun closeArchive()
|
||||||
|
fun close() {
|
||||||
|
closeArchive()
|
||||||
|
pfd.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +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.annotation;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target({ElementType.METHOD})
|
|
||||||
public @interface Implemented {
|
|
||||||
Class value();
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,8 @@
|
|||||||
package com.hippo.app
|
package com.hippo.app
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
||||||
open class BaseDialogBuilder constructor(
|
open class BaseDialogBuilder constructor(
|
||||||
context: Context
|
context: Context
|
||||||
) : MaterialAlertDialogBuilder(context) {
|
) : MaterialAlertDialogBuilder(context)
|
||||||
override fun create(): AlertDialog {
|
|
||||||
return super.create().also { dialog ->
|
|
||||||
dialog.window?.let {
|
|
||||||
it.addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
|
||||||
it.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
|
||||||
it.attributes.blurBehindRadius = 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,493 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 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.beerbelly;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.LruCache;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.hippo.streampipe.InputStreamPipe;
|
|
||||||
import com.hippo.streampipe.OutputStreamPipe;
|
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public abstract class BeerBelly<V> {
|
|
||||||
|
|
||||||
private static final String TAG = BeerBelly.class.getSimpleName();
|
|
||||||
private final boolean mHasMemoryCache;
|
|
||||||
private final boolean mHasDiskCache;
|
|
||||||
@Nullable
|
|
||||||
private MemoryCache<V> mMemoryCache;
|
|
||||||
@Nullable
|
|
||||||
private DiskCache<V> mDiskCache;
|
|
||||||
|
|
||||||
public BeerBelly(BeerBellyParams params) {
|
|
||||||
params.isValid();
|
|
||||||
mHasMemoryCache = params.hasMemoryCache;
|
|
||||||
mHasDiskCache = params.hasDiskCache;
|
|
||||||
|
|
||||||
if (mHasMemoryCache) {
|
|
||||||
initMemoryCache(params.memoryCacheMaxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mHasDiskCache) {
|
|
||||||
initDiskCache(params.diskCacheDir, params.diskCacheMaxSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initMemoryCache(int maxSize) {
|
|
||||||
mMemoryCache = new MemoryCache<>(maxSize, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDiskCache(File cacheDir, int maxSize) {
|
|
||||||
// Set up disk cache
|
|
||||||
try {
|
|
||||||
mDiskCache = new DiskCache<>(cacheDir, maxSize, this);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Can't create disk cache", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract int sizeOf(String key, V value);
|
|
||||||
|
|
||||||
protected abstract void memoryEntryAdded(V value);
|
|
||||||
|
|
||||||
protected abstract void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue);
|
|
||||||
|
|
||||||
protected abstract V read(@NonNull InputStreamPipe isPipe);
|
|
||||||
|
|
||||||
protected abstract boolean write(OutputStream os, V value);
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public LruCache<String, V> getMemoryCache() {
|
|
||||||
return mMemoryCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public SimpleDiskCache getDiskCache() {
|
|
||||||
if (mDiskCache != null) {
|
|
||||||
return mDiskCache.mDiskCache;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if have memory cache
|
|
||||||
*
|
|
||||||
* @return true if have memory cache
|
|
||||||
*/
|
|
||||||
public boolean hasMemoryCache() {
|
|
||||||
return mHasMemoryCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if have disk cache
|
|
||||||
*
|
|
||||||
* @return true if have disk cache
|
|
||||||
*/
|
|
||||||
public boolean hasDiskCache() {
|
|
||||||
return mHasDiskCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get memory cache usage size
|
|
||||||
*
|
|
||||||
* @return {@code -1} for no memory cache
|
|
||||||
*/
|
|
||||||
public int memorySize() {
|
|
||||||
if (mHasMemoryCache && mMemoryCache != null) {
|
|
||||||
return mMemoryCache.size();
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get memory cache max size
|
|
||||||
*
|
|
||||||
* @return {@code -1} for no memory cache
|
|
||||||
*/
|
|
||||||
public int memoryMaxSize() {
|
|
||||||
if (mHasMemoryCache && mMemoryCache != null) {
|
|
||||||
return mMemoryCache.maxSize();
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get disk cache usage size
|
|
||||||
*
|
|
||||||
* @return {@code -1L} for no memory cache
|
|
||||||
*/
|
|
||||||
public long diskSize() {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
return mDiskCache.size();
|
|
||||||
} else {
|
|
||||||
return -1L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get disk cache max size
|
|
||||||
*
|
|
||||||
* @return {@code -1L} for no memory cache
|
|
||||||
*/
|
|
||||||
public long diskMaxSize() {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
return mDiskCache.getMaxSize();
|
|
||||||
} else {
|
|
||||||
return -1L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value from memory cache
|
|
||||||
*
|
|
||||||
* @param key the key to get value
|
|
||||||
* @return the value you get, null for miss or no memory cache
|
|
||||||
*/
|
|
||||||
public V getFromMemory(@NonNull String key) {
|
|
||||||
if (mHasMemoryCache && mMemoryCache != null) {
|
|
||||||
return mMemoryCache.get(key);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value from disk cache. Override {@link #read(InputStreamPipe)} to do it
|
|
||||||
*
|
|
||||||
* @param key the key to get value
|
|
||||||
* @return the value you get, null for miss or no memory cache or get error
|
|
||||||
*/
|
|
||||||
public V getFromDisk(@NonNull String key) {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
return mDiskCache.get(key);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value from memory cache and disk cache. If miss in memory cache and
|
|
||||||
* get in disk cache, it will put value from disk cache to memory cache.
|
|
||||||
*
|
|
||||||
* @param key the key to get value
|
|
||||||
* @return the value you get
|
|
||||||
*/
|
|
||||||
public V get(@NonNull String key) {
|
|
||||||
V value = getFromMemory(key);
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
// Get it in memory cache
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = getFromDisk(key);
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
// Get it in disk cache
|
|
||||||
putToMemory(key, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put value to memory cache
|
|
||||||
*
|
|
||||||
* @param key the key
|
|
||||||
* @param value the value
|
|
||||||
* @return false if no memory cache
|
|
||||||
*/
|
|
||||||
public boolean putToMemory(@NonNull String key, @NonNull V value) {
|
|
||||||
if (mHasMemoryCache && mMemoryCache != null) {
|
|
||||||
memoryEntryAdded(value);
|
|
||||||
mMemoryCache.put(key, value);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put value to disk cache
|
|
||||||
*
|
|
||||||
* @param key the key
|
|
||||||
* @param value the value
|
|
||||||
* @return false if no disk cache or get error
|
|
||||||
*/
|
|
||||||
public boolean putToDisk(@NonNull String key, @NonNull V value) {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
return mDiskCache.put(key, value);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put value to memory cache and disk cache
|
|
||||||
*
|
|
||||||
* @param key the key
|
|
||||||
* @param value the value
|
|
||||||
*/
|
|
||||||
public void put(@NonNull String key, @NonNull V value) {
|
|
||||||
putToMemory(key, value);
|
|
||||||
putToDisk(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeFromMemory(@NonNull String key) {
|
|
||||||
if (mHasMemoryCache && mMemoryCache != null) {
|
|
||||||
mMemoryCache.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeFromDisk(@NonNull String key) {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
mDiskCache.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove(@NonNull String key) {
|
|
||||||
removeFromMemory(key);
|
|
||||||
removeFromDisk(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean pullFromDiskCache(@NonNull String key, @NonNull OutputStream os) {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
return mDiskCache.pull(key, os);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param key the key
|
|
||||||
* @param is the input stream to store
|
|
||||||
* @return false if no disk cache or get error
|
|
||||||
*/
|
|
||||||
public boolean putRawToDisk(@NonNull String key, @NonNull InputStream is) {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
return mDiskCache.putRaw(key, is);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Evicts all of the items from the memory cache
|
|
||||||
*/
|
|
||||||
public void clearMemory() {
|
|
||||||
if (mHasMemoryCache && mMemoryCache != null) {
|
|
||||||
mMemoryCache.evictAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear disk cache
|
|
||||||
*/
|
|
||||||
public void clearDisk() {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
mDiskCache.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void flush() {
|
|
||||||
if (mHasDiskCache && mDiskCache != null) {
|
|
||||||
mDiskCache.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Evicts all of the items from the memory cache and lets the system know
|
|
||||||
* now would be a good time to garbage collect
|
|
||||||
*/
|
|
||||||
public void clear() {
|
|
||||||
clearMemory();
|
|
||||||
clearDisk();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A holder class that contains cache parameters.
|
|
||||||
*/
|
|
||||||
public static class BeerBellyParams {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* is memory cache available
|
|
||||||
*/
|
|
||||||
public boolean hasMemoryCache = false;
|
|
||||||
/**
|
|
||||||
* the maximum number of bytes the memory cache should use to store
|
|
||||||
*/
|
|
||||||
public int memoryCacheMaxSize = 0;
|
|
||||||
/**
|
|
||||||
* is disk cache available
|
|
||||||
*/
|
|
||||||
public boolean hasDiskCache = false;
|
|
||||||
/**
|
|
||||||
* the dir to store disk cache
|
|
||||||
*/
|
|
||||||
public File diskCacheDir = null;
|
|
||||||
/**
|
|
||||||
* the maximum number of bytes the disk cache should use to store
|
|
||||||
*/
|
|
||||||
public int diskCacheMaxSize = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check BeerBellyParams is valid
|
|
||||||
*/
|
|
||||||
public void isValid() throws IllegalStateException {
|
|
||||||
if (!hasMemoryCache && !hasDiskCache) {
|
|
||||||
throw new IllegalStateException("No memory cache and no disk cache. What can I do for you?");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasMemoryCache && memoryCacheMaxSize <= 0) {
|
|
||||||
throw new IllegalStateException("Memory cache max size must be bigger than 0.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasDiskCache) {
|
|
||||||
if (diskCacheDir == null) {
|
|
||||||
throw new IllegalStateException("Disk cache dir can't be null.");
|
|
||||||
}
|
|
||||||
if (diskCacheMaxSize <= 0) {
|
|
||||||
throw new IllegalStateException("Disk cache max size must be bigger than 0.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DiskCache<E> {
|
|
||||||
|
|
||||||
private static final int IO_BUFFER_SIZE = 8 * 1024;
|
|
||||||
|
|
||||||
private final SimpleDiskCache mDiskCache;
|
|
||||||
private final BeerBelly<E> mParent;
|
|
||||||
|
|
||||||
private final File mCacheDir;
|
|
||||||
private final int mMaxSize;
|
|
||||||
|
|
||||||
public DiskCache(File cacheDir, int size, BeerBelly<E> parent) throws IOException {
|
|
||||||
mDiskCache = new SimpleDiskCache(cacheDir, size);
|
|
||||||
mParent = parent;
|
|
||||||
|
|
||||||
mCacheDir = cacheDir;
|
|
||||||
mMaxSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getCacheDir() {
|
|
||||||
return mCacheDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxSize() {
|
|
||||||
return mMaxSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long size() {
|
|
||||||
return mDiskCache.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void flush() {
|
|
||||||
mDiskCache.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
mDiskCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public E get(String key) {
|
|
||||||
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
|
|
||||||
if (isPipe == null) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return mParent.read(isPipe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean put(String key, E value) {
|
|
||||||
OutputStreamPipe osPipe = mDiskCache.getOutputStreamPipe(key);
|
|
||||||
BufferedOutputStream buffOut = null;
|
|
||||||
try {
|
|
||||||
osPipe.obtain();
|
|
||||||
OutputStream os = osPipe.open();
|
|
||||||
buffOut = new BufferedOutputStream(os, IO_BUFFER_SIZE);
|
|
||||||
return mParent.write(os, value);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
Util.closeQuietly(buffOut);
|
|
||||||
osPipe.close();
|
|
||||||
osPipe.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean putRaw(String key, InputStream is) {
|
|
||||||
return mDiskCache.put(key, is);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove(String key) {
|
|
||||||
mDiskCache.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean pull(@NonNull String key, @NonNull OutputStream os) {
|
|
||||||
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
|
|
||||||
if (isPipe == null) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
isPipe.obtain();
|
|
||||||
InputStream is = isPipe.open();
|
|
||||||
Util.copy(is, os);
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
isPipe.close();
|
|
||||||
isPipe.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MemoryCache<E> extends LruCache<String, E> {
|
|
||||||
|
|
||||||
public BeerBelly<E> mParent;
|
|
||||||
|
|
||||||
public MemoryCache(int maxSize, BeerBelly<E> parent) {
|
|
||||||
super(maxSize);
|
|
||||||
mParent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int sizeOf(String key, E value) {
|
|
||||||
return mParent.sizeOf(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void entryRemoved(boolean evicted, String key, E oldValue, E newValue) {
|
|
||||||
mParent.memoryEntryRemoved(evicted, key, oldValue, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -231,21 +231,7 @@ public final class DiskLruCache implements Closeable {
|
|||||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||||
cache.rebuildJournal();
|
cache.rebuildJournal();
|
||||||
return cache;
|
return cache;
|
||||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
}
|
||||||
public Void call() throws Exception {
|
|
||||||
synchronized (DiskLruCache.this) {
|
|
||||||
if (journalWriter == null) {
|
|
||||||
return null; // Closed.
|
|
||||||
}
|
|
||||||
trimToSize();
|
|
||||||
if (journalRebuildRequired()) {
|
|
||||||
rebuildJournal();
|
|
||||||
redundantOpCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static void deleteIfExists(File file) throws IOException {
|
private static void deleteIfExists(File file) throws IOException {
|
||||||
if (file.exists() && !file.delete()) {
|
if (file.exists() && !file.delete()) {
|
||||||
@@ -343,7 +329,21 @@ public final class DiskLruCache implements Closeable {
|
|||||||
} else {
|
} else {
|
||||||
throw new IOException("unexpected journal line: " + line);
|
throw new IOException("unexpected journal line: " + line);
|
||||||
}
|
}
|
||||||
}
|
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||||
|
public Void call() throws Exception {
|
||||||
|
synchronized (DiskLruCache.this) {
|
||||||
|
if (journalWriter == null) {
|
||||||
|
return null; // Closed.
|
||||||
|
}
|
||||||
|
trimToSize();
|
||||||
|
if (journalRebuildRequired()) {
|
||||||
|
rebuildJournal();
|
||||||
|
redundantOpCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the initial size and collects garbage as a part of opening the
|
* Computes the initial size and collects garbage as a part of opening the
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import java.io.Closeable;
|
|||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,7 +139,7 @@ class StrictLineReader implements Closeable {
|
|||||||
for (int i = pos; i != end; ++i) {
|
for (int i = pos; i != end; ++i) {
|
||||||
if (buf[i] == LF) {
|
if (buf[i] == LF) {
|
||||||
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
|
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
|
||||||
String res = new String(buf, pos, lineEnd - pos, charset.name());
|
String res = new String(buf, pos, lineEnd - pos, charset);
|
||||||
pos = i + 1;
|
pos = i + 1;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -152,11 +151,7 @@ class StrictLineReader implements Closeable {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
||||||
try {
|
return new String(buf, 0, length, charset);
|
||||||
return new String(buf, 0, length, charset.name());
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new AssertionError(e); // Since we control the charset this will never happen.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,223 +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 android.os.Process;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
import com.hippo.beerbelly.BeerBelly;
|
|
||||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
|
||||||
import com.hippo.yorozuya.thread.SerialThreadExecutor;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public final class Conaco<V> {
|
|
||||||
|
|
||||||
public static final int SOURCE_MEMORY = 0;
|
|
||||||
public static final int SOURCE_DISK = 1;
|
|
||||||
public static final int SOURCE_NETWORK = 2;
|
|
||||||
private static final String TAG = Conaco.class.getSimpleName();
|
|
||||||
private final ValueHelper<V> mHelper;
|
|
||||||
private final ValueCache<V> mCache;
|
|
||||||
private final OkHttpClient mOkHttpClient;
|
|
||||||
private final Register<V> mRegister;
|
|
||||||
private final SerialThreadExecutor mDiskExecutor;
|
|
||||||
private final ThreadPoolExecutor mNetworkExecutor;
|
|
||||||
private final AtomicInteger mIdGenerator;
|
|
||||||
private final boolean mDebug;
|
|
||||||
|
|
||||||
private Conaco(Builder<V> builder) {
|
|
||||||
mHelper = builder.objectHelper;
|
|
||||||
|
|
||||||
BeerBelly.BeerBellyParams beerBellyParams = new BeerBelly.BeerBellyParams();
|
|
||||||
beerBellyParams.hasMemoryCache = builder.hasMemoryCache;
|
|
||||||
beerBellyParams.memoryCacheMaxSize = builder.memoryCacheMaxSize;
|
|
||||||
beerBellyParams.hasDiskCache = builder.hasDiskCache;
|
|
||||||
beerBellyParams.diskCacheDir = builder.diskCacheDir;
|
|
||||||
beerBellyParams.diskCacheMaxSize = builder.diskCacheMaxSize;
|
|
||||||
|
|
||||||
mCache = new ValueCache<>(beerBellyParams, mHelper);
|
|
||||||
mOkHttpClient = builder.okHttpClient;
|
|
||||||
|
|
||||||
mRegister = new Register<>();
|
|
||||||
|
|
||||||
mDiskExecutor = new SerialThreadExecutor(3000L, new LinkedList<>(),
|
|
||||||
new PriorityThreadFactory("Conaco-Disk", Process.THREAD_PRIORITY_BACKGROUND));
|
|
||||||
mNetworkExecutor = new ThreadPoolExecutor(3, 3, 5L, TimeUnit.SECONDS,
|
|
||||||
new LinkedBlockingQueue<>(),
|
|
||||||
new PriorityThreadFactory("Conaco-Network", Process.THREAD_PRIORITY_BACKGROUND));
|
|
||||||
|
|
||||||
mIdGenerator = new AtomicInteger();
|
|
||||||
|
|
||||||
mDebug = builder.debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BeerBelly getBeerBelly() {
|
|
||||||
return mCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onUnregisterConacoTask(ConacoTask<V> task) {
|
|
||||||
ConacoTask<V> next = mRegister.getByKey(task.getKey());
|
|
||||||
if (next != null) {
|
|
||||||
startConacoTask(next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startConacoTask(ConacoTask<V> task) {
|
|
||||||
Unikery<V> unikery = task.getUnikery();
|
|
||||||
|
|
||||||
// Can't unikery is gone, finish the task now
|
|
||||||
if (unikery == null) {
|
|
||||||
finishConacoTask(task);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String key = task.getKey();
|
|
||||||
V value = null;
|
|
||||||
|
|
||||||
// Get from memory
|
|
||||||
if (key != null && task.useMemoryCache() && mHelper.useMemoryCache(key, null)) {
|
|
||||||
value = mCache.getFromMemory(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == null || !unikery.onGetValue(value, SOURCE_MEMORY)) {
|
|
||||||
unikery.onMiss(SOURCE_MEMORY);
|
|
||||||
task.start();
|
|
||||||
} else {
|
|
||||||
// Get the object, finish the task
|
|
||||||
finishConacoTask(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
public void load(ConacoTask.Builder<V> builder) {
|
|
||||||
builder.isValid();
|
|
||||||
|
|
||||||
if (mDebug) {
|
|
||||||
Log.d(TAG, "Key " + builder.getKey());
|
|
||||||
Log.d(TAG, "Url " + builder.getUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
Unikery<V> unikery = builder.getUnikery();
|
|
||||||
|
|
||||||
// Cancel first
|
|
||||||
cancel(unikery);
|
|
||||||
|
|
||||||
// Build conaco task
|
|
||||||
int id;
|
|
||||||
//noinspection StatementWithEmptyBody
|
|
||||||
while ((id = mIdGenerator.getAndIncrement()) == Unikery.INVALID_ID) ;
|
|
||||||
unikery.setTaskId(id);
|
|
||||||
builder.setId(id)
|
|
||||||
.setHelper(mHelper)
|
|
||||||
.setCache(mCache)
|
|
||||||
.setOkHttpClient(mOkHttpClient)
|
|
||||||
.setDiskExecutor(mDiskExecutor)
|
|
||||||
.setNetworkExecutor(mNetworkExecutor)
|
|
||||||
.setConaco(this);
|
|
||||||
ConacoTask<V> task = builder.build();
|
|
||||||
|
|
||||||
if (!mRegister.register(id, task)) {
|
|
||||||
startConacoTask(task);
|
|
||||||
} else {
|
|
||||||
// The key is repeated, wait
|
|
||||||
unikery.onWait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
public void cancel(Unikery<V> unikery) {
|
|
||||||
int id = unikery.getTaskId();
|
|
||||||
if (id != Unikery.INVALID_ID) {
|
|
||||||
unikery.setTaskId(Unikery.INVALID_ID);
|
|
||||||
ConacoTask<V> task = mRegister.unregister(id);
|
|
||||||
if (task != null) {
|
|
||||||
task.stop();
|
|
||||||
// Don't need unikery anymore
|
|
||||||
task.clearUnikery();
|
|
||||||
// Callback
|
|
||||||
onUnregisterConacoTask(task);
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Can't find conaco task by id " + id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLoading(Unikery<V> unikery) {
|
|
||||||
int id = unikery.getTaskId();
|
|
||||||
return id != Unikery.INVALID_ID && mRegister.contain(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregister task, reset unikery id, clear unikery in task, call next
|
|
||||||
*/
|
|
||||||
void finishConacoTask(ConacoTask<V> task) {
|
|
||||||
mRegister.unregister(task.getId());
|
|
||||||
Unikery unikery = task.getUnikery();
|
|
||||||
if (unikery != null) {
|
|
||||||
unikery.setTaskId(Unikery.INVALID_ID);
|
|
||||||
}
|
|
||||||
task.clearUnikery();
|
|
||||||
|
|
||||||
onUnregisterConacoTask(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
@IntDef({SOURCE_MEMORY, SOURCE_DISK, SOURCE_NETWORK})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
public @interface Source {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder<T> extends BeerBelly.BeerBellyParams {
|
|
||||||
/**
|
|
||||||
* The client to get image from internet
|
|
||||||
*/
|
|
||||||
public OkHttpClient okHttpClient = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode, get size and others
|
|
||||||
*/
|
|
||||||
public ValueHelper<T> objectHelper = null;
|
|
||||||
|
|
||||||
public boolean debug = false;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void isValid() throws IllegalStateException {
|
|
||||||
super.isValid();
|
|
||||||
|
|
||||||
if (okHttpClient == null) {
|
|
||||||
throw new IllegalStateException("No http client? How can I load image via url?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Conaco<T> build() {
|
|
||||||
isValid();
|
|
||||||
return new Conaco<>(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,488 +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 android.annotation.SuppressLint;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
import com.hippo.beerbelly.SimpleDiskCache;
|
|
||||||
import com.hippo.okhttp.ChromeRequestBuilder;
|
|
||||||
import com.hippo.streampipe.InputStreamPipe;
|
|
||||||
import com.hippo.streampipe.OutputStreamPipe;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import okhttp3.Call;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.MediaType;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import okhttp3.ResponseBody;
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "deprecation"})
|
|
||||||
public final class ConacoTask<V> {
|
|
||||||
|
|
||||||
private static final String TAG = ConacoTask.class.getSimpleName();
|
|
||||||
|
|
||||||
private final int mId;
|
|
||||||
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
|
|
||||||
private final String mKey;
|
|
||||||
private final String mUrl;
|
|
||||||
private final boolean mUseMemoryCache;
|
|
||||||
private final boolean mUseDiskCache;
|
|
||||||
private final boolean mUseNetwork;
|
|
||||||
private final ValueHelper<V> mHelper;
|
|
||||||
private final ValueCache<V> mCache;
|
|
||||||
private final OkHttpClient mOkHttpClient;
|
|
||||||
private final Executor mDiskExecutor;
|
|
||||||
private final Executor mNetworkExecutor;
|
|
||||||
private final Conaco<V> mConaco;
|
|
||||||
|
|
||||||
private DiskLoadTask mDiskLoadTask;
|
|
||||||
private NetworkLoadTask mNetworkLoadTask;
|
|
||||||
private Call mCall;
|
|
||||||
private boolean mStart;
|
|
||||||
private volatile boolean mStop;
|
|
||||||
|
|
||||||
private ConacoTask(Builder<V> builder) {
|
|
||||||
mId = builder.mId;
|
|
||||||
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
|
|
||||||
mKey = builder.mKey;
|
|
||||||
mUrl = builder.mUrl;
|
|
||||||
mUseMemoryCache = builder.mUseMemoryCache;
|
|
||||||
mUseDiskCache = builder.mUseDiskCache;
|
|
||||||
mUseNetwork = builder.mUseNetwork;
|
|
||||||
mHelper = builder.mHelper;
|
|
||||||
mCache = builder.mCache;
|
|
||||||
mOkHttpClient = builder.mOkHttpClient;
|
|
||||||
mDiskExecutor = builder.mDiskExecutor;
|
|
||||||
mNetworkExecutor = builder.mNetworkExecutor;
|
|
||||||
mConaco = builder.mConaco;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getId() {
|
|
||||||
return mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getKey() {
|
|
||||||
return mKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean useMemoryCache() {
|
|
||||||
return mUseMemoryCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
Unikery<V> getUnikery() {
|
|
||||||
return mUnikeryWeakReference.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearUnikery() {
|
|
||||||
mUnikeryWeakReference.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFinish() {
|
|
||||||
if (!mStop) {
|
|
||||||
mConaco.finishConacoTask(this);
|
|
||||||
}/* else {
|
|
||||||
// It is done by Conaco
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void start() {
|
|
||||||
if (mStop || mStart) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mStart = true;
|
|
||||||
|
|
||||||
Unikery unikery = mUnikeryWeakReference.get();
|
|
||||||
if (unikery != null && unikery.getTaskId() == mId) {
|
|
||||||
if (mUseDiskCache) {
|
|
||||||
mDiskLoadTask = new DiskLoadTask();
|
|
||||||
mDiskLoadTask.executeOnExecutor(mDiskExecutor);
|
|
||||||
return;
|
|
||||||
} else if (mUseNetwork) {
|
|
||||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
|
||||||
unikery.onRequest();
|
|
||||||
mNetworkLoadTask = new NetworkLoadTask();
|
|
||||||
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
|
||||||
unikery.onMiss(Conaco.SOURCE_NETWORK);
|
|
||||||
unikery.onFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void stop() {
|
|
||||||
if (mStop) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mStop = true;
|
|
||||||
|
|
||||||
// Stop jobs
|
|
||||||
if (mDiskLoadTask != null) { // Getting from disk
|
|
||||||
mDiskLoadTask.cancel(false);
|
|
||||||
} else if (mNetworkLoadTask != null) { // Getting from network
|
|
||||||
mNetworkLoadTask.cancel(false);
|
|
||||||
if (mCall != null) {
|
|
||||||
mCall.cancel();
|
|
||||||
mCall = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Unikery unikery = mUnikeryWeakReference.get();
|
|
||||||
if (unikery != null) {
|
|
||||||
unikery.onCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conaco handle the clean up
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isNotNecessary(AsyncTask asyncTask) {
|
|
||||||
Unikery unikery = mUnikeryWeakReference.get();
|
|
||||||
return mStop || asyncTask.isCancelled() || unikery == null || unikery.getTaskId() != mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"})
|
|
||||||
public static class Builder<T> {
|
|
||||||
|
|
||||||
private int mId;
|
|
||||||
private Unikery<T> mUnikery;
|
|
||||||
private String mKey;
|
|
||||||
private String mUrl;
|
|
||||||
private boolean mUseMemoryCache = true;
|
|
||||||
private boolean mUseDiskCache = true;
|
|
||||||
private boolean mUseNetwork = true;
|
|
||||||
private ValueHelper<T> mHelper;
|
|
||||||
private ValueCache<T> mCache;
|
|
||||||
private OkHttpClient mOkHttpClient;
|
|
||||||
private Executor mDiskExecutor;
|
|
||||||
private Executor mNetworkExecutor;
|
|
||||||
private Conaco<T> mConaco;
|
|
||||||
|
|
||||||
public Builder<T> setId(int id) {
|
|
||||||
mId = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Unikery<T> getUnikery() {
|
|
||||||
return mUnikery;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> setUnikery(Unikery<T> unikery) {
|
|
||||||
mUnikery = unikery;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKey() {
|
|
||||||
return mKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> setKey(String key) {
|
|
||||||
mKey = key;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return mUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> setUrl(String url) {
|
|
||||||
mUrl = url;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isUseMemoryCache() {
|
|
||||||
return mUseMemoryCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> setUseMemoryCache(boolean useMemoryCache) {
|
|
||||||
mUseMemoryCache = useMemoryCache;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isUseDiskCache() {
|
|
||||||
return mUseDiskCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> setUseDiskCache(boolean useDiskCache) {
|
|
||||||
mUseDiskCache = useDiskCache;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isUseNetwork() {
|
|
||||||
return mUseNetwork;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> setUseNetwork(boolean useNetwork) {
|
|
||||||
mUseNetwork = useNetwork;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ValueHelper<T> getHelper() {
|
|
||||||
return mHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder<T> setHelper(ValueHelper<T> helper) {
|
|
||||||
mHelper = helper;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder<T> setCache(ValueCache<T> cache) {
|
|
||||||
mCache = cache;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder<T> setOkHttpClient(OkHttpClient okHttpClient) {
|
|
||||||
mOkHttpClient = okHttpClient;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder<T> setDiskExecutor(Executor diskExecutor) {
|
|
||||||
mDiskExecutor = diskExecutor;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder<T> setNetworkExecutor(Executor networkExecutor) {
|
|
||||||
mNetworkExecutor = networkExecutor;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder<T> setConaco(Conaco<T> conaco) {
|
|
||||||
mConaco = conaco;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isValid() {
|
|
||||||
if (mUnikery == null) {
|
|
||||||
throw new IllegalStateException("Must set unikery");
|
|
||||||
}
|
|
||||||
if (mKey == null && mUrl == null) {
|
|
||||||
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConacoTask<T> build() {
|
|
||||||
return new ConacoTask<>(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private class DiskLoadTask extends AsyncTask<Void, Void, V> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected V doInBackground(Void... params) {
|
|
||||||
if (isNotNecessary(this)) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
V value = null;
|
|
||||||
|
|
||||||
// Then check disk cache
|
|
||||||
if (mKey != null) {
|
|
||||||
if (value == null && mUseDiskCache) {
|
|
||||||
value = mCache.getFromDisk(mKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
|
||||||
mCache.putToMemory(mKey, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(V value) {
|
|
||||||
mDiskLoadTask = null;
|
|
||||||
|
|
||||||
if (isCancelled() || mStop) {
|
|
||||||
onCancelled(value);
|
|
||||||
} else {
|
|
||||||
Unikery<V> unikery = mUnikeryWeakReference.get();
|
|
||||||
if (unikery != null && unikery.getTaskId() == mId) {
|
|
||||||
boolean getValue = false;
|
|
||||||
if ((value == null || !(getValue = unikery.onGetValue(value, Conaco.SOURCE_DISK))) && mUseNetwork) {
|
|
||||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
|
||||||
unikery.onRequest();
|
|
||||||
mNetworkLoadTask = new NetworkLoadTask();
|
|
||||||
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
|
||||||
return;
|
|
||||||
} else if (!getValue) {
|
|
||||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
|
||||||
unikery.onFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCancelled(V holder) {
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private class NetworkLoadTask extends AsyncTask<Void, Long, V> implements ProgressNotifier {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
|
||||||
if (!isNotNecessary(this)) {
|
|
||||||
publishProgress(singleReceivedSize, receivedSize, totalSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean putToDiskCache(InputStream is, long length) {
|
|
||||||
SimpleDiskCache diskCache = mCache.getDiskCache();
|
|
||||||
if (diskCache == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputStreamPipe pipe = diskCache.getOutputStreamPipe(mKey);
|
|
||||||
try {
|
|
||||||
pipe.obtain();
|
|
||||||
OutputStream os = pipe.open();
|
|
||||||
|
|
||||||
final byte[] buffer = new byte[1024 * 4];
|
|
||||||
long receivedSize = 0;
|
|
||||||
int bytesRead;
|
|
||||||
|
|
||||||
while ((bytesRead = is.read(buffer)) != -1) {
|
|
||||||
os.write(buffer, 0, bytesRead);
|
|
||||||
receivedSize += bytesRead;
|
|
||||||
notifyProgress(bytesRead, receivedSize, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
pipe.close();
|
|
||||||
pipe.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected V doInBackground(Void... params) {
|
|
||||||
if (isNotNecessary(this)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
V value;
|
|
||||||
InputStream is = null;
|
|
||||||
try {
|
|
||||||
Log.d("TAG", "Conaco " + mUrl);
|
|
||||||
|
|
||||||
// Load it from internet
|
|
||||||
Request request = new ChromeRequestBuilder(mUrl).build();
|
|
||||||
mCall = mOkHttpClient.newCall(request);
|
|
||||||
|
|
||||||
Response response = mCall.execute();
|
|
||||||
ResponseBody body = response.body();
|
|
||||||
if (body == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
is = body.byteStream();
|
|
||||||
|
|
||||||
if (isNotNecessary(this)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mKey != null) {
|
|
||||||
if (putToDiskCache(is, body.contentLength())) {
|
|
||||||
// Get object from disk cache
|
|
||||||
value = mCache.getFromDisk(mKey);
|
|
||||||
if (value == null) {
|
|
||||||
// Maybe bad download, remove it from disk cache
|
|
||||||
mCache.removeFromDisk(mKey);
|
|
||||||
} else if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
|
||||||
// Put it to memory
|
|
||||||
mCache.putToMemory(mKey, value);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
// Maybe bad download, remove it from disk cache
|
|
||||||
mCache.removeFromDisk(mKey);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
mCall = null;
|
|
||||||
try {
|
|
||||||
if (is != null) {
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(V holder) {
|
|
||||||
mNetworkLoadTask = null;
|
|
||||||
|
|
||||||
if (isCancelled() || mStop) {
|
|
||||||
onCancelled(holder);
|
|
||||||
} else {
|
|
||||||
Unikery<V> unikery = mUnikeryWeakReference.get();
|
|
||||||
if (unikery != null && unikery.getTaskId() == mId) {
|
|
||||||
if (holder == null || !unikery.onGetValue(holder, Conaco.SOURCE_NETWORK)) {
|
|
||||||
unikery.onFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCancelled(V value) {
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onProgressUpdate(Long... values) {
|
|
||||||
Unikery unikery = mUnikeryWeakReference.get();
|
|
||||||
if (!mStop && !isCancelled() && unikery != null && unikery.getTaskId() == mId) {
|
|
||||||
unikery.onProgress(values[0], values[1], values[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +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.conaco;
|
|
||||||
|
|
||||||
public interface ProgressNotifier {
|
|
||||||
|
|
||||||
void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize);
|
|
||||||
}
|
|
||||||
@@ -1,72 +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.conaco;
|
|
||||||
|
|
||||||
import com.hippo.yorozuya.collect.Multimap;
|
|
||||||
import com.hippo.yorozuya.collect.SparseILArray;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
class Register<V> {
|
|
||||||
|
|
||||||
private final SparseILArray<ConacoTask<V>> mIdMap = new SparseILArray<>();
|
|
||||||
private final Multimap<String, ConacoTask<V>> mKeyMap = Multimap.create();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true for the key is already registered
|
|
||||||
*/
|
|
||||||
public synchronized boolean register(int id, ConacoTask<V> task) {
|
|
||||||
boolean repeatedKey = false;
|
|
||||||
String taskKey = task.getKey();
|
|
||||||
|
|
||||||
// For no-key task, there no need to check
|
|
||||||
if (taskKey != null) {
|
|
||||||
repeatedKey = mKeyMap.countElements(taskKey) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append task
|
|
||||||
mKeyMap.putElement(taskKey, task);
|
|
||||||
mIdMap.append(id, task);
|
|
||||||
|
|
||||||
return repeatedKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized ConacoTask<V> unregister(int id) {
|
|
||||||
ConacoTask<V> task = mIdMap.remove(id);
|
|
||||||
if (task != null) {
|
|
||||||
mKeyMap.removeElement(task.getKey(), task);
|
|
||||||
}
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean contain(int id) {
|
|
||||||
return mIdMap.indexOfKey(id) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized ConacoTask<V> getByKey(String key) {
|
|
||||||
if (key == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ConacoTask<V>> list = mKeyMap.get(key);
|
|
||||||
if (list != null && list.size() > 0) {
|
|
||||||
return list.get(0);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +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.conaco;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
public interface Unikery<V> {
|
|
||||||
|
|
||||||
int INVALID_ID = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the conaco task id
|
|
||||||
*
|
|
||||||
* @return the conaco task id
|
|
||||||
*/
|
|
||||||
int getTaskId();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the conaco task id
|
|
||||||
*
|
|
||||||
* @param id the conaco task id
|
|
||||||
*/
|
|
||||||
void setTaskId(int id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On miss in the source
|
|
||||||
*/
|
|
||||||
void onMiss(@Conaco.Source int source);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On start http request
|
|
||||||
*/
|
|
||||||
void onRequest();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On http request progress
|
|
||||||
*/
|
|
||||||
void onProgress(long singleReceivedSize, long receivedSize, long totalSize);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On start to wait because repeated key
|
|
||||||
*/
|
|
||||||
void onWait();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On get the value
|
|
||||||
*
|
|
||||||
* @return {@code true} for the {@code Unikery} accepts the value
|
|
||||||
* and the task ends. {@code false} for the {@code Unikery}
|
|
||||||
* reject the value and the task goes on or failed
|
|
||||||
*/
|
|
||||||
boolean onGetValue(@NonNull V value, @Conaco.Source int source);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On failed to get value
|
|
||||||
*/
|
|
||||||
void onFailure();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On user cancel the conaco task
|
|
||||||
*/
|
|
||||||
void onCancel();
|
|
||||||
}
|
|
||||||
@@ -1,61 +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.conaco;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.hippo.beerbelly.BeerBelly;
|
|
||||||
import com.hippo.streampipe.InputStreamPipe;
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
class ValueCache<V> extends BeerBelly<V> {
|
|
||||||
|
|
||||||
private final ValueHelper<V> mHelper;
|
|
||||||
|
|
||||||
public ValueCache(BeerBelly.BeerBellyParams params, ValueHelper<V> helper) {
|
|
||||||
super(params);
|
|
||||||
mHelper = helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int sizeOf(String key, V value) {
|
|
||||||
return mHelper.sizeOf(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void memoryEntryAdded(V value) {
|
|
||||||
mHelper.onAddToMemoryCache(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue) {
|
|
||||||
if (oldValue != null) {
|
|
||||||
mHelper.onRemoveFromMemoryCache(key, oldValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected V read(@NonNull InputStreamPipe isPipe) {
|
|
||||||
return mHelper.decode(isPipe);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean write(OutputStream os, V value) {
|
|
||||||
throw new UnsupportedOperationException("Not support write object");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +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.conaco;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.hippo.streampipe.InputStreamPipe;
|
|
||||||
|
|
||||||
public interface ValueHelper<V> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode object for the {@code InputStreamPipe}
|
|
||||||
*
|
|
||||||
* @param isPipe the {@code InputStreamPipe}
|
|
||||||
* @return the decoded object
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
V decode(@NonNull InputStreamPipe isPipe);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the size of the object
|
|
||||||
*/
|
|
||||||
int sizeOf(@NonNull String key, @NonNull V value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the object added to memory cache
|
|
||||||
*/
|
|
||||||
void onAddToMemoryCache(@NonNull V oldValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the object removed from memory cache
|
|
||||||
*/
|
|
||||||
void onRemoveFromMemoryCache(@NonNull String key, @NonNull V oldValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use cache memory or not. Sometimes large object
|
|
||||||
* should not be stored in memory cache.
|
|
||||||
*/
|
|
||||||
boolean useMemoryCache(@NonNull String key, V holder);
|
|
||||||
}
|
|
||||||
@@ -22,6 +22,7 @@ import android.graphics.ColorFilter;
|
|||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -29,17 +30,25 @@ import androidx.core.graphics.drawable.DrawableCompat;
|
|||||||
|
|
||||||
public class DrawableWrapper extends Drawable implements Drawable.Callback {
|
public class DrawableWrapper extends Drawable implements Drawable.Callback {
|
||||||
private Drawable mDrawable;
|
private Drawable mDrawable;
|
||||||
|
private Rect mBounds;
|
||||||
|
|
||||||
public DrawableWrapper(Drawable drawable) {
|
public DrawableWrapper(Drawable drawable) {
|
||||||
this.setWrappedDrawable(drawable);
|
this.setWrappedDrawable(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(@NonNull Canvas canvas) {
|
public void draw(@NonNull Canvas canvas) {
|
||||||
this.mDrawable.draw(canvas);
|
if (mDrawable instanceof BitmapDrawable bitmapDrawable) {
|
||||||
|
canvas.drawBitmap(bitmapDrawable.getBitmap(), null, mBounds, bitmapDrawable.getPaint());
|
||||||
|
} else {
|
||||||
|
synchronized (mDrawable) {
|
||||||
|
mDrawable.setBounds(mBounds);
|
||||||
|
mDrawable.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onBoundsChange(Rect bounds) {
|
protected void onBoundsChange(Rect bounds) {
|
||||||
this.mDrawable.setBounds(bounds);
|
mBounds = bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getChangingConfigurations() {
|
public int getChangingConfigurations() {
|
||||||
|
|||||||
@@ -1,215 +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.drawable;
|
|
||||||
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.ColorFilter;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Path;
|
|
||||||
import android.graphics.PixelFormat;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.Keep;
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.R;
|
|
||||||
import com.hippo.yorozuya.MathUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate between them.
|
|
||||||
*/
|
|
||||||
public class DrawerArrowDrawable extends Drawable {
|
|
||||||
|
|
||||||
// The angle in degrees that the arrow head is inclined at.
|
|
||||||
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
|
|
||||||
private final Paint mPaint = new Paint();
|
|
||||||
private final float mBarThickness;
|
|
||||||
// The length of top and bottom bars when they merge into an arrow
|
|
||||||
private final float mTopBottomArrowSize;
|
|
||||||
// The length of middle bar
|
|
||||||
private final float mBarSize;
|
|
||||||
// The length of the middle bar when arrow is shaped
|
|
||||||
private final float mMiddleArrowSize;
|
|
||||||
// The space between bars when they are parallel
|
|
||||||
private final float mBarGap;
|
|
||||||
// Whether bars should spin or not during progress
|
|
||||||
private final boolean mSpin;
|
|
||||||
// Use Path instead of canvas operations so that if color has transparency, overlapping sections
|
|
||||||
// wont look different
|
|
||||||
private final Path mPath = new Path();
|
|
||||||
// The reported intrinsic size of the drawable.
|
|
||||||
private final int mSize;
|
|
||||||
// the amount that overlaps w/ bar size when rotation is max
|
|
||||||
private final float mMaxCutForBarSize;
|
|
||||||
// Whether we should mirror animation when animation is reversed.
|
|
||||||
private boolean mVerticalMirror = false;
|
|
||||||
// The interpolated version of the original progress
|
|
||||||
private float mProgress;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context used to get the configuration for the drawable from
|
|
||||||
*/
|
|
||||||
public DrawerArrowDrawable(Context context, int color) {
|
|
||||||
Resources resources = context.getResources();
|
|
||||||
|
|
||||||
mPaint.setAntiAlias(true);
|
|
||||||
mPaint.setColor(color);
|
|
||||||
mSize = resources.getDimensionPixelSize(R.dimen.dad_drawable_size);
|
|
||||||
// round this because having this floating may cause bad measurements
|
|
||||||
mBarSize = Math.round(resources.getDimension(R.dimen.dad_bar_size));
|
|
||||||
// round this because having this floating may cause bad measurements
|
|
||||||
mTopBottomArrowSize = Math.round(resources.getDimension(R.dimen.dad_top_bottom_bar_arrow_size));
|
|
||||||
mBarThickness = resources.getDimension(R.dimen.dad_thickness);
|
|
||||||
// round this because having this floating may cause bad measurements
|
|
||||||
mBarGap = Math.round(resources.getDimension(R.dimen.dad_gap_between_bars));
|
|
||||||
mSpin = resources.getBoolean(R.bool.dad_spin_bars);
|
|
||||||
mMiddleArrowSize = resources.getDimension(R.dimen.dad_middle_bar_arrow_size);
|
|
||||||
|
|
||||||
mPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mPaint.setStrokeJoin(Paint.Join.MITER);
|
|
||||||
mPaint.setStrokeCap(Paint.Cap.BUTT);
|
|
||||||
mPaint.setStrokeWidth(mBarThickness);
|
|
||||||
|
|
||||||
mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If set, canvas is flipped when progress reached to end and going back to start.
|
|
||||||
*/
|
|
||||||
protected void setVerticalMirror(boolean verticalMirror) {
|
|
||||||
mVerticalMirror = verticalMirror;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(Canvas canvas) {
|
|
||||||
Rect bounds = getBounds();
|
|
||||||
// Interpolated widths of arrow bars
|
|
||||||
final float arrowSize = MathUtils.lerp(mBarSize, mTopBottomArrowSize, mProgress);
|
|
||||||
final float middleBarSize = MathUtils.lerp(mBarSize, mMiddleArrowSize, mProgress);
|
|
||||||
// Interpolated size of middle bar
|
|
||||||
final float middleBarCut = Math.round(MathUtils.lerp(0, mMaxCutForBarSize, mProgress));
|
|
||||||
// The rotation of the top and bottom bars (that make the arrow head)
|
|
||||||
final float rotation = MathUtils.lerp(0, ARROW_HEAD_ANGLE, mProgress);
|
|
||||||
|
|
||||||
// The whole canvas rotates as the transition happens
|
|
||||||
final float canvasRotate = MathUtils.lerp(-180, 0, mProgress);
|
|
||||||
final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
|
|
||||||
final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
|
|
||||||
|
|
||||||
mPath.rewind();
|
|
||||||
final float topBottomBarOffset = MathUtils.lerp(mBarGap + mBarThickness, -mMaxCutForBarSize,
|
|
||||||
mProgress);
|
|
||||||
|
|
||||||
final float arrowEdge = -middleBarSize / 2;
|
|
||||||
// draw middle bar
|
|
||||||
mPath.moveTo(arrowEdge + middleBarCut, 0);
|
|
||||||
mPath.rLineTo(middleBarSize - middleBarCut * 2, 0);
|
|
||||||
|
|
||||||
// bottom bar
|
|
||||||
mPath.moveTo(arrowEdge, topBottomBarOffset);
|
|
||||||
mPath.rLineTo(arrowWidth, arrowHeight);
|
|
||||||
|
|
||||||
// top bar
|
|
||||||
mPath.moveTo(arrowEdge, -topBottomBarOffset);
|
|
||||||
mPath.rLineTo(arrowWidth, -arrowHeight);
|
|
||||||
|
|
||||||
mPath.close();
|
|
||||||
|
|
||||||
canvas.save();
|
|
||||||
// Rotate the whole canvas if spinning, if not, rotate it 180 to get
|
|
||||||
// the arrow pointing the other way for RTL.
|
|
||||||
canvas.translate(bounds.centerX(), bounds.centerY());
|
|
||||||
if (mSpin) {
|
|
||||||
canvas.rotate(canvasRotate * (mVerticalMirror ? -1 : 1));
|
|
||||||
}
|
|
||||||
canvas.drawPath(mPath, mPaint);
|
|
||||||
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColor(@ColorInt int color) {
|
|
||||||
mPaint.setColor(color);
|
|
||||||
invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAlpha(int i) {
|
|
||||||
mPaint.setAlpha(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setColorFilter(ColorFilter colorFilter) {
|
|
||||||
mPaint.setColorFilter(colorFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIntrinsicHeight() {
|
|
||||||
return mSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIntrinsicWidth() {
|
|
||||||
return mSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOpacity() {
|
|
||||||
return PixelFormat.TRANSLUCENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
public float getProgress() {
|
|
||||||
return mProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
public void setProgress(float progress) {
|
|
||||||
if (progress == 1f) {
|
|
||||||
setVerticalMirror(true);
|
|
||||||
} else if (progress == 0f) {
|
|
||||||
setVerticalMirror(false);
|
|
||||||
}
|
|
||||||
mProgress = progress;
|
|
||||||
invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMenu(long duration) {
|
|
||||||
setShape(false, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setArrow(long duration) {
|
|
||||||
setShape(true, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShape(boolean arrow, long duration) {
|
|
||||||
if (!((!arrow && mProgress == 0f) || (arrow && mProgress == 1f))) {
|
|
||||||
float endProgress = arrow ? 1f : 0f;
|
|
||||||
if (duration <= 0) {
|
|
||||||
setProgress(endProgress);
|
|
||||||
} else {
|
|
||||||
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", endProgress);
|
|
||||||
oa.setDuration(duration);
|
|
||||||
oa.setAutoCancel(true);
|
|
||||||
oa.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,31 +16,25 @@
|
|||||||
|
|
||||||
package com.hippo.drawable;
|
package com.hippo.drawable;
|
||||||
|
|
||||||
|
import android.graphics.drawable.AnimatedImageDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.hippo.conaco.Conaco;
|
|
||||||
import com.hippo.conaco.ConacoTask;
|
|
||||||
import com.hippo.conaco.Unikery;
|
|
||||||
import com.hippo.image.ImageBitmap;
|
|
||||||
import com.hippo.image.ImageDrawable;
|
|
||||||
import com.hippo.widget.ObservedTextView;
|
import com.hippo.widget.ObservedTextView;
|
||||||
|
|
||||||
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
|
import coil.Coil;
|
||||||
ObservedTextView.OnWindowAttachListener {
|
import coil.request.Disposable;
|
||||||
|
import coil.request.ImageRequest;
|
||||||
|
|
||||||
|
public class UnikeryDrawable extends WrapDrawable implements ObservedTextView.OnWindowAttachListener {
|
||||||
|
|
||||||
private static final String TAG = UnikeryDrawable.class.getSimpleName();
|
private static final String TAG = UnikeryDrawable.class.getSimpleName();
|
||||||
private final ObservedTextView mTextView;
|
private final ObservedTextView mTextView;
|
||||||
private final Conaco<ImageBitmap> mConaco;
|
|
||||||
private int mTaskId = Unikery.INVALID_ID;
|
|
||||||
private String mUrl;
|
private String mUrl;
|
||||||
|
private Disposable task;
|
||||||
|
|
||||||
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
public UnikeryDrawable(ObservedTextView textView) {
|
||||||
mTextView = textView;
|
mTextView = textView;
|
||||||
mTextView.setOnWindowAttachListener(this);
|
mTextView.setOnWindowAttachListener(this);
|
||||||
mConaco = conaco;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -50,22 +44,28 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDetachedFromWindow() {
|
public void onDetachedFromWindow() {
|
||||||
mConaco.cancel(this);
|
if (task != null && !task.isDisposed())
|
||||||
|
task.dispose();
|
||||||
clearDrawable();
|
clearDrawable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load(String url) {
|
public void load(String url) {
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
mUrl = url;
|
mUrl = url;
|
||||||
mConaco.load(new ConacoTask.Builder<ImageBitmap>().setUnikery(this).setUrl(url).setKey(url));
|
var imageLoader = Coil.imageLoader(mTextView.getContext());
|
||||||
|
var request = new ImageRequest.Builder(mTextView.getContext()).data(url).memoryCacheKey(url).diskCacheKey(url).target(
|
||||||
|
drawable -> null,
|
||||||
|
drawable -> null,
|
||||||
|
drawable -> {
|
||||||
|
onGetValue(drawable);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
).build();
|
||||||
|
task = imageLoader.enqueue(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearDrawable() {
|
private void clearDrawable() {
|
||||||
Drawable drawable = getDrawable();
|
|
||||||
if (drawable instanceof ImageDrawable) {
|
|
||||||
((ImageDrawable) drawable).recycle();
|
|
||||||
}
|
|
||||||
setDrawable(null);
|
setDrawable(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,63 +89,16 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTaskId() {
|
|
||||||
return mTaskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTaskId(int id) {
|
|
||||||
mTaskId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateSelf() {
|
public void invalidateSelf() {
|
||||||
CharSequence cs = mTextView.getText();
|
CharSequence cs = mTextView.getText();
|
||||||
mTextView.setText(cs);
|
mTextView.setText(cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void onGetValue(Drawable drawable) {
|
||||||
public void onMiss(int source) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWait() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
|
|
||||||
ImageDrawable drawable;
|
|
||||||
try {
|
|
||||||
drawable = new ImageDrawable(value);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.d(TAG, "The ImageBitmap is recycled", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearDrawable();
|
clearDrawable();
|
||||||
|
|
||||||
setDrawable(drawable);
|
setDrawable(drawable);
|
||||||
drawable.start();
|
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable)
|
||||||
|
animatedImageDrawable.start();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure() {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancel() {
|
|
||||||
// Empty
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,8 +64,7 @@ public final class LayoutManagerUtils {
|
|||||||
RecyclerView.LayoutManager layoutManager, Context context, int position,
|
RecyclerView.LayoutManager layoutManager, Context context, int position,
|
||||||
int millisecondsPerInch) {
|
int millisecondsPerInch) {
|
||||||
SimpleSmoothScroller smoothScroller;
|
SimpleSmoothScroller smoothScroller;
|
||||||
if (layoutManager instanceof LinearLayoutManager) {
|
if (layoutManager instanceof final LinearLayoutManager linearLayoutManager) {
|
||||||
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
|
||||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||||
@Override
|
@Override
|
||||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||||
@@ -73,8 +72,7 @@ public final class LayoutManagerUtils {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
} else if (layoutManager instanceof final StaggeredGridLayoutManager staggeredGridLayoutManager) {
|
||||||
final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
|
|
||||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||||
@Override
|
@Override
|
||||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||||
|
|||||||
@@ -1,600 +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;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ComponentCallbacks2;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Debug;
|
|
||||||
import android.text.Html;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
|
||||||
import androidx.collection.LruCache;
|
|
||||||
import androidx.core.os.LocaleListCompat;
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
|
||||||
|
|
||||||
import com.hippo.Native;
|
|
||||||
import com.hippo.app.BaseDialogBuilder;
|
|
||||||
import com.hippo.beerbelly.SimpleDiskCache;
|
|
||||||
import com.hippo.conaco.Conaco;
|
|
||||||
import com.hippo.ehviewer.client.EhClient;
|
|
||||||
import com.hippo.ehviewer.client.EhCookieStore;
|
|
||||||
import com.hippo.ehviewer.client.EhDns;
|
|
||||||
import com.hippo.ehviewer.client.EhEngine;
|
|
||||||
import com.hippo.ehviewer.client.EhRequestBuilder;
|
|
||||||
import com.hippo.ehviewer.client.EhSSLSocketFactory;
|
|
||||||
import com.hippo.ehviewer.client.EhUrl;
|
|
||||||
import com.hippo.ehviewer.client.EhX509TrustManager;
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
|
||||||
import com.hippo.ehviewer.client.parser.EventPaneParser;
|
|
||||||
import com.hippo.ehviewer.download.DownloadManager;
|
|
||||||
import com.hippo.ehviewer.spider.SpiderDen;
|
|
||||||
import com.hippo.ehviewer.ui.CommonOperations;
|
|
||||||
import com.hippo.ehviewer.ui.EhActivity;
|
|
||||||
import com.hippo.image.Image;
|
|
||||||
import com.hippo.image.ImageBitmap;
|
|
||||||
import com.hippo.network.StatusCodeException;
|
|
||||||
import com.hippo.scene.SceneApplication;
|
|
||||||
import com.hippo.unifile.UniFile;
|
|
||||||
import com.hippo.util.BitmapUtils;
|
|
||||||
import com.hippo.util.ClipboardUtil;
|
|
||||||
import com.hippo.util.ExceptionUtils;
|
|
||||||
import com.hippo.util.IoThreadPoolExecutor;
|
|
||||||
import com.hippo.util.ReadableTime;
|
|
||||||
import com.hippo.yorozuya.FileUtils;
|
|
||||||
import com.hippo.yorozuya.IntIdGenerator;
|
|
||||||
import com.hippo.yorozuya.OSUtils;
|
|
||||||
import com.hippo.yorozuya.SimpleHandler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
|
|
||||||
import okhttp3.Cache;
|
|
||||||
import okhttp3.Call;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import okhttp3.ResponseBody;
|
|
||||||
|
|
||||||
public class EhApplication extends SceneApplication {
|
|
||||||
|
|
||||||
public static final boolean BETA = false;
|
|
||||||
private static final String TAG = EhApplication.class.getSimpleName();
|
|
||||||
private static final String KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id";
|
|
||||||
private static final boolean DEBUG_CONACO = false;
|
|
||||||
private static final boolean DEBUG_PRINT_NATIVE_MEMORY = false;
|
|
||||||
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
|
|
||||||
private static final long DEBUG_PRINT_INTERVAL = 3000L;
|
|
||||||
|
|
||||||
public static boolean locked = true;
|
|
||||||
public static long locked_last_leave_time = 0;
|
|
||||||
|
|
||||||
private static EhApplication instance;
|
|
||||||
|
|
||||||
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
|
|
||||||
private final HashMap<Integer, Object> mGlobalStuffMap = new HashMap<>();
|
|
||||||
private final List<Activity> mActivityList = new ArrayList<>();
|
|
||||||
private EhCookieStore mEhCookieStore;
|
|
||||||
private EhClient mEhClient;
|
|
||||||
private EhProxySelector mEhProxySelector;
|
|
||||||
private OkHttpClient mOkHttpClient;
|
|
||||||
private Cache mOkHttpCache;
|
|
||||||
private ImageBitmapHelper mImageBitmapHelper;
|
|
||||||
private Conaco<ImageBitmap> mConaco;
|
|
||||||
private LruCache<Long, GalleryDetail> mGalleryDetailCache;
|
|
||||||
private SimpleDiskCache mSpiderInfoCache;
|
|
||||||
private DownloadManager mDownloadManager;
|
|
||||||
private Hosts mHosts;
|
|
||||||
private FavouriteStatusRouter mFavouriteStatusRouter;
|
|
||||||
private boolean initialized = false;
|
|
||||||
|
|
||||||
public static EhApplication getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EhCookieStore getEhCookieStore(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mEhCookieStore == null) {
|
|
||||||
application.mEhCookieStore = new EhCookieStore(context);
|
|
||||||
}
|
|
||||||
return application.mEhCookieStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static EhClient getEhClient(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mEhClient == null) {
|
|
||||||
application.mEhClient = new EhClient(application);
|
|
||||||
}
|
|
||||||
return application.mEhClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static EhProxySelector getEhProxySelector(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mEhProxySelector == null) {
|
|
||||||
application.mEhProxySelector = new EhProxySelector();
|
|
||||||
}
|
|
||||||
return application.mEhProxySelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static OkHttpClient getOkHttpClient(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mOkHttpClient == null) {
|
|
||||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
|
||||||
.connectTimeout(5, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(5, TimeUnit.SECONDS)
|
|
||||||
.writeTimeout(5, TimeUnit.SECONDS)
|
|
||||||
.callTimeout(10, TimeUnit.SECONDS)
|
|
||||||
.cookieJar(getEhCookieStore(application))
|
|
||||||
.cache(getOkHttpCache(application))
|
|
||||||
.dns(new EhDns(application))
|
|
||||||
.addNetworkInterceptor(chain -> {
|
|
||||||
try {
|
|
||||||
return chain.proceed(chain.request());
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
// crash on meizu devices due to old Android version
|
|
||||||
// https://github.com/square/okhttp/issues/3301#issuecomment-348415095
|
|
||||||
throw new IOException(e.getMessage());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.proxySelector(getEhProxySelector(application));
|
|
||||||
try {
|
|
||||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
|
||||||
TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
trustManagerFactory.init((KeyStore) null);
|
|
||||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
|
||||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
|
||||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
|
||||||
}
|
|
||||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
|
||||||
builder.sslSocketFactory(new EhSSLSocketFactory(), trustManager);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
builder.sslSocketFactory(new EhSSLSocketFactory(), new EhX509TrustManager());
|
|
||||||
}
|
|
||||||
application.mOkHttpClient = builder.build();
|
|
||||||
}
|
|
||||||
return application.mOkHttpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Cache getOkHttpCache(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mOkHttpCache == null) {
|
|
||||||
application.mOkHttpCache = new Cache(new File(application.getCacheDir(), "http_cache"), 50L * 1024L * 1024L);
|
|
||||||
}
|
|
||||||
return application.mOkHttpCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static ImageBitmapHelper getImageBitmapHelper(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mImageBitmapHelper == null) {
|
|
||||||
application.mImageBitmapHelper = new ImageBitmapHelper();
|
|
||||||
}
|
|
||||||
return application.mImageBitmapHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getMemoryCacheMaxSize() {
|
|
||||||
return Math.min(20 * 1024 * 1024, (int) OSUtils.getAppMaxMemory());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Conaco<ImageBitmap> getConaco(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mConaco == null) {
|
|
||||||
Conaco.Builder<ImageBitmap> builder = new Conaco.Builder<>();
|
|
||||||
builder.hasMemoryCache = true;
|
|
||||||
builder.memoryCacheMaxSize = getMemoryCacheMaxSize();
|
|
||||||
builder.hasDiskCache = true;
|
|
||||||
builder.diskCacheDir = new File(context.getCacheDir(), "thumb");
|
|
||||||
builder.diskCacheMaxSize = 320 * 1024 * 1024; // 320MB
|
|
||||||
builder.okHttpClient = getOkHttpClient(context);
|
|
||||||
builder.objectHelper = getImageBitmapHelper(context);
|
|
||||||
builder.debug = DEBUG_CONACO;
|
|
||||||
application.mConaco = builder.build();
|
|
||||||
}
|
|
||||||
return application.mConaco;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static LruCache<Long, GalleryDetail> getGalleryDetailCache(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mGalleryDetailCache == null) {
|
|
||||||
// Max size 25, 3 min timeout
|
|
||||||
application.mGalleryDetailCache = new LruCache<>(25);
|
|
||||||
getFavouriteStatusRouter().addListener((gid, slot) -> {
|
|
||||||
GalleryDetail gd = application.mGalleryDetailCache.get(gid);
|
|
||||||
if (gd != null) {
|
|
||||||
gd.favoriteSlot = slot;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return application.mGalleryDetailCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static SimpleDiskCache getSpiderInfoCache(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (null == application.mSpiderInfoCache) {
|
|
||||||
application.mSpiderInfoCache = new SimpleDiskCache(
|
|
||||||
new File(context.getCacheDir(), "spider_info"), 20 * 1024 * 1024); // 20M
|
|
||||||
}
|
|
||||||
return application.mSpiderInfoCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static DownloadManager getDownloadManager() {
|
|
||||||
return getDownloadManager(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static DownloadManager getDownloadManager(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mDownloadManager == null) {
|
|
||||||
application.mDownloadManager = new DownloadManager(application);
|
|
||||||
}
|
|
||||||
return application.mDownloadManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Hosts getHosts(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mHosts == null) {
|
|
||||||
application.mHosts = new Hosts(application, "hosts.db");
|
|
||||||
}
|
|
||||||
return application.mHosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static FavouriteStatusRouter getFavouriteStatusRouter() {
|
|
||||||
return getFavouriteStatusRouter(getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static FavouriteStatusRouter getFavouriteStatusRouter(@NonNull Context context) {
|
|
||||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
|
||||||
if (application.mFavouriteStatusRouter == null) {
|
|
||||||
application.mFavouriteStatusRouter = new FavouriteStatusRouter();
|
|
||||||
}
|
|
||||||
return application.mFavouriteStatusRouter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(new EhlifecycleObserver());
|
|
||||||
instance = this;
|
|
||||||
|
|
||||||
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
|
||||||
try {
|
|
||||||
// Always save crash file if onCreate() is not done
|
|
||||||
if (!initialized || Settings.getSaveCrashLog()) {
|
|
||||||
Crash.saveCrashLog(instance, e);
|
|
||||||
}
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler != null) {
|
|
||||||
handler.uncaughtException(t, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
super.onCreate();
|
|
||||||
|
|
||||||
Native.initialize();
|
|
||||||
ClipboardUtil.initialize(this);
|
|
||||||
GetText.initialize(this);
|
|
||||||
StatusCodeException.initialize(this);
|
|
||||||
Settings.initialize(this);
|
|
||||||
ReadableTime.initialize(this);
|
|
||||||
AppConfig.initialize(this);
|
|
||||||
SpiderDen.initialize(this);
|
|
||||||
EhDB.initialize(this);
|
|
||||||
EhEngine.initialize();
|
|
||||||
BitmapUtils.initialize(this);
|
|
||||||
|
|
||||||
// Locales can be managed by system automatically above Snow Cone v2
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
|
||||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
|
|
||||||
|
|
||||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
|
|
||||||
|
|
||||||
// Do io tasks in new thread
|
|
||||||
//noinspection deprecation
|
|
||||||
new AsyncTask<Void, Void, Void>() {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
// Check no media file
|
|
||||||
try {
|
|
||||||
UniFile downloadLocation = Settings.getDownloadLocation();
|
|
||||||
if (Settings.getMediaScan()) {
|
|
||||||
CommonOperations.removeNoMediaFile(downloadLocation);
|
|
||||||
} else {
|
|
||||||
CommonOperations.ensureNoMediaFile(downloadLocation);
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
t.printStackTrace();
|
|
||||||
ExceptionUtils.throwIfFatal(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear temp files
|
|
||||||
try {
|
|
||||||
clearTempDir();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
t.printStackTrace();
|
|
||||||
ExceptionUtils.throwIfFatal(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance());
|
|
||||||
|
|
||||||
// Check app update
|
|
||||||
update();
|
|
||||||
|
|
||||||
// Update version code
|
|
||||||
try {
|
|
||||||
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
|
|
||||||
Settings.putVersionCode(pi.versionCode);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0));
|
|
||||||
|
|
||||||
if (DEBUG_PRINT_NATIVE_MEMORY || DEBUG_PRINT_IMAGE_COUNT) {
|
|
||||||
debugPrint();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized = true;
|
|
||||||
theDawnOfNewDay();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void theDawnOfNewDay() {
|
|
||||||
if (!Settings.getRequestNews()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
EhCookieStore store = getEhCookieStore(this);
|
|
||||||
HttpUrl eh = HttpUrl.get(EhUrl.HOST_E);
|
|
||||||
|
|
||||||
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(eh, EhCookieStore.KEY_IPD_PASS_HASH)) {
|
|
||||||
IoThreadPoolExecutor.getInstance().execute(() -> {
|
|
||||||
String referer = EhUrl.REFERER_E;
|
|
||||||
Request request = new EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build();
|
|
||||||
Call call = getOkHttpClient(this).newCall(request);
|
|
||||||
try {
|
|
||||||
Response response = call.execute();
|
|
||||||
ResponseBody responseBody = response.body();
|
|
||||||
if (responseBody == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String body = responseBody.string();
|
|
||||||
String html = EventPaneParser.parse(body);
|
|
||||||
if (html != null) {
|
|
||||||
showEventPane(html);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showEventPane(String html) {
|
|
||||||
Activity activity = getTopActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
AlertDialog dialog = new BaseDialogBuilder(activity)
|
|
||||||
.setMessage(Html.fromHtml(html))
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.create();
|
|
||||||
dialog.setOnShowListener(d -> {
|
|
||||||
final View messageView = dialog.findViewById(android.R.id.message);
|
|
||||||
if (messageView instanceof TextView) {
|
|
||||||
((TextView) messageView).setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
dialog.show();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearTempDir() {
|
|
||||||
File dir = AppConfig.getTempDir();
|
|
||||||
if (null != dir) {
|
|
||||||
FileUtils.deleteContent(dir);
|
|
||||||
}
|
|
||||||
dir = AppConfig.getExternalTempDir();
|
|
||||||
if (null != dir) {
|
|
||||||
FileUtils.deleteContent(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update() {
|
|
||||||
int version = Settings.getVersionCode();
|
|
||||||
if (version < 52) {
|
|
||||||
Settings.putGuideGallery(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearMemoryCache() {
|
|
||||||
if (null != mConaco) {
|
|
||||||
mConaco.getBeerBelly().clearMemory();
|
|
||||||
}
|
|
||||||
if (null != mGalleryDetailCache) {
|
|
||||||
mGalleryDetailCache.evictAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrimMemory(int level) {
|
|
||||||
super.onTrimMemory(level);
|
|
||||||
|
|
||||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
|
||||||
clearMemoryCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void debugPrint() {
|
|
||||||
new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (DEBUG_PRINT_NATIVE_MEMORY) {
|
|
||||||
Log.i(TAG, "Native memory: " + FileUtils.humanReadableByteCount(
|
|
||||||
Debug.getNativeHeapAllocatedSize(), false));
|
|
||||||
}
|
|
||||||
if (DEBUG_PRINT_IMAGE_COUNT) {
|
|
||||||
Log.i(TAG, "Image count: " + Image.getImageCount());
|
|
||||||
}
|
|
||||||
SimpleHandler.getInstance().postDelayed(this, DEBUG_PRINT_INTERVAL);
|
|
||||||
}
|
|
||||||
}.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int putGlobalStuff(@NonNull Object o) {
|
|
||||||
int id = mIdGenerator.nextId();
|
|
||||||
mGlobalStuffMap.put(id, o);
|
|
||||||
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId());
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containGlobalStuff(int id) {
|
|
||||||
return mGlobalStuffMap.containsKey(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getGlobalStuff(int id) {
|
|
||||||
return mGlobalStuffMap.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object removeGlobalStuff(int id) {
|
|
||||||
return mGlobalStuffMap.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeGlobalStuff(Object o) {
|
|
||||||
mGlobalStuffMap.values().removeAll(Collections.singleton(o));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerActivity(Activity activity) {
|
|
||||||
mActivityList.add(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unregisterActivity(Activity activity) {
|
|
||||||
mActivityList.remove(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public EhActivity getTopActivity() {
|
|
||||||
if (!mActivityList.isEmpty()) {
|
|
||||||
return (EhActivity) mActivityList.get(mActivityList.size() - 1);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid crash on some "energy saving" devices
|
|
||||||
@Override
|
|
||||||
public ComponentName startService(Intent service) {
|
|
||||||
try {
|
|
||||||
return super.startService(service);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
t.printStackTrace();
|
|
||||||
ExceptionUtils.throwIfFatal(t);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid crash on some "energy saving" devices
|
|
||||||
@Override
|
|
||||||
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
|
|
||||||
try {
|
|
||||||
return super.bindService(service, conn, flags);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
t.printStackTrace();
|
|
||||||
ExceptionUtils.throwIfFatal(t);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid crash on some "energy saving" devices
|
|
||||||
@Override
|
|
||||||
public void unbindService(ServiceConnection conn) {
|
|
||||||
try {
|
|
||||||
super.unbindService(conn);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
t.printStackTrace();
|
|
||||||
ExceptionUtils.throwIfFatal(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid crash on some "energy saving" devices
|
|
||||||
@Override
|
|
||||||
public ComponentName startForegroundService(Intent service) {
|
|
||||||
try {
|
|
||||||
return super.startForegroundService(service);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
t.printStackTrace();
|
|
||||||
ExceptionUtils.throwIfFatal(t);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class EhlifecycleObserver implements DefaultLifecycleObserver {
|
|
||||||
@Override
|
|
||||||
public void onPause(@NonNull LifecycleOwner owner) {
|
|
||||||
if (!locked)
|
|
||||||
locked_last_leave_time = System.currentTimeMillis() / 1000;
|
|
||||||
locked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.content.ComponentCallbacks2
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.collection.LruCache
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.core.os.LocaleListCompat
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.ImageLoaderFactory
|
||||||
|
import com.hippo.Native
|
||||||
|
import com.hippo.app.BaseDialogBuilder
|
||||||
|
import com.hippo.beerbelly.SimpleDiskCache
|
||||||
|
import com.hippo.ehviewer.client.EhClient
|
||||||
|
import com.hippo.ehviewer.client.EhCookieStore
|
||||||
|
import com.hippo.ehviewer.client.EhDns
|
||||||
|
import com.hippo.ehviewer.client.EhEngine
|
||||||
|
import com.hippo.ehviewer.client.EhRequestBuilder
|
||||||
|
import com.hippo.ehviewer.client.EhSSLSocketFactory
|
||||||
|
import com.hippo.ehviewer.client.EhUrl
|
||||||
|
import com.hippo.ehviewer.client.EhX509TrustManager
|
||||||
|
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||||
|
import com.hippo.ehviewer.client.parser.EventPaneParser
|
||||||
|
import com.hippo.ehviewer.download.DownloadManager
|
||||||
|
import com.hippo.ehviewer.spider.SpiderDen
|
||||||
|
import com.hippo.ehviewer.ui.CommonOperations
|
||||||
|
import com.hippo.ehviewer.ui.EhActivity
|
||||||
|
import com.hippo.scene.SceneApplication
|
||||||
|
import com.hippo.util.BitmapUtils
|
||||||
|
import com.hippo.util.ExceptionUtils
|
||||||
|
import com.hippo.util.ReadableTime
|
||||||
|
import com.hippo.yorozuya.FileUtils
|
||||||
|
import com.hippo.yorozuya.IntIdGenerator
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.Cache
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.io.File
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.util.Arrays
|
||||||
|
import javax.net.ssl.TrustManagerFactory
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
|
class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderFactory {
|
||||||
|
private val mIdGenerator = IntIdGenerator()
|
||||||
|
private val mGlobalStuffMap = HashMap<Int, Any>()
|
||||||
|
private val mActivityList = ArrayList<Activity>()
|
||||||
|
private var initialized = false
|
||||||
|
val topActivity: EhActivity?
|
||||||
|
get() = if (mActivityList.isNotEmpty()) {
|
||||||
|
mActivityList[mActivityList.size - 1] as EhActivity
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recreateAllActivity() {
|
||||||
|
mActivityList.forEach { it.recreate() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
override fun onCreate() {
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||||
|
application = this
|
||||||
|
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
||||||
|
try {
|
||||||
|
if (Settings.getSaveCrashLog()) {
|
||||||
|
Crash.saveCrashLog(application, e)
|
||||||
|
}
|
||||||
|
} catch (ignored: Throwable) {
|
||||||
|
}
|
||||||
|
handler?.uncaughtException(t, e)
|
||||||
|
}
|
||||||
|
super<SceneApplication>.onCreate()
|
||||||
|
Native.initialize()
|
||||||
|
GetText.initialize(this)
|
||||||
|
Settings.initialize(this)
|
||||||
|
ReadableTime.initialize(this)
|
||||||
|
AppConfig.initialize(this)
|
||||||
|
SpiderDen.initialize(this)
|
||||||
|
EhDB.initialize(this)
|
||||||
|
EhEngine.initialize()
|
||||||
|
BitmapUtils.initialize(this)
|
||||||
|
|
||||||
|
// Locales can be managed by system automatically above Snow Cone v2
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||||
|
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()))
|
||||||
|
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
|
||||||
|
|
||||||
|
GlobalScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val downloadLocation = Settings.getDownloadLocation()
|
||||||
|
if (Settings.getMediaScan()) {
|
||||||
|
CommonOperations.removeNoMediaFile(downloadLocation)
|
||||||
|
} else {
|
||||||
|
CommonOperations.ensureNoMediaFile(downloadLocation)
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
t.printStackTrace()
|
||||||
|
ExceptionUtils.throwIfFatal(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear temp files
|
||||||
|
try {
|
||||||
|
clearTempDir()
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
t.printStackTrace()
|
||||||
|
ExceptionUtils.throwIfFatal(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GlobalScope.launch {
|
||||||
|
theDawnOfNewDay()
|
||||||
|
}
|
||||||
|
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0))
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun theDawnOfNewDay() {
|
||||||
|
if (!Settings.getRequestNews()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val store = ehCookieStore
|
||||||
|
val eh = EhUrl.HOST_E.toHttpUrl()
|
||||||
|
|
||||||
|
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(
|
||||||
|
eh,
|
||||||
|
EhCookieStore.KEY_IPD_PASS_HASH
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val referer = EhUrl.REFERER_E
|
||||||
|
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
|
||||||
|
val call = okHttpClient.newCall(request)
|
||||||
|
try {
|
||||||
|
call.execute().use { response ->
|
||||||
|
val responseBody = response.body
|
||||||
|
val body = responseBody.string()
|
||||||
|
val html = EventPaneParser.parse(body)
|
||||||
|
if (html != null) {
|
||||||
|
showEventPane(html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showEventPane(html: String) {
|
||||||
|
if (Settings.getHideHvEvents() && html.contains("You have encountered a monster!")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val activity = topActivity
|
||||||
|
activity?.runOnUiThread {
|
||||||
|
val dialog = BaseDialogBuilder(activity)
|
||||||
|
.setMessage(Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY))
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.create()
|
||||||
|
dialog.setOnShowListener {
|
||||||
|
val messageView = dialog.findViewById<View>(android.R.id.message)
|
||||||
|
if (messageView is TextView) {
|
||||||
|
messageView.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
dialog.show()
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearTempDir() {
|
||||||
|
var dir = AppConfig.getTempDir()
|
||||||
|
if (null != dir) {
|
||||||
|
FileUtils.deleteContent(dir)
|
||||||
|
}
|
||||||
|
dir = AppConfig.getExternalTempDir()
|
||||||
|
if (null != dir) {
|
||||||
|
FileUtils.deleteContent(dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTrimMemory(level: Int) {
|
||||||
|
super.onTrimMemory(level)
|
||||||
|
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
||||||
|
galleryDetailCache.evictAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun putGlobalStuff(o: Any): Int {
|
||||||
|
val id = mIdGenerator.nextId()
|
||||||
|
mGlobalStuffMap[id] = o
|
||||||
|
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId())
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containGlobalStuff(id: Int): Boolean {
|
||||||
|
return mGlobalStuffMap.containsKey(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGlobalStuff(id: Int): Any? {
|
||||||
|
return mGlobalStuffMap[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeGlobalStuff(id: Int): Any? {
|
||||||
|
return mGlobalStuffMap.remove(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeGlobalStuff(o: Any) {
|
||||||
|
mGlobalStuffMap.values.removeAll(setOf(o))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerActivity(activity: Activity) {
|
||||||
|
mActivityList.add(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregisterActivity(activity: Activity) {
|
||||||
|
mActivityList.remove(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause(owner: LifecycleOwner) {
|
||||||
|
if (!locked)
|
||||||
|
locked_last_leave_time = System.currentTimeMillis() / 1000
|
||||||
|
locked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
override fun newImageLoader(): ImageLoader {
|
||||||
|
return ImageLoader.Builder(this).apply {
|
||||||
|
okHttpClient(okHttpClient)
|
||||||
|
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
|
||||||
|
// Coil spawns a new thread for every image load by default
|
||||||
|
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8))
|
||||||
|
decoderDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||||
|
transformationDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id"
|
||||||
|
var locked = true
|
||||||
|
var locked_last_leave_time: Long = 0
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
lateinit var application: EhApplication
|
||||||
|
private set
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val ehCookieStore by lazy { EhCookieStore(application) }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val ehClient by lazy { EhClient() }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val ehProxySelector by lazy { EhProxySelector() }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val okHttpClient by lazy {
|
||||||
|
val builder = OkHttpClient.Builder()
|
||||||
|
.cookieJar(ehCookieStore)
|
||||||
|
.cache(Cache(File(application.cacheDir, "http_cache"), 50L * 1024L * 1024L))
|
||||||
|
.dns(EhDns())
|
||||||
|
.proxySelector(ehProxySelector)
|
||||||
|
|
||||||
|
if (Settings.getDF()) {
|
||||||
|
var trustManager: X509TrustManager
|
||||||
|
try {
|
||||||
|
val trustManagerFactory = TrustManagerFactory.getInstance(
|
||||||
|
TrustManagerFactory.getDefaultAlgorithm()
|
||||||
|
)
|
||||||
|
trustManagerFactory.init(null as KeyStore?)
|
||||||
|
val trustManagers = trustManagerFactory.trustManagers
|
||||||
|
check(!(trustManagers.size != 1 || trustManagers[0] !is X509TrustManager)) {
|
||||||
|
"Unexpected default trust managers:" + Arrays.toString(
|
||||||
|
trustManagers
|
||||||
|
)
|
||||||
|
}
|
||||||
|
trustManager = trustManagers[0] as X509TrustManager
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
trustManager = EhX509TrustManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.sslSocketFactory(EhSSLSocketFactory(), trustManager)
|
||||||
|
builder.proxy(Proxy.NO_PROXY)
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val galleryDetailCache by lazy {
|
||||||
|
LruCache<Long, GalleryDetail>(25).also {
|
||||||
|
favouriteStatusRouter.addListener { gid, slot ->
|
||||||
|
it[gid]?.favoriteSlot = slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val spiderInfoCache by lazy {
|
||||||
|
SimpleDiskCache(
|
||||||
|
File(application.cacheDir, "spider_info"),
|
||||||
|
20 * 1024 * 1024
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val downloadManager by lazy { DownloadManager(application) }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val hosts by lazy { Hosts(application, "hosts.db") }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val favouriteStatusRouter by lazy { FavouriteStatusRouter() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -234,7 +234,7 @@ public class EhDB {
|
|||||||
|
|
||||||
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
|
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
|
||||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||||
List<LocalFavoriteInfo> list = dao.list();
|
List<LocalFavoriteInfo> list = dao.list("%" + query + "%");
|
||||||
return new ArrayList<>(list);
|
return new ArrayList<>(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,11 +293,6 @@ public class EhDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
|
|
||||||
QuickSearchDao dao = db.quickSearchDao();
|
|
||||||
dao.update(quickSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
|
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
|
||||||
QuickSearchDao dao = db.quickSearchDao();
|
QuickSearchDao dao = db.quickSearchDao();
|
||||||
dao.delete(quickSearch);
|
dao.delete(quickSearch);
|
||||||
@@ -327,25 +322,22 @@ public class EhDB {
|
|||||||
dao.update(list);
|
dao.update(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized List<HistoryInfo> getHistoryList() {
|
|
||||||
return db.historyDao().list();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
|
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
|
||||||
return db.historyDao().listLazy();
|
return db.historyDao().listLazy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
|
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
|
||||||
HistoryDao dao = db.historyDao();
|
HistoryDao dao = db.historyDao();
|
||||||
HistoryInfo info = dao.load(galleryInfo.gid);
|
HistoryInfo info;
|
||||||
if (null != info) {
|
if (galleryInfo instanceof HistoryInfo) {
|
||||||
// Update time
|
info = (HistoryInfo) galleryInfo;
|
||||||
info.time = System.currentTimeMillis();
|
} else {
|
||||||
|
info = new HistoryInfo(galleryInfo);
|
||||||
|
}
|
||||||
|
info.time = System.currentTimeMillis();
|
||||||
|
if (null != dao.load(info.gid)) {
|
||||||
dao.update(info);
|
dao.update(info);
|
||||||
} else {
|
} else {
|
||||||
// New history
|
|
||||||
info = new HistoryInfo(galleryInfo);
|
|
||||||
info.time = System.currentTimeMillis();
|
|
||||||
dao.insert(info);
|
dao.insert(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,11 +390,10 @@ public class EhDB {
|
|||||||
db.filterDao().update(filter);
|
db.filterDao().update(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> boolean copyDao(BasicDao<T> from, BasicDao<T> to) {
|
private static <T> void copyDao(BasicDao<T> from, BasicDao<T> to) {
|
||||||
List<T> list = from.list();
|
List<T> list = from.list();
|
||||||
for (T item : list)
|
for (T item : list)
|
||||||
to.insert(item);
|
to.insert(item);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized boolean exportDB(Context context, Uri uri) {
|
public static synchronized boolean exportDB(Context context, Uri uri) {
|
||||||
@@ -414,20 +405,18 @@ public class EhDB {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Copy data to a export db
|
// Copy data to a export db
|
||||||
if (copyDao(db.downloadsDao(), newDb.downloadsDao()))
|
copyDao(db.downloadsDao(), newDb.downloadsDao());
|
||||||
return false;
|
copyDao(db.downloadLabelDao(), newDb.downloadLabelDao());
|
||||||
if (copyDao(db.downloadLabelDao(), newDb.downloadLabelDao()))
|
copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao());
|
||||||
return false;
|
copyDao(db.historyDao(), newDb.historyDao());
|
||||||
if (copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao()))
|
copyDao(db.quickSearchDao(), newDb.quickSearchDao());
|
||||||
return false;
|
copyDao(db.localFavoritesDao(), newDb.localFavoritesDao());
|
||||||
if (copyDao(db.historyDao(), newDb.historyDao()))
|
copyDao(db.filterDao(), newDb.filterDao());
|
||||||
return false;
|
|
||||||
if (copyDao(db.quickSearchDao(), newDb.quickSearchDao()))
|
// Close export db so we can copy it
|
||||||
return false;
|
if (newDb.isOpen()) {
|
||||||
if (copyDao(db.localFavoritesDao(), newDb.localFavoritesDao()))
|
newDb.close();
|
||||||
return false;
|
}
|
||||||
if (copyDao(db.filterDao(), newDb.filterDao()))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Copy export db to data dir
|
// Copy export db to data dir
|
||||||
File dbFile = context.getDatabasePath(ehExportName);
|
File dbFile = context.getDatabasePath(ehExportName);
|
||||||
@@ -447,6 +436,7 @@ public class EhDB {
|
|||||||
IOUtils.closeQuietly(is);
|
IOUtils.closeQuietly(is);
|
||||||
IOUtils.closeQuietly(os);
|
IOUtils.closeQuietly(os);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete failed file
|
// Delete failed file
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -460,6 +450,8 @@ public class EhDB {
|
|||||||
public static synchronized String importDB(Context context, Uri uri) {
|
public static synchronized String importDB(Context context, Uri uri) {
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
|
|
||||||
|
// Copy file to cache dir
|
||||||
File file = File.createTempFile("importDatabase", "");
|
File file = File.createTempFile("importDatabase", "");
|
||||||
FileOutputStream outputStream = new FileOutputStream(file);
|
FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
byte[] buff = new byte[1024];
|
byte[] buff = new byte[1024];
|
||||||
@@ -474,6 +466,7 @@ public class EhDB {
|
|||||||
inputStream.close();
|
inputStream.close();
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
|
|
||||||
|
// Check database version
|
||||||
SQLiteDatabase oldDB = SQLiteDatabase.openDatabase(
|
SQLiteDatabase oldDB = SQLiteDatabase.openDatabase(
|
||||||
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
|
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
|
||||||
int newVersion = CUR_DB_VER;
|
int newVersion = CUR_DB_VER;
|
||||||
@@ -482,26 +475,29 @@ public class EhDB {
|
|||||||
upgradeDB(oldDB, oldVersion);
|
upgradeDB(oldDB, oldVersion);
|
||||||
oldDB.setVersion(newVersion);
|
oldDB.setVersion(newVersion);
|
||||||
} else if (oldVersion > newVersion) {
|
} else if (oldVersion > newVersion) {
|
||||||
return context.getString(R.string.cant_read_the_file);
|
return context.getString(R.string.db_version_too_high);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Crete temp room database from cache file
|
||||||
String tmpDBName = "tmp.db";
|
String tmpDBName = "tmp.db";
|
||||||
context.deleteDatabase(tmpDBName);
|
context.deleteDatabase(tmpDBName);
|
||||||
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
|
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
|
||||||
.createFromFile(file).allowMainThreadQueries().build();
|
.createFromFile(file).allowMainThreadQueries().build();
|
||||||
|
|
||||||
// Download label
|
// Download label
|
||||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
DownloadManager manager = EhApplication.getDownloadManager();
|
||||||
try {
|
try {
|
||||||
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
||||||
manager.addDownloadLabel(downloadLabelList);
|
manager.addDownloadLabel(downloadLabelList);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
// Downloads
|
// Downloads
|
||||||
try {
|
try {
|
||||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||||
manager.addDownload(downloadInfoList, false);
|
manager.addDownload(downloadInfoList, false);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
// Download dirname
|
// Download dirname
|
||||||
try {
|
try {
|
||||||
@@ -509,13 +505,15 @@ public class EhDB {
|
|||||||
for (DownloadDirname dirname : downloadDirnameList) {
|
for (DownloadDirname dirname : downloadDirnameList) {
|
||||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
// History
|
// History
|
||||||
try {
|
try {
|
||||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||||
putHistoryInfo(historyInfoList);
|
putHistoryInfo(historyInfoList);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
// QuickSearch
|
// QuickSearch
|
||||||
try {
|
try {
|
||||||
@@ -537,7 +535,8 @@ public class EhDB {
|
|||||||
importList.add(quickSearch);
|
importList.add(quickSearch);
|
||||||
}
|
}
|
||||||
importQuickSearch(importList);
|
importQuickSearch(importList);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
// LocalFavorites
|
// LocalFavorites
|
||||||
try {
|
try {
|
||||||
@@ -545,7 +544,8 @@ public class EhDB {
|
|||||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||||
putLocalFavorites(info);
|
putLocalFavorites(info);
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
// Filter
|
// Filter
|
||||||
try {
|
try {
|
||||||
@@ -556,7 +556,14 @@ public class EhDB {
|
|||||||
addFilter(filter);
|
addFilter(filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete temp database
|
||||||
|
if (oldRoomDatabase.isOpen()) {
|
||||||
|
oldRoomDatabase.close();
|
||||||
|
}
|
||||||
|
context.deleteDatabase(tmpDBName);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|||||||
@@ -1,72 +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;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.hippo.conaco.ValueHelper;
|
|
||||||
import com.hippo.image.ImageBitmap;
|
|
||||||
import com.hippo.streampipe.InputStreamPipe;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
|
||||||
|
|
||||||
private static final int MAX_CACHE_SIZE = 512 * 512;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
|
|
||||||
try {
|
|
||||||
isPipe.obtain();
|
|
||||||
FileInputStream is = (FileInputStream) isPipe.open();
|
|
||||||
return ImageBitmap.decode(is.getFD());
|
|
||||||
} catch (OutOfMemoryError | IOException e) {
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
isPipe.close();
|
|
||||||
isPipe.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int sizeOf(@NonNull String key, @NonNull ImageBitmap value) {
|
|
||||||
return value.getWidth() * value.getHeight() * 4 /* value.getByteCount() TODO Update Image */;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAddToMemoryCache(@NonNull ImageBitmap oldValue) {
|
|
||||||
oldValue.obtain();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRemoveFromMemoryCache(@NonNull String key, @NonNull ImageBitmap oldValue) {
|
|
||||||
oldValue.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean useMemoryCache(@NonNull String key, ImageBitmap value) {
|
|
||||||
if (value != null) {
|
|
||||||
return value.getWidth() * value.getHeight() <= MAX_CACHE_SIZE
|
|
||||||
/* value.getByteCount() <= MAX_CACHE_BYTE_COUNT TODO Update Image */;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,19 +27,13 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.hippo.ehviewer.client.EhConfig;
|
import com.hippo.ehviewer.client.EhConfig;
|
||||||
import com.hippo.ehviewer.client.EhUtils;
|
|
||||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
|
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
|
||||||
import com.hippo.ehviewer.ui.CommonOperations;
|
import com.hippo.ehviewer.ui.CommonOperations;
|
||||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||||
import com.hippo.glgallery.GalleryView;
|
|
||||||
import com.hippo.unifile.UniFile;
|
import com.hippo.unifile.UniFile;
|
||||||
import com.hippo.util.ExceptionUtils;
|
|
||||||
import com.hippo.yorozuya.AssertUtils;
|
import com.hippo.yorozuya.AssertUtils;
|
||||||
import com.hippo.yorozuya.FileUtils;
|
|
||||||
import com.hippo.yorozuya.MathUtils;
|
|
||||||
import com.hippo.yorozuya.NumberUtils;
|
import com.hippo.yorozuya.NumberUtils;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -53,18 +47,15 @@ public class Settings {
|
|||||||
|
|
||||||
public static final String KEY_ACCOUNT = "account";
|
public static final String KEY_ACCOUNT = "account";
|
||||||
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
|
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;
|
public static final int THEME_SYSTEM = -1;
|
||||||
public static final int THEME_BLACK = 2;
|
|
||||||
public static final String KEY_APPLY_NAV_BAR_THEME_COLOR = "apply_nav_bar_theme_color";
|
|
||||||
public static final String KEY_GALLERY_SITE = "gallery_site";
|
public static final String KEY_GALLERY_SITE = "gallery_site";
|
||||||
public static final String KEY_LIST_MODE = "list_mode";
|
public static final String KEY_LIST_MODE = "list_mode";
|
||||||
public static final String KEY_DETAIL_SIZE = "detail_size";
|
public static final String KEY_DETAIL_SIZE = "detail_size";
|
||||||
public static final String KEY_THUMB_SIZE = "thumb_size";
|
public static final String KEY_THUMB_SIZE = "thumb_size_";
|
||||||
public static final String KEY_THUMB_RESOLUTION = "thumb_resolution";
|
public static final String KEY_THUMB_RESOLUTION = "thumb_resolution";
|
||||||
public static final String KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
|
public static final String KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
|
||||||
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
|
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
|
||||||
public static final int DEFAULT_DEFAULT_CATEGORIES = EhUtils.ALL_CATEGORY;
|
public static final int DEFAULT_DEFAULT_CATEGORIES = EhConfig.ALL_CATEGORY;
|
||||||
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
|
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
|
||||||
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
|
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
|
||||||
/********************
|
/********************
|
||||||
@@ -78,7 +69,6 @@ public class Settings {
|
|||||||
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
|
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
|
||||||
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
|
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
|
||||||
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
|
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
|
||||||
|
|
||||||
public static final String KEY_NOTIFICATION_REQUIRED = "notification_required";
|
public static final String KEY_NOTIFICATION_REQUIRED = "notification_required";
|
||||||
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
|
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
|
||||||
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
|
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
|
||||||
@@ -86,53 +76,43 @@ public class Settings {
|
|||||||
public static final String KEY_IMAGE_RESOLUTION = "image_size";
|
public static final String KEY_IMAGE_RESOLUTION = "image_size";
|
||||||
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
|
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
|
||||||
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
|
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
|
||||||
public static final String KEY_ENABLE_ANALYTICS = "enable_analytics";
|
|
||||||
/********************
|
/********************
|
||||||
****** Advanced
|
****** Advanced
|
||||||
********************/
|
********************/
|
||||||
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
|
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 = "require_unlock";
|
||||||
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
|
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";
|
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
|
||||||
public static final int DEFAULT_READ_CACHE_SIZE = 320;
|
public static final int DEFAULT_READ_CACHE_SIZE = 320;
|
||||||
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts_2";
|
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_DOMAIN_FRONTING = "domain_fronting";
|
||||||
public static final String KEY_APP_LANGUAGE = "app_language";
|
public static final String KEY_APP_LANGUAGE = "app_language";
|
||||||
|
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
|
||||||
|
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
|
||||||
|
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
|
||||||
|
private static final String KEY_SHOW_COMMENTS = "show_gallery_comments";
|
||||||
|
private static final boolean DEFAULT_SHOW_COMMENTS = true;
|
||||||
|
private static final String KEY_PREVIEW_NUM = "preview_num";
|
||||||
|
private static final int DEFAULT_PREVIEW_NUM = 60;
|
||||||
private static final String TAG = Settings.class.getSimpleName();
|
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 KEY_DISPLAY_NAME = "display_name";
|
||||||
private static final String DEFAULT_DISPLAY_NAME = null;
|
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 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 KEY_AVATAR = "avatar";
|
||||||
private static final String DEFAULT_AVATAR = null;
|
|
||||||
private static final String KEY_SHOW_WARNING = "show_warning";
|
|
||||||
private static final boolean DEFAULT_SHOW_WARNING = true;
|
|
||||||
private static final String KEY_REMOVE_IMAGE_FILES = "include_pic";
|
private static final String KEY_REMOVE_IMAGE_FILES = "include_pic";
|
||||||
private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true;
|
private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true;
|
||||||
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
|
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
|
||||||
private static final boolean DEFAULT_NEED_SIGN_IN = true;
|
private static final boolean DEFAULT_NEED_SIGN_IN = true;
|
||||||
private static final String KEY_SELECT_SITE = "select_site";
|
private static final String KEY_SELECT_SITE = "select_site";
|
||||||
private static final boolean DEFAULT_SELECT_SITE = true;
|
private static final boolean DEFAULT_SELECT_SITE = true;
|
||||||
private static final String KEY_QUICK_SEARCH_TIP = "quick_search_tip";
|
|
||||||
private static final boolean DEFAULT_QUICK_SEARCH_TIP = true;
|
|
||||||
private static final int DEFAULT_THEME = THEME_SYSTEM;
|
private static final int DEFAULT_THEME = THEME_SYSTEM;
|
||||||
private static final boolean DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
|
|
||||||
private static final int DEFAULT_GALLERY_SITE = 1;
|
private static final int DEFAULT_GALLERY_SITE = 1;
|
||||||
private static final String KEY_LAUNCH_PAGE = "launch_page";
|
private static final String KEY_LAUNCH_PAGE = "launch_page";
|
||||||
private static final int DEFAULT_LAUNCH_PAGE = 0;
|
private static final int DEFAULT_LAUNCH_PAGE = 0;
|
||||||
private static final int DEFAULT_LIST_MODE = 0;
|
private static final int DEFAULT_LIST_MODE = 0;
|
||||||
private static final int DEFAULT_DETAIL_SIZE = 0;
|
private static final int DEFAULT_DETAIL_SIZE = 0;
|
||||||
private static final int DEFAULT_THUMB_SIZE = 1;
|
private static final int DEFAULT_THUMB_SIZE = 120;
|
||||||
private static final int DEFAULT_THUMB_RESOLUTION = 0;
|
private static final int DEFAULT_THUMB_RESOLUTION = 0;
|
||||||
private static final String KEY_FIX_THUMB_URL = "fix_thumb_url";
|
|
||||||
private static final boolean DEFAULT_FIX_THUMB_URL = false;
|
|
||||||
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
|
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
|
||||||
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
|
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
|
||||||
private static final String KEY_SHOW_GALLERY_PAGES = "show_gallery_pages";
|
private static final String KEY_SHOW_GALLERY_PAGES = "show_gallery_pages";
|
||||||
@@ -144,21 +124,14 @@ public class Settings {
|
|||||||
private static final boolean DEFAULT_METERED_NETWORK_WARNING = false;
|
private static final boolean DEFAULT_METERED_NETWORK_WARNING = false;
|
||||||
private static final String KEY_APP_LINK_VERIFY_TIP = "app_link_verify_tip";
|
private static final String KEY_APP_LINK_VERIFY_TIP = "app_link_verify_tip";
|
||||||
private static final boolean DEFAULT_APP_LINK_VERIFY_TIP = false;
|
private static final boolean DEFAULT_APP_LINK_VERIFY_TIP = false;
|
||||||
private static final String KEY_NIGHT_MODE = "night_mode";
|
|
||||||
private static final String DEFAULT_NIGHT_MODE = "-1";
|
|
||||||
private static final String KEY_E_INK_MODE = "e_ink_mode_2";
|
|
||||||
private static final boolean DEFAULT_E_INK_MODE = false;
|
|
||||||
/********************
|
/********************
|
||||||
****** Read
|
****** Read
|
||||||
********************/
|
********************/
|
||||||
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
|
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
|
||||||
private static final int DEFAULT_SCREEN_ROTATION = 0;
|
private static final int DEFAULT_SCREEN_ROTATION = 0;
|
||||||
private static final String KEY_READING_DIRECTION = "reading_direction";
|
private static final String KEY_READING_DIRECTION = "reading_direction";
|
||||||
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
|
|
||||||
private static final String KEY_PAGE_SCALING = "page_scaling";
|
private static final String KEY_PAGE_SCALING = "page_scaling";
|
||||||
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
|
|
||||||
private static final String KEY_START_POSITION = "start_position";
|
private static final String KEY_START_POSITION = "start_position";
|
||||||
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
|
|
||||||
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
|
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
|
||||||
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
|
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
|
||||||
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
|
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
|
||||||
@@ -235,22 +208,6 @@ public class Settings {
|
|||||||
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
|
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
|
||||||
private static final String KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
|
private static final String KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
|
||||||
private static final int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
|
private static final int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
|
||||||
/********************
|
|
||||||
****** Analytics
|
|
||||||
********************/
|
|
||||||
private static final String KEY_ASK_ANALYTICS = "ask_analytics";
|
|
||||||
private static final boolean DEFAULT_ASK_ANALYTICS = true;
|
|
||||||
private static final boolean DEFAULT_ENABLE_ANALYTICS = false;
|
|
||||||
private static final String KEY_USER_ID = "user_id";
|
|
||||||
private static final String FILENAME_USER_ID = ".user_id";
|
|
||||||
private static final int LENGTH_USER_ID = 32;
|
|
||||||
/********************
|
|
||||||
****** Update
|
|
||||||
********************/
|
|
||||||
private static final String KEY_BETA_UPDATE_CHANNEL = "beta_update_channel";
|
|
||||||
private static final boolean DEFAULT_BETA_UPDATE_CHANNEL = EhApplication.BETA;
|
|
||||||
private static final String KEY_SKIP_UPDATE_VERSION = "skip_update_version";
|
|
||||||
private static final int DEFAULT_SKIP_UPDATE_VERSION = 0;
|
|
||||||
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = true;
|
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = true;
|
||||||
private static final String KEY_SAVE_CRASH_LOG = "save_crash_log";
|
private static final String KEY_SAVE_CRASH_LOG = "save_crash_log";
|
||||||
private static final boolean DEFAULT_SAVE_CRASH_LOG = true;
|
private static final boolean DEFAULT_SAVE_CRASH_LOG = true;
|
||||||
@@ -263,17 +220,6 @@ public class Settings {
|
|||||||
private static final String DEFAULT_PROXY_IP = null;
|
private static final String DEFAULT_PROXY_IP = null;
|
||||||
private static final String KEY_PROXY_PORT = "proxy_port";
|
private static final String KEY_PROXY_PORT = "proxy_port";
|
||||||
private static final int DEFAULT_PROXY_PORT = -1;
|
private static final int DEFAULT_PROXY_PORT = -1;
|
||||||
/********************
|
|
||||||
****** Guide
|
|
||||||
********************/
|
|
||||||
private static final String KEY_GUIDE_QUICK_SEARCH = "guide_quick_search";
|
|
||||||
private static final boolean DEFAULT_GUIDE_QUICK_SEARCH = true;
|
|
||||||
private static final String KEY_GUIDE_COLLECTIONS = "guide_collections";
|
|
||||||
private static final boolean DEFAULT_GUIDE_COLLECTIONS = true;
|
|
||||||
private static final String KEY_GUIDE_DOWNLOAD_THUMB = "guide_download_thumb";
|
|
||||||
private static final boolean DEFAULT_GUIDE_DOWNLOAD_THUMB = true;
|
|
||||||
private static final String KEY_GUIDE_DOWNLOAD_LABELS = "guide_download_labels";
|
|
||||||
private static final boolean DEFAULT_GUIDE_DOWNLOAD_LABELS = true;
|
|
||||||
private static final String KEY_GUIDE_GALLERY = "guide_gallery";
|
private static final String KEY_GUIDE_GALLERY = "guide_gallery";
|
||||||
private static final boolean DEFAULT_GUIDE_GALLERY = true;
|
private static final boolean DEFAULT_GUIDE_GALLERY = true;
|
||||||
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
|
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
|
||||||
@@ -282,23 +228,21 @@ public class Settings {
|
|||||||
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
|
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
|
||||||
private static final String KEY_REQUEST_NEWS = "request_news";
|
private static final String KEY_REQUEST_NEWS = "request_news";
|
||||||
private static final boolean DEFAULT_REQUEST_NEWS = true;
|
private static final boolean DEFAULT_REQUEST_NEWS = true;
|
||||||
|
|
||||||
|
|
||||||
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
|
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
|
||||||
|
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||||
|
public static SharedPreferences sSettingsPre;
|
||||||
|
private static int LIST_THUMB_SIZE = 40;
|
||||||
private static Context sContext;
|
private static Context sContext;
|
||||||
private static SharedPreferences sSettingsPre;
|
|
||||||
private static EhConfig sEhConfig;
|
private static EhConfig sEhConfig;
|
||||||
|
|
||||||
public static void initialize(Context context) {
|
public static void initialize(Context context) {
|
||||||
sContext = context.getApplicationContext();
|
sContext = context.getApplicationContext();
|
||||||
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
|
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
|
||||||
sEhConfig = loadEhConfig();
|
sEhConfig = loadEhConfig();
|
||||||
fixDefaultValue(context);
|
fixDefaultValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fixDefaultValue(Context context) {
|
private static void fixDefaultValue() {
|
||||||
if ("CN".equals(Locale.getDefault().getCountry())) {
|
if ("CN".equals(Locale.getDefault().getCountry())) {
|
||||||
// Enable domain fronting if the country is CN
|
// Enable domain fronting if the country is CN
|
||||||
if (!sSettingsPre.contains(KEY_BUILT_IN_HOSTS)) {
|
if (!sSettingsPre.contains(KEY_BUILT_IN_HOSTS)) {
|
||||||
@@ -316,7 +260,6 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Locale getLocale() {
|
public static Locale getLocale() {
|
||||||
Locale locale = null;
|
|
||||||
String language = Settings.getAppLanguage();
|
String language = Settings.getAppLanguage();
|
||||||
if (language != null && !language.equals("system")) {
|
if (language != null && !language.equals("system")) {
|
||||||
return Locale.forLanguageTag(language);
|
return Locale.forLanguageTag(language);
|
||||||
@@ -361,32 +304,6 @@ public class Settings {
|
|||||||
sSettingsPre.edit().putInt(key, value).apply();
|
sSettingsPre.edit().putInt(key, value).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long getLong(String key, long defValue) {
|
|
||||||
try {
|
|
||||||
return sSettingsPre.getLong(key, defValue);
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putLong(String key, long value) {
|
|
||||||
sSettingsPre.edit().putLong(key, value).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float getFloat(String key, float defValue) {
|
|
||||||
try {
|
|
||||||
return sSettingsPre.getFloat(key, defValue);
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putFloat(String key, float value) {
|
|
||||||
sSettingsPre.edit().putFloat(key, value).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getString(String key, String defValue) {
|
public static String getString(String key, String defValue) {
|
||||||
try {
|
try {
|
||||||
return sSettingsPre.getString(key, defValue);
|
return sSettingsPre.getString(key, defValue);
|
||||||
@@ -428,14 +345,6 @@ public class Settings {
|
|||||||
sSettingsPre.edit().putString(key, Integer.toString(value)).apply();
|
sSettingsPre.edit().putString(key, Integer.toString(value)).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getVersionCode() {
|
|
||||||
return getInt(KEY_VERSION_CODE, DEFAULT_VERSION_CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putVersionCode(int value) {
|
|
||||||
putInt(KEY_VERSION_CODE, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String getDisplayName() {
|
public static String getDisplayName() {
|
||||||
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
|
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
|
||||||
@@ -445,22 +354,10 @@ public class Settings {
|
|||||||
putString(KEY_DISPLAY_NAME, value);
|
putString(KEY_DISPLAY_NAME, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAvatar() {
|
|
||||||
return getString(KEY_AVATAR, DEFAULT_AVATAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putAvatar(String value) {
|
public static void putAvatar(String value) {
|
||||||
putString(KEY_AVATAR, value);
|
putString(KEY_AVATAR, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getShowWarning() {
|
|
||||||
return getBoolean(KEY_SHOW_WARNING, DEFAULT_SHOW_WARNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putShowWarning(boolean value) {
|
|
||||||
putBoolean(KEY_SHOW_WARNING, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getRemoveImageFiles() {
|
public static boolean getRemoveImageFiles() {
|
||||||
return getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES);
|
return getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES);
|
||||||
}
|
}
|
||||||
@@ -493,14 +390,6 @@ public class Settings {
|
|||||||
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
|
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putTheme(int theme) {
|
|
||||||
putIntToStr(KEY_THEME, theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getApplyNavBarThemeColor() {
|
|
||||||
return getBoolean(KEY_APPLY_NAV_BAR_THEME_COLOR, DEFAULT_APPLY_NAV_BAR_THEME_COLOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getGallerySite() {
|
public static int getGallerySite() {
|
||||||
return getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE);
|
return getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE);
|
||||||
}
|
}
|
||||||
@@ -511,17 +400,13 @@ public class Settings {
|
|||||||
|
|
||||||
public static String getLaunchPageGalleryListSceneAction() {
|
public static String getLaunchPageGalleryListSceneAction() {
|
||||||
int value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE);
|
int value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE);
|
||||||
switch (value) {
|
return switch (value) {
|
||||||
default:
|
case 0 -> GalleryListScene.ACTION_HOMEPAGE;
|
||||||
case 0:
|
case 1 -> GalleryListScene.ACTION_SUBSCRIPTION;
|
||||||
return GalleryListScene.ACTION_HOMEPAGE;
|
case 2 -> GalleryListScene.ACTION_WHATS_HOT;
|
||||||
case 1:
|
case 3 -> GalleryListScene.ACTION_TOP_LIST;
|
||||||
return GalleryListScene.ACTION_SUBSCRIPTION;
|
default -> throw new IllegalStateException("Unexpected value: " + value);
|
||||||
case 2:
|
};
|
||||||
return GalleryListScene.ACTION_WHATS_HOT;
|
|
||||||
case 3:
|
|
||||||
return GalleryListScene.ACTION_TOP_LIST;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getListMode() {
|
public static int getListMode() {
|
||||||
@@ -534,44 +419,38 @@ public class Settings {
|
|||||||
|
|
||||||
@DimenRes
|
@DimenRes
|
||||||
public static int getDetailSizeResId() {
|
public static int getDetailSizeResId() {
|
||||||
switch (getDetailSize()) {
|
return switch (getDetailSize()) {
|
||||||
default:
|
case 0 -> R.dimen.gallery_list_column_width_long;
|
||||||
case 0:
|
case 1 -> R.dimen.gallery_list_column_width_short;
|
||||||
return R.dimen.gallery_list_column_width_long;
|
default -> throw new IllegalStateException("Unexpected value: " + getDetailSize());
|
||||||
case 1:
|
};
|
||||||
return R.dimen.gallery_list_column_width_short;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getThumbSize() {
|
public static int getThumbSize() {
|
||||||
return getIntFromStr(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE);
|
return dip2px(getInt(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DimenRes
|
public static int dip2px(int dpValue) {
|
||||||
public static int getThumbSizeResId() {
|
final float scale = sContext.getResources().getDisplayMetrics().density;
|
||||||
switch (getThumbSize()) {
|
return (int) (dpValue * scale + 0.5f);
|
||||||
case 0:
|
|
||||||
return R.dimen.gallery_grid_column_width_large;
|
|
||||||
default:
|
|
||||||
case 1:
|
|
||||||
return R.dimen.gallery_grid_column_width_middle;
|
|
||||||
case 2:
|
|
||||||
return R.dimen.gallery_grid_column_width_small;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getThumbResolution() {
|
public static int getThumbResolution() {
|
||||||
return getIntFromStr(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION);
|
return getIntFromStr(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getFixThumbUrl() {
|
public static boolean getShowComments() {
|
||||||
return getBoolean(KEY_FIX_THUMB_URL, DEFAULT_FIX_THUMB_URL);
|
return getBoolean(KEY_SHOW_COMMENTS, DEFAULT_SHOW_COMMENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getRequestNews() {
|
public static boolean getRequestNews() {
|
||||||
return getBoolean(KEY_REQUEST_NEWS, DEFAULT_REQUEST_NEWS);
|
return getBoolean(KEY_REQUEST_NEWS, DEFAULT_REQUEST_NEWS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean getHideHvEvents() {
|
||||||
|
return getBoolean(KEY_HIDE_HV_EVENTS, DEFAULT_HIDE_HV_EVENTS);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean getShowJpnTitle() {
|
public static boolean getShowJpnTitle() {
|
||||||
return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE);
|
return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE);
|
||||||
}
|
}
|
||||||
@@ -588,36 +467,22 @@ public class Settings {
|
|||||||
putBoolean(KEY_SHOW_TAG_TRANSLATIONS, value);
|
putBoolean(KEY_SHOW_TAG_TRANSLATIONS, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDefaultCategories() {
|
public static int getPreviewNum() {
|
||||||
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
|
return getInt(KEY_PREVIEW_NUM, DEFAULT_PREVIEW_NUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putDefaultCategories(int value) {
|
public static int getDefaultCategories() {
|
||||||
sEhConfig.defaultCategories = value;
|
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
|
||||||
sEhConfig.setDirty();
|
|
||||||
putInt(KEY_DEFAULT_CATEGORIES, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getExcludedTagNamespaces() {
|
public static int getExcludedTagNamespaces() {
|
||||||
return getInt(KEY_EXCLUDED_TAG_NAMESPACES, DEFAULT_EXCLUDED_TAG_NAMESPACES);
|
return getInt(KEY_EXCLUDED_TAG_NAMESPACES, DEFAULT_EXCLUDED_TAG_NAMESPACES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putExcludedTagNamespaces(int value) {
|
|
||||||
sEhConfig.excludedNamespaces = value;
|
|
||||||
sEhConfig.setDirty();
|
|
||||||
putInt(KEY_EXCLUDED_TAG_NAMESPACES, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getExcludedLanguages() {
|
public static String getExcludedLanguages() {
|
||||||
return getString(KEY_EXCLUDED_LANGUAGES, DEFAULT_EXCLUDED_LANGUAGES);
|
return getString(KEY_EXCLUDED_LANGUAGES, DEFAULT_EXCLUDED_LANGUAGES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putExcludedLanguages(String value) {
|
|
||||||
sEhConfig.excludedLanguages = value;
|
|
||||||
sEhConfig.setDirty();
|
|
||||||
putString(KEY_EXCLUDED_LANGUAGES, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getMeteredNetworkWarning() {
|
public static boolean getMeteredNetworkWarning() {
|
||||||
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
|
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
|
||||||
}
|
}
|
||||||
@@ -638,29 +503,14 @@ public class Settings {
|
|||||||
putIntToStr(KEY_SCREEN_ROTATION, value);
|
putIntToStr(KEY_SCREEN_ROTATION, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GalleryView.LayoutMode
|
|
||||||
public static int getReadingDirection() {
|
|
||||||
return GalleryView.sanitizeLayoutMode(getIntFromStr(KEY_READING_DIRECTION, DEFAULT_READING_DIRECTION));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putReadingDirection(int value) {
|
public static void putReadingDirection(int value) {
|
||||||
putIntToStr(KEY_READING_DIRECTION, value);
|
putIntToStr(KEY_READING_DIRECTION, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GalleryView.ScaleMode
|
|
||||||
public static int getPageScaling() {
|
|
||||||
return GalleryView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putPageScaling(int value) {
|
public static void putPageScaling(int value) {
|
||||||
putIntToStr(KEY_PAGE_SCALING, value);
|
putIntToStr(KEY_PAGE_SCALING, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GalleryView.StartPosition
|
|
||||||
public static int getStartPosition() {
|
|
||||||
return GalleryView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putStartPosition(int value) {
|
public static void putStartPosition(int value) {
|
||||||
putIntToStr(KEY_START_POSITION, value);
|
putIntToStr(KEY_START_POSITION, value);
|
||||||
}
|
}
|
||||||
@@ -757,25 +607,16 @@ public class Settings {
|
|||||||
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
|
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putEnabledSecurity(boolean value) {
|
|
||||||
putBoolean(KEY_READING_FULLSCREEN, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static UniFile getDownloadLocation() {
|
public static UniFile getDownloadLocation() {
|
||||||
UniFile dir = null;
|
UniFile dir;
|
||||||
try {
|
Uri.Builder builder = new Uri.Builder();
|
||||||
Uri.Builder builder = new Uri.Builder();
|
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
|
||||||
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
|
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
|
||||||
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
|
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
|
||||||
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
|
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
|
||||||
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
|
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
|
||||||
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
|
dir = UniFile.fromUri(sContext, builder.build());
|
||||||
dir = UniFile.fromUri(sContext, builder.build());
|
|
||||||
} catch (Throwable e) {
|
|
||||||
ExceptionUtils.throwIfFatal(e);
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
return dir != null ? dir : UniFile.fromFile(AppConfig.getDefaultDownloadDir());
|
return dir != null ? dir : UniFile.fromFile(AppConfig.getDefaultDownloadDir());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,26 +667,14 @@ public class Settings {
|
|||||||
return getIntFromStr(KEY_MULTI_THREAD_DOWNLOAD, DEFAULT_MULTI_THREAD_DOWNLOAD);
|
return getIntFromStr(KEY_MULTI_THREAD_DOWNLOAD, DEFAULT_MULTI_THREAD_DOWNLOAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putMultiThreadDownload(int value) {
|
|
||||||
putIntToStr(KEY_MULTI_THREAD_DOWNLOAD, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getDownloadDelay() {
|
public static int getDownloadDelay() {
|
||||||
return getIntFromStr(KEY_DOWNLOAD_DELAY, DEFAULT_DOWNLOAD_DELAY);
|
return getIntFromStr(KEY_DOWNLOAD_DELAY, DEFAULT_DOWNLOAD_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putDownloadDelay(int value) {
|
|
||||||
putIntToStr(KEY_DOWNLOAD_DELAY, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getPreloadImage() {
|
public static int getPreloadImage() {
|
||||||
return getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE);
|
return getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putPreloadImage(int value) {
|
|
||||||
putIntToStr(KEY_PRELOAD_IMAGE, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getImageResolution() {
|
public static String getImageResolution() {
|
||||||
return getString(KEY_IMAGE_RESOLUTION, DEFAULT_IMAGE_RESOLUTION);
|
return getString(KEY_IMAGE_RESOLUTION, DEFAULT_IMAGE_RESOLUTION);
|
||||||
}
|
}
|
||||||
@@ -860,10 +689,6 @@ public class Settings {
|
|||||||
return getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE);
|
return getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putDownloadOriginImage(boolean value) {
|
|
||||||
putBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String[] getFavCat() {
|
public static String[] getFavCat() {
|
||||||
String[] favCat = new String[10];
|
String[] favCat = new String[10];
|
||||||
favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0);
|
favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0);
|
||||||
@@ -958,104 +783,10 @@ public class Settings {
|
|||||||
putInt(KEY_DEFAULT_FAV_SLOT, value);
|
putInt(KEY_DEFAULT_FAV_SLOT, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getAskAnalytics() {
|
|
||||||
return getBoolean(KEY_ASK_ANALYTICS, DEFAULT_ASK_ANALYTICS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putAskAnalytics(boolean value) {
|
|
||||||
putBoolean(KEY_ASK_ANALYTICS, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getEnableAnalytics() {
|
|
||||||
return getBoolean(KEY_ENABLE_ANALYTICS, DEFAULT_ENABLE_ANALYTICS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putEnableAnalytics(boolean value) {
|
|
||||||
putBoolean(KEY_ENABLE_ANALYTICS, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getUserID() {
|
|
||||||
boolean writeXml = false;
|
|
||||||
boolean writeFile = false;
|
|
||||||
String userID = getString(KEY_USER_ID, null);
|
|
||||||
File file = AppConfig.getFileInExternalAppDir(FILENAME_USER_ID);
|
|
||||||
if (null == userID || !isValidUserID(userID)) {
|
|
||||||
writeXml = true;
|
|
||||||
// Get use ID from out sd card file
|
|
||||||
userID = FileUtils.read(file);
|
|
||||||
if (null == userID || !isValidUserID(userID)) {
|
|
||||||
writeFile = true;
|
|
||||||
userID = generateUserID();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
writeFile = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (writeXml) {
|
|
||||||
putString(KEY_USER_ID, userID);
|
|
||||||
}
|
|
||||||
if (writeFile) {
|
|
||||||
FileUtils.write(file, userID);
|
|
||||||
}
|
|
||||||
|
|
||||||
return userID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static String generateUserID() {
|
|
||||||
int length = LENGTH_USER_ID;
|
|
||||||
StringBuilder sb = new StringBuilder(length);
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
if (MathUtils.random(0, ('9' - '0' + 1) + ('z' - 'a' + 1)) <= '9' - '0') {
|
|
||||||
sb.append((char) MathUtils.random('0', '9' + 1));
|
|
||||||
} else {
|
|
||||||
sb.append((char) MathUtils.random('a', 'z' + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isValidUserID(@Nullable String userID) {
|
|
||||||
if (null == userID || LENGTH_USER_ID != userID.length()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < LENGTH_USER_ID; i++) {
|
|
||||||
char ch = userID.charAt(i);
|
|
||||||
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getBetaUpdateChannel() {
|
|
||||||
return getBoolean(KEY_BETA_UPDATE_CHANNEL, DEFAULT_BETA_UPDATE_CHANNEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putBetaUpdateChannel(boolean value) {
|
|
||||||
putBoolean(KEY_BETA_UPDATE_CHANNEL, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getSkipUpdateVersion() {
|
|
||||||
return getInt(KEY_SKIP_UPDATE_VERSION, DEFAULT_SKIP_UPDATE_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putSkipUpdateVersion(int value) {
|
|
||||||
putInt(KEY_SKIP_UPDATE_VERSION, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getSaveParseErrorBody() {
|
public static boolean getSaveParseErrorBody() {
|
||||||
return getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY);
|
return getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putSaveParseErrorBody(boolean value) {
|
|
||||||
putBoolean(KEY_SAVE_PARSE_ERROR_BODY, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getSaveCrashLog() {
|
public static boolean getSaveCrashLog() {
|
||||||
return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG);
|
return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG);
|
||||||
}
|
}
|
||||||
@@ -1067,14 +798,11 @@ public class Settings {
|
|||||||
public static void putSecurity(boolean value) {
|
public static void putSecurity(boolean value) {
|
||||||
putBoolean(KEY_SECURITY, value);
|
putBoolean(KEY_SECURITY, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getSecurityDelay() {
|
public static int getSecurityDelay() {
|
||||||
return getInt(KEY_SECURITY_DELAY, 0);
|
return getInt(KEY_SECURITY_DELAY, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getEnableFingerprint() {
|
|
||||||
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getReadCacheSize() {
|
public static int getReadCacheSize() {
|
||||||
return getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE);
|
return getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE);
|
||||||
}
|
}
|
||||||
@@ -1099,10 +827,6 @@ public class Settings {
|
|||||||
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
|
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putAppLanguage(String value) {
|
|
||||||
putString(KEY_APP_LANGUAGE, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getProxyType() {
|
public static int getProxyType() {
|
||||||
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
|
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
|
||||||
}
|
}
|
||||||
@@ -1127,38 +851,6 @@ public class Settings {
|
|||||||
putInt(KEY_PROXY_PORT, value);
|
putInt(KEY_PROXY_PORT, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getGuideQuickSearch() {
|
|
||||||
return getBoolean(KEY_GUIDE_QUICK_SEARCH, DEFAULT_GUIDE_QUICK_SEARCH);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putGuideQuickSearch(boolean value) {
|
|
||||||
putBoolean(KEY_GUIDE_QUICK_SEARCH, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getGuideCollections() {
|
|
||||||
return getBoolean(KEY_GUIDE_COLLECTIONS, DEFAULT_GUIDE_COLLECTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putGuideCollections(boolean value) {
|
|
||||||
putBoolean(KEY_GUIDE_COLLECTIONS, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getGuideDownloadThumb() {
|
|
||||||
return getBoolean(KEY_GUIDE_DOWNLOAD_THUMB, DEFAULT_GUIDE_DOWNLOAD_THUMB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putGuideDownloadThumb(boolean value) {
|
|
||||||
putBoolean(KEY_GUIDE_DOWNLOAD_THUMB, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getGuideDownloadLabels() {
|
|
||||||
return getBoolean(KEY_GUIDE_DOWNLOAD_LABELS, DEFAULT_GUIDE_DOWNLOAD_LABELS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void puttGuideDownloadLabels(boolean value) {
|
|
||||||
putBoolean(KEY_GUIDE_DOWNLOAD_LABELS, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getGuideGallery() {
|
public static boolean getGuideGallery() {
|
||||||
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
|
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,12 @@ import androidx.browser.customtabs.CustomTabsIntent;
|
|||||||
import com.hippo.ehviewer.client.EhUrlOpener;
|
import com.hippo.ehviewer.client.EhUrlOpener;
|
||||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
||||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
|
||||||
import com.hippo.ehviewer.ui.MainActivity;
|
import com.hippo.ehviewer.ui.MainActivity;
|
||||||
import com.hippo.scene.Announcer;
|
import com.hippo.scene.Announcer;
|
||||||
import com.hippo.scene.StageActivity;
|
import com.hippo.scene.StageActivity;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||||
|
|
||||||
public final class UrlOpener {
|
public final class UrlOpener {
|
||||||
|
|
||||||
private UrlOpener() {
|
private UrlOpener() {
|
||||||
@@ -56,19 +57,19 @@ public final class UrlOpener {
|
|||||||
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
|
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
if (result.gid == galleryDetail.gid) {
|
if (result.gid == galleryDetail.gid) {
|
||||||
intent = new Intent(context, GalleryActivity.class);
|
intent = new Intent(context, ReaderActivity.class);
|
||||||
intent.setAction(GalleryActivity.ACTION_EH);
|
intent.setAction(ReaderActivity.ACTION_EH);
|
||||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||||
intent.putExtra(GalleryActivity.KEY_PAGE, result.page);
|
intent.putExtra(ReaderActivity.KEY_PAGE, result.page);
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (url.startsWith("#c")) {
|
} else if (url.startsWith("#c")) {
|
||||||
try {
|
try {
|
||||||
intent = new Intent(context, GalleryActivity.class);
|
intent = new Intent(context, ReaderActivity.class);
|
||||||
intent.setAction(GalleryActivity.ACTION_EH);
|
intent.setAction(ReaderActivity.ACTION_EH);
|
||||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||||
intent.putExtra(GalleryActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
intent.putExtra(ReaderActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
return;
|
return;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.hippo.ehviewer.client;
|
package com.hippo.ehviewer.client;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
import com.hippo.ehviewer.EhApplication;
|
import com.hippo.ehviewer.EhApplication;
|
||||||
@@ -55,18 +54,18 @@ public class EhClient {
|
|||||||
public static final int METHOD_ARCHIVE_LIST = 17;
|
public static final int METHOD_ARCHIVE_LIST = 17;
|
||||||
public static final int METHOD_DOWNLOAD_ARCHIVE = 18;
|
public static final int METHOD_DOWNLOAD_ARCHIVE = 18;
|
||||||
public static final int METHOD_VOTE_TAG = 19;
|
public static final int METHOD_VOTE_TAG = 19;
|
||||||
|
public static final int METHOD_GET_UCONFIG = 20;
|
||||||
|
private static final OkHttpClient mOkHttpClient = EhApplication.getOkHttpClient();
|
||||||
private final ThreadPoolExecutor mRequestThreadPool;
|
private final ThreadPoolExecutor mRequestThreadPool;
|
||||||
private final OkHttpClient mOkHttpClient;
|
|
||||||
|
|
||||||
public EhClient(Context context) {
|
public EhClient() {
|
||||||
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
|
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
|
||||||
mOkHttpClient = EhApplication.getOkHttpClient(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void execute(EhRequest request) {
|
public void execute(EhRequest request) {
|
||||||
if (!request.isCancelled()) {
|
if (!request.isCancelled()) {
|
||||||
Task task = new Task(request.getMethod(), request.getCallback(), request.getEhConfig());
|
Task task = new Task(request.getMethod(), request.getCallback());
|
||||||
task.executeOnExecutor(mRequestThreadPool, request.getArgs());
|
task.executeOnExecutor(mRequestThreadPool, request.getArgs());
|
||||||
request.task = task;
|
request.task = task;
|
||||||
} else {
|
} else {
|
||||||
@@ -83,18 +82,17 @@ public class EhClient {
|
|||||||
void onCancel();
|
void onCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Task extends AsyncTask<Object, Void, Object> {
|
@SuppressWarnings("deprecation")
|
||||||
|
public static class Task extends AsyncTask<Object, Void, Object> {
|
||||||
|
|
||||||
private final int mMethod;
|
private final int mMethod;
|
||||||
private final AtomicReference<Call> mCall = new AtomicReference<>();
|
private final AtomicReference<Call> mCall = new AtomicReference<>();
|
||||||
private final AtomicBoolean mStop = new AtomicBoolean();
|
private final AtomicBoolean mStop = new AtomicBoolean();
|
||||||
private Callback mCallback;
|
private Callback<Object> mCallback;
|
||||||
private EhConfig mEhConfig;
|
|
||||||
|
|
||||||
public Task(int method, Callback callback, EhConfig ehConfig) {
|
public Task(int method, Callback<Object> callback) {
|
||||||
mMethod = method;
|
mMethod = method;
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
mEhConfig = ehConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called in Job thread
|
// Called in Job thread
|
||||||
@@ -107,10 +105,6 @@ public class EhClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public EhConfig getEhConfig() {
|
|
||||||
return mEhConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
if (!mStop.get()) {
|
if (!mStop.get()) {
|
||||||
mStop.lazySet(true);
|
mStop.lazySet(true);
|
||||||
@@ -133,7 +127,6 @@ public class EhClient {
|
|||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
mCallback = null;
|
mCallback = null;
|
||||||
mEhConfig = null;
|
|
||||||
mCall.lazySet(null);
|
mCall.lazySet(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,53 +134,51 @@ public class EhClient {
|
|||||||
@Override
|
@Override
|
||||||
protected Object doInBackground(Object... params) {
|
protected Object doInBackground(Object... params) {
|
||||||
try {
|
try {
|
||||||
switch (mMethod) {
|
return switch (mMethod) {
|
||||||
case METHOD_SIGN_IN:
|
case METHOD_SIGN_IN ->
|
||||||
return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
|
EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
|
||||||
case METHOD_GET_GALLERY_LIST:
|
case METHOD_GET_GALLERY_LIST ->
|
||||||
return EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
|
EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
|
||||||
case METHOD_GET_GALLERY_DETAIL:
|
case METHOD_GET_GALLERY_DETAIL ->
|
||||||
return EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
|
EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
|
||||||
case METHOD_GET_PREVIEW_SET:
|
case METHOD_GET_PREVIEW_SET ->
|
||||||
return EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
|
EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
|
||||||
case METHOD_GET_RATE_GALLERY:
|
case METHOD_GET_RATE_GALLERY ->
|
||||||
return EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
|
EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
|
||||||
case METHOD_GET_COMMENT_GALLERY:
|
case METHOD_GET_COMMENT_GALLERY ->
|
||||||
return EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
|
EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
|
||||||
case METHOD_GET_GALLERY_TOKEN:
|
case METHOD_GET_GALLERY_TOKEN ->
|
||||||
return EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
|
EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
|
||||||
case METHOD_GET_FAVORITES:
|
case METHOD_GET_FAVORITES ->
|
||||||
return EhEngine.getFavorites(this, mOkHttpClient, (String) params[0], (Boolean) params[1]);
|
EhEngine.getFavorites(this, mOkHttpClient, (String) params[0]);
|
||||||
case METHOD_ADD_FAVORITES:
|
case METHOD_ADD_FAVORITES ->
|
||||||
return EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
|
EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
|
||||||
case METHOD_ADD_FAVORITES_RANGE:
|
case METHOD_ADD_FAVORITES_RANGE ->
|
||||||
return EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
|
EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
|
||||||
case METHOD_MODIFY_FAVORITES:
|
case METHOD_MODIFY_FAVORITES ->
|
||||||
return EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2], (Boolean) params[3]);
|
EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2]);
|
||||||
case METHOD_GET_TORRENT_LIST:
|
case METHOD_GET_TORRENT_LIST ->
|
||||||
return EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||||
case METHOD_GET_PROFILE:
|
case METHOD_GET_PROFILE -> EhEngine.getProfile(this, mOkHttpClient);
|
||||||
return EhEngine.getProfile(this, mOkHttpClient);
|
case METHOD_VOTE_COMMENT ->
|
||||||
case METHOD_VOTE_COMMENT:
|
EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
|
||||||
return EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
|
case METHOD_IMAGE_SEARCH ->
|
||||||
case METHOD_IMAGE_SEARCH:
|
EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
|
||||||
return EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
|
case METHOD_ARCHIVE_LIST ->
|
||||||
case METHOD_ARCHIVE_LIST:
|
EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||||
return EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
case METHOD_DOWNLOAD_ARCHIVE ->
|
||||||
case METHOD_DOWNLOAD_ARCHIVE:
|
EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
|
||||||
return EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
|
case METHOD_VOTE_TAG ->
|
||||||
case METHOD_VOTE_TAG:
|
EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
|
||||||
return EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
|
case METHOD_GET_UCONFIG -> EhEngine.getUConfig(this, mOkHttpClient);
|
||||||
default:
|
default -> new IllegalStateException("Can't detect method " + mMethod);
|
||||||
return new IllegalStateException("Can't detect method " + mMethod);
|
};
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
ExceptionUtils.throwIfFatal(e);
|
ExceptionUtils.throwIfFatal(e);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Object result) {
|
protected void onPostExecute(Object result) {
|
||||||
if (mCallback != null) {
|
if (mCallback != null) {
|
||||||
@@ -205,7 +196,6 @@ public class EhClient {
|
|||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
mCallback = null;
|
mCallback = null;
|
||||||
mEhConfig = null;
|
|
||||||
mCall.lazySet(null);
|
mCall.lazySet(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2018 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.hippo.ehviewer.client;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Created by Hippo on 2018/3/23.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.EhApplication;
|
|
||||||
import com.hippo.ehviewer.Hosts;
|
|
||||||
import com.hippo.ehviewer.Settings;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import okhttp3.Dns;
|
|
||||||
|
|
||||||
public class EhDns implements Dns {
|
|
||||||
|
|
||||||
private static final Map<String, List<InetAddress>> builtInHosts;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Map<String, List<InetAddress>> map = new HashMap<>();
|
|
||||||
put(map, "e-hentai.org", "104.20.134.21", "104.20.135.21", "172.67.0.127");
|
|
||||||
put(map, "exhentai.org", "178.175.128.252", "178.175.129.252", "178.175.129.254", "178.175.128.254",
|
|
||||||
"178.175.132.20", "178.175.132.22");
|
|
||||||
put(map, "repo.e-hentai.org", "94.100.28.57", "94.100.29.73");
|
|
||||||
put(map, "forums.e-hentai.org", "94.100.18.243");
|
|
||||||
put(map, "ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
|
||||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
|
||||||
put(map, "gt0.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
|
||||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
|
||||||
put(map, "gt1.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
|
||||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
|
||||||
put(map, "gt2.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
|
||||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
|
||||||
put(map, "gt3.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
|
||||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
|
||||||
put(map, "ul.ehgt.org", "94.100.24.82", "94.100.24.72");
|
|
||||||
put(map, "raw.githubusercontent.com", "151.101.0.133", "151.101.64.133", "151.101.128.133", "151.101.192.133");
|
|
||||||
builtInHosts = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Hosts hosts;
|
|
||||||
|
|
||||||
public EhDns(Context context) {
|
|
||||||
hosts = EhApplication.getHosts(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void put(Map<String, List<InetAddress>> map, String host, String... ips) {
|
|
||||||
List<InetAddress> addresses = new ArrayList<>();
|
|
||||||
for (String ip : ips) {
|
|
||||||
addresses.add(Hosts.toInetAddress(host, ip));
|
|
||||||
}
|
|
||||||
map.put(host, addresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException {
|
|
||||||
List<InetAddress> inetAddresses = hosts.get(hostname);
|
|
||||||
if (inetAddresses != null) {
|
|
||||||
return inetAddresses;
|
|
||||||
}
|
|
||||||
if (Settings.getBuiltInHosts()) {
|
|
||||||
inetAddresses = builtInHosts.get(hostname);
|
|
||||||
if (inetAddresses != null) {
|
|
||||||
return inetAddresses;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SYSTEM.lookup(hostname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Hippo Seven
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.hippo.ehviewer.client
|
||||||
|
|
||||||
|
import com.hippo.ehviewer.EhApplication
|
||||||
|
import com.hippo.ehviewer.Hosts
|
||||||
|
import com.hippo.ehviewer.Settings
|
||||||
|
import okhttp3.Dns
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
|
class EhDns : Dns {
|
||||||
|
private val hosts = EhApplication.hosts
|
||||||
|
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
/* Pair(ip: String!, blockedByCCP: Boolean!) */
|
||||||
|
val ehgtHosts = arrayOf(
|
||||||
|
Pair("37.48.89.44", false),
|
||||||
|
Pair("81.171.10.48", false),
|
||||||
|
Pair("178.162.139.24", false),
|
||||||
|
Pair("178.162.140.212", false),
|
||||||
|
Pair("2001:1af8:4700:a062:8::47de", false),
|
||||||
|
Pair("2001:1af8:4700:a062:9::47de", true),
|
||||||
|
Pair("2001:1af8:4700:a0c9:4::47de", false),
|
||||||
|
Pair("2001:1af8:4700:a0c9:3::47de", true),
|
||||||
|
)
|
||||||
|
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"e-hentai.org",
|
||||||
|
Pair("104.20.134.21", false),
|
||||||
|
Pair("104.20.135.21", false),
|
||||||
|
Pair("172.67.0.127", false),
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"exhentai.org",
|
||||||
|
Pair("178.175.128.252", false),
|
||||||
|
Pair("178.175.129.252", false),
|
||||||
|
Pair("178.175.129.254", false),
|
||||||
|
Pair("178.175.128.254", false),
|
||||||
|
Pair("178.175.132.20", false),
|
||||||
|
Pair("178.175.132.22", false),
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"repo.e-hentai.org",
|
||||||
|
Pair("94.100.28.57", true),
|
||||||
|
Pair("94.100.29.73", true),
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"forums.e-hentai.org",
|
||||||
|
Pair("94.100.18.243", false),
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"ehgt.org",
|
||||||
|
*ehgtHosts
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"gt0.ehgt.org",
|
||||||
|
*ehgtHosts
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"gt1.ehgt.org",
|
||||||
|
*ehgtHosts
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"gt2.ehgt.org",
|
||||||
|
*ehgtHosts
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"gt3.ehgt.org",
|
||||||
|
*ehgtHosts
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"ul.ehgt.org",
|
||||||
|
Pair("94.100.24.82", true),
|
||||||
|
Pair("94.100.24.72", true),
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
builtInHosts,
|
||||||
|
"raw.githubusercontent.com",
|
||||||
|
Pair("151.101.0.133", false),
|
||||||
|
Pair("151.101.64.133", false),
|
||||||
|
Pair("151.101.128.133", false),
|
||||||
|
Pair("151.101.192.133", false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun put(
|
||||||
|
map: MutableMap<String, List<InetAddress>>,
|
||||||
|
host: String,
|
||||||
|
vararg ips: Pair<String, Boolean>
|
||||||
|
) {
|
||||||
|
map[host] = ips.mapNotNull { pair ->
|
||||||
|
Hosts.toInetAddress(host, pair.first).takeUnless { Settings.getDF() && pair.second }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(UnknownHostException::class)
|
||||||
|
override fun lookup(hostname: String): List<InetAddress> {
|
||||||
|
return hosts[hostname] ?: builtInHosts[hostname].takeIf { Settings.getBuiltInHosts() }
|
||||||
|
?: Dns.SYSTEM.lookup(hostname)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -185,8 +185,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -245,12 +244,6 @@ public class EhEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.getFixThumbUrl()) {
|
|
||||||
for (GalleryInfo info : list) {
|
|
||||||
info.thumb = EhUrl.getFixedPreviewThumbUrl(info.thumb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GalleryListParser.Result getGalleryList(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
public static GalleryListParser.Result getGalleryList(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||||
@@ -269,8 +262,7 @@ public class EhEngine {
|
|||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
GalleryListParser.Result result;
|
GalleryListParser.Result result;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -332,8 +324,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -360,14 +351,13 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
String html = EventPaneParser.parse(body);
|
String html = EventPaneParser.parse(body);
|
||||||
if (html != null) {
|
if (html != null) {
|
||||||
EhApplication.getInstance().showEventPane(html);
|
EhApplication.getApplication().showEventPane(html);
|
||||||
}
|
}
|
||||||
return GalleryDetailParser.parse(body);
|
return GalleryDetailParser.parse(body);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@@ -393,8 +383,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -435,8 +424,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -472,8 +460,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -516,8 +503,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -530,7 +516,7 @@ public class EhEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static FavoritesParser.Result getFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
public static FavoritesParser.Result getFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||||
String url, boolean callApi) throws Throwable {
|
String url) throws Throwable {
|
||||||
String referer = EhUrl.getReferer();
|
String referer = EhUrl.getReferer();
|
||||||
Log.d(TAG, url);
|
Log.d(TAG, url);
|
||||||
Request request = new EhRequestBuilder(url, referer).build();
|
Request request = new EhRequestBuilder(url, referer).build();
|
||||||
@@ -545,8 +531,7 @@ public class EhEngine {
|
|||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
FavoritesParser.Result result;
|
FavoritesParser.Result result;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -598,8 +583,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -623,7 +607,7 @@ public class EhEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static FavoritesParser.Result modifyFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
public static FavoritesParser.Result modifyFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||||
String url, long[] gidArray, int dstCat, boolean callApi) throws Throwable {
|
String url, long[] gidArray, int dstCat) throws Throwable {
|
||||||
String catStr;
|
String catStr;
|
||||||
if (dstCat == -1) {
|
if (dstCat == -1) {
|
||||||
catStr = "delete";
|
catStr = "delete";
|
||||||
@@ -654,8 +638,7 @@ public class EhEngine {
|
|||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
FavoritesParser.Result result;
|
FavoritesParser.Result result;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -687,8 +670,7 @@ public class EhEngine {
|
|||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
Pair<String, String>[] result;
|
Pair<String, String>[] result;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -718,8 +700,7 @@ public class EhEngine {
|
|||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
Pair<String, Pair<String, String>[]> result;
|
Pair<String, Pair<String, String>[]> result;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -760,8 +741,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -794,8 +774,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -822,8 +801,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -835,6 +813,55 @@ public class EhEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Void getUConfig(@Nullable EhClient.Task task,
|
||||||
|
OkHttpClient okHttpClient) throws Throwable {
|
||||||
|
String url = EhUrl.getUConfigUrl();
|
||||||
|
Log.d(TAG, url);
|
||||||
|
Request request = new EhRequestBuilder(url, null).build();
|
||||||
|
Call call = okHttpClient.newCall(request);
|
||||||
|
|
||||||
|
// Put call
|
||||||
|
if (null != task) {
|
||||||
|
task.setCall(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
String body = null;
|
||||||
|
Headers headers = null;
|
||||||
|
int code = -1;
|
||||||
|
try (Response response = call.execute()) {
|
||||||
|
code = response.code();
|
||||||
|
headers = response.headers();
|
||||||
|
body = response.body().string();
|
||||||
|
request = response.request();
|
||||||
|
throwException(call, code, headers, body, null);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
ExceptionUtils.throwIfFatal(e);
|
||||||
|
throwException(call, code, headers, body, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Use a better way to handle 302
|
||||||
|
if (!request.url().toString().equals(url)) {
|
||||||
|
Log.d(TAG, "Redirected! Retry " + url);
|
||||||
|
request = new EhRequestBuilder(url, null).build();
|
||||||
|
call = okHttpClient.newCall(request);
|
||||||
|
if (null != task) task.setCall(call);
|
||||||
|
|
||||||
|
try (Response response = call.execute()) {
|
||||||
|
code = response.code();
|
||||||
|
headers = response.headers();
|
||||||
|
body = response.body().string();
|
||||||
|
throwException(call, code, headers, body, null);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
ExceptionUtils.throwIfFatal(e);
|
||||||
|
throwException(call, code, headers, body, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static VoteCommentParser.Result voteComment(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
public static VoteCommentParser.Result voteComment(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||||
long apiUid, String apiKey, long gid, String token, long commentId, int commentVote) throws Throwable {
|
long apiUid, String apiKey, long gid, String token, long commentId, int commentVote) throws Throwable {
|
||||||
final JSONObject json = new JSONObject();
|
final JSONObject json = new JSONObject();
|
||||||
@@ -863,8 +890,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -904,8 +930,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -968,8 +993,7 @@ public class EhEngine {
|
|||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
GalleryListParser.Result result;
|
GalleryListParser.Result result;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
|
|
||||||
Log.d(TAG, "" + response.request().url());
|
Log.d(TAG, "" + response.request().url());
|
||||||
|
|
||||||
@@ -1003,8 +1027,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
@@ -1045,8 +1068,7 @@ public class EhEngine {
|
|||||||
String body = null;
|
String body = null;
|
||||||
Headers headers = null;
|
Headers headers = null;
|
||||||
int code = -1;
|
int code = -1;
|
||||||
try {
|
try (Response response = call.execute()) {
|
||||||
Response response = call.execute();
|
|
||||||
code = response.code();
|
code = response.code();
|
||||||
headers = response.headers();
|
headers = response.headers();
|
||||||
body = response.body().string();
|
body = response.body().string();
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 Tarsin Norbin
|
|
||||||
*
|
|
||||||
* This file is part of EhViewer
|
|
||||||
*
|
|
||||||
* EhViewer is free software: you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* EhViewer is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.hippo.ehviewer.client;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.Settings;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
|
|
||||||
public class EhSSLSocketFactory extends SSLSocketFactory {
|
|
||||||
@Override
|
|
||||||
public String[] getDefaultCipherSuites() {
|
|
||||||
return ((SSLSocketFactory) getDefault()).getDefaultCipherSuites();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getSupportedCipherSuites() {
|
|
||||||
return ((SSLSocketFactory) getDefault()).getSupportedCipherSuites();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
|
||||||
SSLSocketFactory defaultFactory = (SSLSocketFactory) getDefault();
|
|
||||||
if (!Settings.getDF()) {
|
|
||||||
return defaultFactory.createSocket(s, host, port, autoClose);
|
|
||||||
}
|
|
||||||
String address = s.getInetAddress().getHostAddress();
|
|
||||||
SSLSocket socket = (SSLSocket) defaultFactory.createSocket(s, address, port, autoClose);
|
|
||||||
SSLSession sslSession = socket.getSession();
|
|
||||||
Log.d("EhSSLSocketFactory", "Host: " + host + " Address: " + address + " Protocol:" + sslSession.getProtocol() + " CipherSuite:" + sslSession.getCipherSuite());
|
|
||||||
socket.setEnabledProtocols(socket.getSupportedProtocols());
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(String host, int port) throws IOException {
|
|
||||||
return getDefault().createSocket(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
|
||||||
return getDefault().createSocket(host, port, localHost, localPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
|
||||||
return getDefault().createSocket(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
|
||||||
return getDefault().createSocket(address, port, localAddress, localPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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.ehviewer.client
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.Socket
|
||||||
|
import javax.net.ssl.SSLSocket
|
||||||
|
import javax.net.ssl.SSLSocketFactory
|
||||||
|
|
||||||
|
class EhSSLSocketFactory : SSLSocketFactory() {
|
||||||
|
private val sslSocketFactory = getDefault() as SSLSocketFactory
|
||||||
|
override fun getDefaultCipherSuites(): Array<String> {
|
||||||
|
return sslSocketFactory.defaultCipherSuites
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedCipherSuites(): Array<String> {
|
||||||
|
return sslSocketFactory.supportedCipherSuites
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
|
||||||
|
val address = s.inetAddress.hostAddress
|
||||||
|
val socket = sslSocketFactory.createSocket(s, address, port, autoClose) as SSLSocket
|
||||||
|
val sslSession = socket.session
|
||||||
|
Log.d(
|
||||||
|
"EhSSLSocketFactory",
|
||||||
|
"Host: " + host + " Address: " + address + " Protocol:" + sslSession.protocol + " CipherSuite:" + sslSession.cipherSuite
|
||||||
|
)
|
||||||
|
return socket
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createSocket(host: String, port: Int): Socket {
|
||||||
|
return sslSocketFactory.createSocket(host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createSocket(
|
||||||
|
host: String,
|
||||||
|
port: Int,
|
||||||
|
localHost: InetAddress,
|
||||||
|
localPort: Int
|
||||||
|
): Socket {
|
||||||
|
return sslSocketFactory.createSocket(host, port, localHost, localPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createSocket(host: InetAddress, port: Int): Socket {
|
||||||
|
return sslSocketFactory.createSocket(host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createSocket(
|
||||||
|
address: InetAddress,
|
||||||
|
port: Int,
|
||||||
|
localAddress: InetAddress,
|
||||||
|
localPort: Int
|
||||||
|
): Socket {
|
||||||
|
return sslSocketFactory.createSocket(address, port, localAddress, localPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -260,7 +260,7 @@ public class EhTagDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OkHttpClient client = EhApplication.getOkHttpClient(context);
|
OkHttpClient client = EhApplication.getOkHttpClient();
|
||||||
|
|
||||||
// Save new sha1
|
// Save new sha1
|
||||||
File tempSha1File = new File(dir, sha1Name + ".tmp");
|
File tempSha1File = new File(dir, sha1Name + ".tmp");
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ import androidx.annotation.NonNull;
|
|||||||
import com.hippo.ehviewer.Settings;
|
import com.hippo.ehviewer.Settings;
|
||||||
import com.hippo.network.UrlBuilder;
|
import com.hippo.network.UrlBuilder;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ListIterator;
|
|
||||||
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
|
|
||||||
public class EhUrl {
|
public class EhUrl {
|
||||||
|
|
||||||
public static final int SITE_E = 0;
|
public static final int SITE_E = 0;
|
||||||
@@ -59,8 +54,6 @@ public class EhUrl {
|
|||||||
public static final String ORIGIN_EX = REFERER_EX;
|
public static final String ORIGIN_EX = REFERER_EX;
|
||||||
public static final String REFERER_E = "https://" + DOMAIN_E;
|
public static final String REFERER_E = "https://" + DOMAIN_E;
|
||||||
public static final String ORIGIN_E = REFERER_E;
|
public static final String ORIGIN_E = REFERER_E;
|
||||||
private static final String URL_PREFIX_THUMB_E = "https://ehgt.org/";
|
|
||||||
private static final String URL_PREFIX_THUMB_EX = "https://exhentai.org/t/";
|
|
||||||
|
|
||||||
public static String getGalleryDetailUrl(long gid, String token) {
|
public static String getGalleryDetailUrl(long gid, String token) {
|
||||||
return getGalleryDetailUrl(gid, token, 0, false);
|
return getGalleryDetailUrl(gid, token, 0, false);
|
||||||
@@ -200,44 +193,4 @@ public class EhUrl {
|
|||||||
return URL_WATCHED_EX;
|
return URL_WATCHED_EX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getThumbUrlPrefix() {
|
|
||||||
switch (Settings.getGallerySite()) {
|
|
||||||
default:
|
|
||||||
//case SITE_E:
|
|
||||||
return URL_PREFIX_THUMB_E;
|
|
||||||
//case SITE_EX:
|
|
||||||
// return URL_PREFIX_THUMB_EX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getFixedPreviewThumbUrl(String originUrl) {
|
|
||||||
HttpUrl url = HttpUrl.parse(originUrl);
|
|
||||||
if (url == null) return originUrl;
|
|
||||||
List<String> pathSegments = url.pathSegments();
|
|
||||||
if (pathSegments.size() < 3) return originUrl;
|
|
||||||
|
|
||||||
ListIterator<String> iterator = pathSegments.listIterator(pathSegments.size());
|
|
||||||
// The last segments, like
|
|
||||||
// 317a1a254cd9c3269e71b2aa2671fe8d28c91097-260198-640-480-png_250.jpg
|
|
||||||
if (!iterator.hasPrevious()) return originUrl;
|
|
||||||
String lastSegment = iterator.previous();
|
|
||||||
// The second last segments, like
|
|
||||||
// 7a
|
|
||||||
if (!iterator.hasPrevious()) return originUrl;
|
|
||||||
String secondLastSegment = iterator.previous();
|
|
||||||
// The third last segments, like
|
|
||||||
// 31
|
|
||||||
if (!iterator.hasPrevious()) return originUrl;
|
|
||||||
String thirdLastSegment = iterator.previous();
|
|
||||||
// Check path segments
|
|
||||||
if (lastSegment != null && secondLastSegment != null
|
|
||||||
&& thirdLastSegment != null
|
|
||||||
&& lastSegment.startsWith(thirdLastSegment)
|
|
||||||
&& lastSegment.startsWith(secondLastSegment, thirdLastSegment.length())) {
|
|
||||||
return getThumbUrlPrefix() + thirdLastSegment + "/" + secondLastSegment + "/" + lastSegment;
|
|
||||||
} else {
|
|
||||||
return originUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package com.hippo.ehviewer.client;
|
package com.hippo.ehviewer.client;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.hippo.ehviewer.EhApplication;
|
import com.hippo.ehviewer.EhApplication;
|
||||||
@@ -31,10 +31,8 @@ import java.util.regex.Pattern;
|
|||||||
public class EhUtils {
|
public class EhUtils {
|
||||||
|
|
||||||
public static final int NONE = -1; // Use it for homepage
|
public static final int NONE = -1; // Use it for homepage
|
||||||
public static final int UNKNOWN = 0x400;
|
public static final int PRIVATE = 0x400;
|
||||||
|
public static final int UNKNOWN = 0x800;
|
||||||
public static final int ALL_CATEGORY = EhUtils.UNKNOWN - 1;
|
|
||||||
//DOUJINSHI|MANGA|ARTIST_CG|GAME_CG|WESTERN|NON_H|IMAGE_SET|COSPLAY|ASIAN_PORN|MISC;
|
|
||||||
|
|
||||||
public static final int BG_COLOR_DOUJINSHI = 0xfff44336;
|
public static final int BG_COLOR_DOUJINSHI = 0xfff44336;
|
||||||
public static final int BG_COLOR_MANGA = 0xffff9800;
|
public static final int BG_COLOR_MANGA = 0xffff9800;
|
||||||
@@ -50,10 +48,10 @@ public class EhUtils {
|
|||||||
|
|
||||||
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
|
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
|
||||||
public static final Pattern PATTERN_TITLE_PREFIX = Pattern.compile(
|
public static final Pattern PATTERN_TITLE_PREFIX = Pattern.compile(
|
||||||
"^(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*");
|
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*");
|
||||||
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
|
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
|
||||||
public static final Pattern PATTERN_TITLE_SUFFIX = Pattern.compile(
|
public static final Pattern PATTERN_TITLE_SUFFIX = Pattern.compile(
|
||||||
"(?:\\s+ch.[\\s\\d-]+)?(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*$",
|
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
|
||||||
Pattern.CASE_INSENSITIVE);
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
private static final int[] CATEGORY_VALUES = {
|
private static final int[] CATEGORY_VALUES = {
|
||||||
@@ -67,6 +65,7 @@ public class EhUtils {
|
|||||||
EhConfig.ASIAN_PORN,
|
EhConfig.ASIAN_PORN,
|
||||||
EhConfig.NON_H,
|
EhConfig.NON_H,
|
||||||
EhConfig.WESTERN,
|
EhConfig.WESTERN,
|
||||||
|
PRIVATE,
|
||||||
UNKNOWN};
|
UNKNOWN};
|
||||||
|
|
||||||
private static final String[][] CATEGORY_STRINGS = {
|
private static final String[][] CATEGORY_STRINGS = {
|
||||||
@@ -80,6 +79,7 @@ public class EhUtils {
|
|||||||
new String[]{"asianporn", "Asian Porn"},
|
new String[]{"asianporn", "Asian Porn"},
|
||||||
new String[]{"non-h"},
|
new String[]{"non-h"},
|
||||||
new String[]{"western"},
|
new String[]{"western"},
|
||||||
|
new String[]{"private"},
|
||||||
new String[]{"unknown"}
|
new String[]{"unknown"}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,6 +94,7 @@ public class EhUtils {
|
|||||||
return CATEGORY_VALUES[i];
|
return CATEGORY_VALUES[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public static String getCategory(int type) {
|
public static String getCategory(int type) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < CATEGORY_VALUES.length - 1; i++) {
|
for (i = 0; i < CATEGORY_VALUES.length - 1; i++) {
|
||||||
@@ -104,41 +105,30 @@ public class EhUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getCategoryColor(int category) {
|
public static int getCategoryColor(int category) {
|
||||||
switch (category) {
|
return switch (category) {
|
||||||
case EhConfig.DOUJINSHI:
|
case EhConfig.DOUJINSHI -> BG_COLOR_DOUJINSHI;
|
||||||
return BG_COLOR_DOUJINSHI;
|
case EhConfig.MANGA -> BG_COLOR_MANGA;
|
||||||
case EhConfig.MANGA:
|
case EhConfig.ARTIST_CG -> BG_COLOR_ARTIST_CG;
|
||||||
return BG_COLOR_MANGA;
|
case EhConfig.GAME_CG -> BG_COLOR_GAME_CG;
|
||||||
case EhConfig.ARTIST_CG:
|
case EhConfig.WESTERN -> BG_COLOR_WESTERN;
|
||||||
return BG_COLOR_ARTIST_CG;
|
case EhConfig.NON_H -> BG_COLOR_NON_H;
|
||||||
case EhConfig.GAME_CG:
|
case EhConfig.IMAGE_SET -> BG_COLOR_IMAGE_SET;
|
||||||
return BG_COLOR_GAME_CG;
|
case EhConfig.COSPLAY -> BG_COLOR_COSPLAY;
|
||||||
case EhConfig.WESTERN:
|
case EhConfig.ASIAN_PORN -> BG_COLOR_ASIAN_PORN;
|
||||||
return BG_COLOR_WESTERN;
|
case EhConfig.MISC -> BG_COLOR_MISC;
|
||||||
case EhConfig.NON_H:
|
default -> BG_COLOR_UNKNOWN;
|
||||||
return BG_COLOR_NON_H;
|
};
|
||||||
case EhConfig.IMAGE_SET:
|
|
||||||
return BG_COLOR_IMAGE_SET;
|
|
||||||
case EhConfig.COSPLAY:
|
|
||||||
return BG_COLOR_COSPLAY;
|
|
||||||
case EhConfig.ASIAN_PORN:
|
|
||||||
return BG_COLOR_ASIAN_PORN;
|
|
||||||
case EhConfig.MISC:
|
|
||||||
return BG_COLOR_MISC;
|
|
||||||
default:
|
|
||||||
return BG_COLOR_UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void signOut(Context context) {
|
public static void signOut() {
|
||||||
EhApplication.getEhCookieStore(context).signOut();
|
EhApplication.getEhCookieStore().signOut();
|
||||||
Settings.putAvatar(null);
|
Settings.putAvatar(null);
|
||||||
Settings.putDisplayName(null);
|
Settings.putDisplayName(null);
|
||||||
Settings.putNeedSignIn(true);
|
Settings.putNeedSignIn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean needSignedIn(Context context) {
|
public static boolean needSignedIn() {
|
||||||
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore(context).hasSignedIn();
|
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore().hasSignedIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSuitableTitle(GalleryInfo gi) {
|
public static String getSuitableTitle(GalleryInfo gi) {
|
||||||
|
|||||||
+10
-17
@@ -15,30 +15,23 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
package com.hippo.ehviewer.client
|
||||||
|
|
||||||
package com.hippo.ehviewer.client;
|
import android.annotation.SuppressLint
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
@SuppressLint("CustomX509TrustManager")
|
||||||
|
class EhX509TrustManager : X509TrustManager {
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
|
|
||||||
public class EhX509TrustManager implements X509TrustManager {
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
@Override
|
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
@Override
|
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun getAcceptedIssuers(): Array<X509Certificate?> {
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
return arrayOfNulls(0)
|
||||||
return new X509Certificate[0];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,16 +16,17 @@
|
|||||||
|
|
||||||
package com.hippo.ehviewer.client.data;
|
package com.hippo.ehviewer.client.data;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.client.EhUrl;
|
import com.hippo.ehviewer.client.EhUrl;
|
||||||
import com.hippo.network.UrlBuilder;
|
import com.hippo.network.UrlBuilder;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class FavListUrlBuilder implements Parcelable {
|
public class FavListUrlBuilder implements Parcelable {
|
||||||
|
|
||||||
@@ -44,7 +45,9 @@ public class FavListUrlBuilder implements Parcelable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
|
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
|
||||||
private int mIndex;
|
private String mPrev;
|
||||||
|
private String mNext;
|
||||||
|
private String mJumpTo;
|
||||||
private String mKeyword;
|
private String mKeyword;
|
||||||
private int mFavCat = FAV_CAT_ALL;
|
private int mFavCat = FAV_CAT_ALL;
|
||||||
|
|
||||||
@@ -52,7 +55,9 @@ public class FavListUrlBuilder implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected FavListUrlBuilder(Parcel in) {
|
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.mKeyword = in.readString();
|
||||||
this.mFavCat = in.readInt();
|
this.mFavCat = in.readInt();
|
||||||
}
|
}
|
||||||
@@ -61,8 +66,16 @@ public class FavListUrlBuilder implements Parcelable {
|
|||||||
return favCat >= 0 && favCat <= 9;
|
return favCat >= 0 && favCat <= 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIndex(int index) {
|
public void setPrev(String prev) {
|
||||||
mIndex = index;
|
mPrev = prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNext(String next) {
|
||||||
|
mNext = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJumpTo(String jumpTo) {
|
||||||
|
mJumpTo = jumpTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getKeyword() {
|
public String getKeyword() {
|
||||||
@@ -93,20 +106,30 @@ public class FavListUrlBuilder implements Parcelable {
|
|||||||
ub.addQuery("favcat", "all");
|
ub.addQuery("favcat", "all");
|
||||||
}
|
}
|
||||||
if (!TextUtils.isEmpty(mKeyword)) {
|
if (!TextUtils.isEmpty(mKeyword)) {
|
||||||
try {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"));
|
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||||
// Name
|
} else {
|
||||||
ub.addQuery("sn", "on");
|
try {
|
||||||
// Tags
|
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8.displayName()));
|
||||||
ub.addQuery("st", "on");
|
} catch (UnsupportedEncodingException e) {
|
||||||
// Note
|
e.printStackTrace();
|
||||||
ub.addQuery("sf", "on");
|
}
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
Log.e(TAG, "Can't URLEncoder.encode " + mKeyword);
|
|
||||||
}
|
}
|
||||||
|
// Name
|
||||||
|
ub.addQuery("sn", "on");
|
||||||
|
// Tags
|
||||||
|
ub.addQuery("st", "on");
|
||||||
|
// Note
|
||||||
|
ub.addQuery("sf", "on");
|
||||||
}
|
}
|
||||||
if (mIndex > 0) {
|
if (mPrev != null && !mPrev.isEmpty()) {
|
||||||
ub.addQuery("page", Integer.toString(mIndex));
|
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();
|
return ub.build();
|
||||||
}
|
}
|
||||||
@@ -118,7 +141,9 @@ public class FavListUrlBuilder implements Parcelable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
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.writeString(this.mKeyword);
|
||||||
dest.writeInt(this.mFavCat);
|
dest.writeInt(this.mFavCat);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,636 +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.io.UnsupportedEncodingException;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
|
|
||||||
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, "utf-8");
|
|
||||||
} catch (UnsupportedEncodingException | 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()) {
|
|
||||||
try {
|
|
||||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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/");
|
|
||||||
try {
|
|
||||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
if (mPageIndex != 0) {
|
|
||||||
sb.append('/').append(mPageIndex);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
case MODE_TAG: {
|
|
||||||
StringBuilder sb = new StringBuilder(EhUrl.getHost());
|
|
||||||
sb.append("tag/");
|
|
||||||
try {
|
|
||||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
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=");
|
|
||||||
try {
|
|
||||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
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,547 @@
|
|||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
if (mJumpTo != null) {
|
||||||
|
sb.append("&seek=").append(mJumpTo)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if (mJumpTo != null) {
|
||||||
|
sb.append("&seek=").append(mJumpTo)
|
||||||
|
}
|
||||||
|
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 org.jsoup.select.Elements;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class FavoritesParser {
|
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 {
|
public static Result parse(String body) throws Exception {
|
||||||
if (body.contains("This page requires you to log on.</p>")) {
|
if (body.contains("This page requires you to log on.</p>")) {
|
||||||
throw new EhException(GetText.getString(R.string.need_sign_in));
|
throw new EhException(GetText.getString(R.string.need_sign_in));
|
||||||
}
|
}
|
||||||
|
Document d = Jsoup.parse(body);
|
||||||
|
|
||||||
String[] catArray = new String[10];
|
String[] catArray = new String[10];
|
||||||
int[] countArray = new int[10];
|
int[] countArray = new int[10];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Document d = Jsoup.parse(body);
|
|
||||||
Element ido = JsoupUtils.getElementByClass(d, "ido");
|
Element ido = JsoupUtils.getElementByClass(d, "ido");
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
Elements fps = ido.getElementsByClass("fp");
|
Elements fps = ido.getElementsByClass("fp");
|
||||||
@@ -60,13 +64,24 @@ public class FavoritesParser {
|
|||||||
throw new ParseException("Parse favorites error", body);
|
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);
|
GalleryListParser.Result result = GalleryListParser.parse(body);
|
||||||
|
|
||||||
Result re = new Result();
|
|
||||||
re.catArray = catArray;
|
re.catArray = catArray;
|
||||||
re.countArray = countArray;
|
re.countArray = countArray;
|
||||||
re.pages = result.pages;
|
|
||||||
re.nextPage = result.nextPage;
|
|
||||||
re.galleryInfoList = result.galleryInfoList;
|
re.galleryInfoList = result.galleryInfoList;
|
||||||
|
|
||||||
return re;
|
return re;
|
||||||
@@ -75,8 +90,8 @@ public class FavoritesParser {
|
|||||||
public static class Result {
|
public static class Result {
|
||||||
public String[] catArray; // Size 10
|
public String[] catArray; // Size 10
|
||||||
public int[] countArray; // Size 10
|
public int[] countArray; // Size 10
|
||||||
public int pages;
|
public String prevPage;
|
||||||
public int nextPage;
|
public String nextPage;
|
||||||
public List<GalleryInfo> galleryInfoList;
|
public List<GalleryInfo> galleryInfoList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,7 @@ package com.hippo.ehviewer.client.parser;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.hippo.ehviewer.Settings;
|
import com.hippo.ehviewer.EhDB;
|
||||||
import com.hippo.ehviewer.client.EhUrl;
|
|
||||||
import com.hippo.ehviewer.client.EhUtils;
|
import com.hippo.ehviewer.client.EhUtils;
|
||||||
import com.hippo.ehviewer.client.data.GalleryComment;
|
import com.hippo.ehviewer.client.data.GalleryComment;
|
||||||
import com.hippo.ehviewer.client.data.GalleryCommentList;
|
import com.hippo.ehviewer.client.data.GalleryCommentList;
|
||||||
@@ -48,14 +47,12 @@ import org.jsoup.select.Elements;
|
|||||||
import org.jsoup.select.NodeTraversor;
|
import org.jsoup.select.NodeTraversor;
|
||||||
import org.jsoup.select.NodeVisitor;
|
import org.jsoup.select.NodeVisitor;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.time.Instant;
|
||||||
import java.text.SimpleDateFormat;
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -66,27 +63,39 @@ public class GalleryDetailParser {
|
|||||||
private static final Pattern PATTERN_TORRENT = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Torrent Download[^<]+(\\d+)[^<]+</a");
|
private static final Pattern PATTERN_TORRENT = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Torrent Download[^<]+(\\d+)[^<]+</a");
|
||||||
private static final Pattern PATTERN_ARCHIVE = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Archive Download</a>");
|
private static final Pattern PATTERN_ARCHIVE = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Archive Download</a>");
|
||||||
private static final Pattern PATTERN_COVER = Pattern.compile("width:(\\d+)px; height:(\\d+)px.+?url\\((.+?)\\)");
|
private static final Pattern PATTERN_COVER = Pattern.compile("width:(\\d+)px; height:(\\d+)px.+?url\\((.+?)\\)");
|
||||||
private static final Pattern PATTERN_TAG_GROUP = Pattern.compile("<tr><td[^<>]+>([\\w\\s]+):</td><td>(?:<div[^<>]+><a[^<>]+>[\\w\\s]+</a></div>)+</td></tr>");
|
|
||||||
private static final Pattern PATTERN_TAG = Pattern.compile("<div[^<>]+><a[^<>]+>([\\w\\s]+)</a></div>");
|
|
||||||
private static final Pattern PATTERN_COMMENT = Pattern.compile("<div class=\"c3\">Posted on ([^<>]+) by: <a[^<>]+>([^<>]+)</a>.+?<div class=\"c6\"[^>]*>(.+?)</div><div class=\"c[78]\"");
|
|
||||||
private static final Pattern PATTERN_PAGES = Pattern.compile("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>");
|
private static final Pattern PATTERN_PAGES = Pattern.compile("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>");
|
||||||
private static final Pattern PATTERN_PREVIEW_PAGES = Pattern.compile("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?>(?:</a>)?</td>");
|
private static final Pattern PATTERN_PREVIEW_PAGES = Pattern.compile("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?>(?:</a>)?</td>");
|
||||||
private static final Pattern PATTERN_NORMAL_PREVIEW = Pattern.compile("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"");
|
private static final Pattern PATTERN_NORMAL_PREVIEW = Pattern.compile("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"");
|
||||||
private static final Pattern PATTERN_LARGE_PREVIEW = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"");
|
private static final Pattern PATTERN_LARGE_PREVIEW = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"");
|
||||||
private static final Pattern PATTERN_NEWER_DATE = Pattern.compile(", added (.+?)<br />");
|
private static final Pattern PATTERN_NEWER_DATE = Pattern.compile(", added (.+?)<br />");
|
||||||
|
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("/fav.png\\); background-position:0px -(\\d+)px");
|
||||||
|
|
||||||
private static final GalleryTagGroup[] EMPTY_GALLERY_TAG_GROUP_ARRAY = new GalleryTagGroup[0];
|
private static final GalleryTagGroup[] EMPTY_GALLERY_TAG_GROUP_ARRAY = new GalleryTagGroup[0];
|
||||||
private static final GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
|
private static final GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
|
||||||
|
|
||||||
private static final DateFormat WEB_COMMENT_DATE_FORMAT = new SimpleDateFormat("dd MMMMM yyyy, HH:mm", Locale.US);
|
private static final DateTimeFormatter WEB_COMMENT_DATE_FORMAT = DateTimeFormatter
|
||||||
|
.ofPattern("dd MMMM yyyy, HH:mm", Locale.US).withZone(ZoneOffset.UTC);
|
||||||
private static final String OFFENSIVE_STRING =
|
private static final String OFFENSIVE_STRING =
|
||||||
"<p>(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)</p>";
|
"<p>(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)</p>";
|
||||||
private static final String PINING_STRING =
|
private static final String PINING_STRING =
|
||||||
"<p>This gallery is pining for the fjords.</p>";
|
"<p>This gallery is pining for the fjords.</p>";
|
||||||
|
|
||||||
static {
|
private static final String[] LANGUAGES = {
|
||||||
WEB_COMMENT_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
"English",
|
||||||
}
|
"Chinese",
|
||||||
|
"Spanish",
|
||||||
|
"Korean",
|
||||||
|
"Russian",
|
||||||
|
"French",
|
||||||
|
"Portuguese",
|
||||||
|
"Thai",
|
||||||
|
"German",
|
||||||
|
"Italian",
|
||||||
|
"Vietnamese",
|
||||||
|
"Polish",
|
||||||
|
"Hungarian",
|
||||||
|
"Dutch",
|
||||||
|
};
|
||||||
|
|
||||||
public static GalleryDetail parse(String body) throws EhException {
|
public static GalleryDetail parse(String body) throws EhException {
|
||||||
if (body.contains(OFFENSIVE_STRING)) {
|
if (body.contains(OFFENSIVE_STRING)) {
|
||||||
@@ -110,6 +119,15 @@ public class GalleryDetailParser {
|
|||||||
galleryDetail.comments = parseComments(document);
|
galleryDetail.comments = parseComments(document);
|
||||||
galleryDetail.previewPages = parsePreviewPages(document, body);
|
galleryDetail.previewPages = parsePreviewPages(document, body);
|
||||||
galleryDetail.previewSet = parsePreviewSet(document, body);
|
galleryDetail.previewSet = parsePreviewSet(document, body);
|
||||||
|
|
||||||
|
// Generate simpleLanguage for local favorites
|
||||||
|
for (int i = 0; i < LANGUAGES.length; i++) {
|
||||||
|
if (galleryDetail.language.equals(LANGUAGES[i])) {
|
||||||
|
galleryDetail.simpleLanguage = GalleryInfo.S_LANGS[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return galleryDetail;
|
return galleryDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +206,7 @@ public class GalleryDetailParser {
|
|||||||
// Uploader
|
// Uploader
|
||||||
Element gdn = gm.getElementById("gdn");
|
Element gdn = gm.getElementById("gdn");
|
||||||
if (null != gdn) {
|
if (null != gdn) {
|
||||||
gd.disowned = "opacity:0.5".equals(gdn.attr("style"));
|
gd.disowned = gdn.attr("style").contains("opacity:0.5");
|
||||||
gd.uploader = StringUtils.trim(gdn.text());
|
gd.uploader = StringUtils.trim(gdn.text());
|
||||||
} else {
|
} else {
|
||||||
gd.uploader = "";
|
gd.uploader = "";
|
||||||
@@ -198,18 +216,12 @@ public class GalleryDetailParser {
|
|||||||
gd.posted = "";
|
gd.posted = "";
|
||||||
gd.parent = "";
|
gd.parent = "";
|
||||||
gd.visible = "";
|
gd.visible = "";
|
||||||
gd.visible = "";
|
|
||||||
gd.size = "";
|
gd.size = "";
|
||||||
gd.pages = 0;
|
gd.pages = 0;
|
||||||
gd.favoriteCount = 0;
|
gd.favoriteCount = 0;
|
||||||
try {
|
Elements es = gdd.child(0).child(0).children();
|
||||||
Elements es = gdd.child(0).child(0).children();
|
for (int i = 0, n = es.size(); i < n; i++) {
|
||||||
for (int i = 0, n = es.size(); i < n; i++) {
|
parseDetailInfo(gd, es.get(i));
|
||||||
parseDetailInfo(gd, es.get(i), body);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
ExceptionUtils.throwIfFatal(e);
|
|
||||||
// Ignore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rating count
|
// Rating count
|
||||||
@@ -241,15 +253,24 @@ public class GalleryDetailParser {
|
|||||||
|
|
||||||
// isFavorited
|
// isFavorited
|
||||||
Element gdf = gm.getElementById("gdf");
|
Element gdf = gm.getElementById("gdf");
|
||||||
gd.isFavorited = null != gdf && !StringUtils.trim(gdf.text()).equals("Add to Favorites");
|
gd.isFavorited = false;
|
||||||
if (gdf != null) {
|
if (gdf != null) {
|
||||||
final String favoriteName = StringUtils.trim(gdf.text());
|
final String favoriteName = StringUtils.trim(gdf.text());
|
||||||
if (favoriteName.equals("Add to Favorites")) {
|
if (favoriteName.equals("Add to Favorites")) {
|
||||||
gd.favoriteName = null;
|
gd.favoriteName = null;
|
||||||
} else {
|
} else {
|
||||||
|
gd.isFavorited = true;
|
||||||
gd.favoriteName = StringUtils.trim(gdf.text());
|
gd.favoriteName = StringUtils.trim(gdf.text());
|
||||||
|
|
||||||
|
matcher = PATTERN_FAVORITE_SLOT.matcher(body);
|
||||||
|
if (matcher.find()) {
|
||||||
|
gd.favoriteSlot = (NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (gd.favoriteSlot == -2 && EhDB.containLocalFavorites(gd.gid)) {
|
||||||
|
gd.favoriteSlot = -1;
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
ExceptionUtils.throwIfFatal(e);
|
ExceptionUtils.throwIfFatal(e);
|
||||||
throw new ParseException("Can't parse gallery detail", body);
|
throw new ParseException("Can't parse gallery detail", body);
|
||||||
@@ -293,7 +314,7 @@ public class GalleryDetailParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseDetailInfo(GalleryDetail gd, Element e, String body) {
|
private static void parseDetailInfo(GalleryDetail gd, Element e) {
|
||||||
Elements es = e.children();
|
Elements es = e.children();
|
||||||
if (es.size() < 2) {
|
if (es.size() < 2) {
|
||||||
return;
|
return;
|
||||||
@@ -323,20 +344,16 @@ public class GalleryDetailParser {
|
|||||||
}
|
}
|
||||||
} else if (key.startsWith("Favorited")) {
|
} else if (key.startsWith("Favorited")) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case "Never":
|
case "Never" -> gd.favoriteCount = 0;
|
||||||
gd.favoriteCount = 0;
|
case "Once" -> gd.favoriteCount = 1;
|
||||||
break;
|
default -> {
|
||||||
case "Once":
|
|
||||||
gd.favoriteCount = 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
int index = value.indexOf(' ');
|
int index = value.indexOf(' ');
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
gd.favoriteCount = 0;
|
gd.favoriteCount = 0;
|
||||||
} else {
|
} else {
|
||||||
gd.favoriteCount = NumberUtils.parseIntSafely(value.substring(0, index), 0);
|
gd.favoriteCount = NumberUtils.parseIntSafely(value.substring(0, index), 0);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,6 +394,7 @@ public class GalleryDetailParser {
|
|||||||
public static GalleryTagGroup[] parseTagGroups(Document document) {
|
public static GalleryTagGroup[] parseTagGroups(Document document) {
|
||||||
try {
|
try {
|
||||||
Element taglist = document.getElementById("taglist");
|
Element taglist = document.getElementById("taglist");
|
||||||
|
assert taglist != null;
|
||||||
Elements tagGroups = taglist.child(0).child(0).children();
|
Elements tagGroups = taglist.child(0).child(0).children();
|
||||||
return parseTagGroups(tagGroups);
|
return parseTagGroups(tagGroups);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@@ -396,7 +414,7 @@ public class GalleryDetailParser {
|
|||||||
list.add(group);
|
list.add(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list.toArray(new GalleryTagGroup[list.size()]);
|
return list.toArray(new GalleryTagGroup[0]);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
ExceptionUtils.throwIfFatal(e);
|
ExceptionUtils.throwIfFatal(e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -404,31 +422,6 @@ public class GalleryDetailParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse tag groups with regular expressions
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
private static GalleryTagGroup[] parseTagGroups(String body) throws EhException {
|
|
||||||
List<GalleryTagGroup> list = new LinkedList<>();
|
|
||||||
|
|
||||||
Matcher m = PATTERN_TAG_GROUP.matcher(body);
|
|
||||||
while (m.find()) {
|
|
||||||
GalleryTagGroup tagGroup = new GalleryTagGroup();
|
|
||||||
tagGroup.groupName = ParserUtils.trim(m.group(1));
|
|
||||||
parseGroup(tagGroup, m.group(0));
|
|
||||||
list.add(tagGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.toArray(new GalleryTagGroup[list.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void parseGroup(GalleryTagGroup tagGroup, String body) {
|
|
||||||
Matcher m = PATTERN_TAG.matcher(body);
|
|
||||||
while (m.find()) {
|
|
||||||
tagGroup.addTag(ParserUtils.trim(m.group(1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
public static GalleryComment parseComment(Element element) {
|
public static GalleryComment parseComment(Element element) {
|
||||||
@@ -446,17 +439,15 @@ public class GalleryDetailParser {
|
|||||||
}
|
}
|
||||||
for (Element e : c4.children()) {
|
for (Element e : c4.children()) {
|
||||||
switch (e.text()) {
|
switch (e.text()) {
|
||||||
case "Vote+":
|
case "Vote+" -> {
|
||||||
comment.voteUpAble = true;
|
comment.voteUpAble = true;
|
||||||
comment.voteUpEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
comment.voteUpEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
||||||
break;
|
}
|
||||||
case "Vote-":
|
case "Vote-" -> {
|
||||||
comment.voteDownAble = true;
|
comment.voteDownAble = true;
|
||||||
comment.voteDownEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
comment.voteDownEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
||||||
break;
|
}
|
||||||
case "Edit":
|
case "Edit" -> comment.editable = true;
|
||||||
comment.editable = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,7 +468,7 @@ public class GalleryDetailParser {
|
|||||||
Element c3 = JsoupUtils.getElementByClass(element, "c3");
|
Element c3 = JsoupUtils.getElementByClass(element, "c3");
|
||||||
String temp = c3.ownText();
|
String temp = c3.ownText();
|
||||||
temp = temp.substring("Posted on ".length(), temp.length() - " by:".length());
|
temp = temp.substring("Posted on ".length(), temp.length() - " by:".length());
|
||||||
comment.time = WEB_COMMENT_DATE_FORMAT.parse(temp).getTime();
|
comment.time = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli();
|
||||||
// user
|
// user
|
||||||
comment.user = c3.child(0).text();
|
comment.user = c3.child(0).text();
|
||||||
// comment
|
// comment
|
||||||
@@ -487,7 +478,7 @@ public class GalleryDetailParser {
|
|||||||
if (c8 != null) {
|
if (c8 != null) {
|
||||||
Element e = c8.children().first();
|
Element e = c8.children().first();
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
comment.lastEdited = WEB_COMMENT_DATE_FORMAT.parse(temp).getTime();
|
comment.lastEdited = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return comment;
|
return comment;
|
||||||
@@ -505,6 +496,7 @@ public class GalleryDetailParser {
|
|||||||
public static GalleryCommentList parseComments(Document document) {
|
public static GalleryCommentList parseComments(Document document) {
|
||||||
try {
|
try {
|
||||||
Element cdiv = document.getElementById("cdiv");
|
Element cdiv = document.getElementById("cdiv");
|
||||||
|
assert cdiv != null;
|
||||||
Elements c1s = cdiv.getElementsByClass("c1");
|
Elements c1s = cdiv.getElementsByClass("c1");
|
||||||
|
|
||||||
List<GalleryComment> list = new ArrayList<>(c1s.size());
|
List<GalleryComment> list = new ArrayList<>(c1s.size());
|
||||||
@@ -517,20 +509,21 @@ public class GalleryDetailParser {
|
|||||||
|
|
||||||
Element chd = cdiv.getElementById("chd");
|
Element chd = cdiv.getElementById("chd");
|
||||||
MutableBoolean hasMore = new MutableBoolean(false);
|
MutableBoolean hasMore = new MutableBoolean(false);
|
||||||
|
assert chd != null;
|
||||||
NodeTraversor.traverse(new NodeVisitor() {
|
NodeTraversor.traverse(new NodeVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public void head(Node node, int depth) {
|
public void head(@NonNull Node node, int depth) {
|
||||||
if (node instanceof Element && ((Element) node).text().equals("click to show all")) {
|
if (node instanceof Element && ((Element) node).text().equals("click to show all")) {
|
||||||
hasMore.value = true;
|
hasMore.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tail(Node node, int depth) {
|
public void tail(@NonNull Node node, int depth) {
|
||||||
}
|
}
|
||||||
}, chd);
|
}, chd);
|
||||||
|
|
||||||
return new GalleryCommentList(list.toArray(new GalleryComment[list.size()]), hasMore.value);
|
return new GalleryCommentList(list.toArray(new GalleryComment[0]), hasMore.value);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
ExceptionUtils.throwIfFatal(e);
|
ExceptionUtils.throwIfFatal(e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -538,38 +531,14 @@ public class GalleryDetailParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse comments with regular expressions
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static GalleryComment[] parseComments(String body) {
|
|
||||||
List<GalleryComment> list = new LinkedList<>();
|
|
||||||
|
|
||||||
Matcher m = PATTERN_COMMENT.matcher(body);
|
|
||||||
while (m.find()) {
|
|
||||||
String webDateString = ParserUtils.trim(m.group(1));
|
|
||||||
Date date;
|
|
||||||
try {
|
|
||||||
date = WEB_COMMENT_DATE_FORMAT.parse(webDateString);
|
|
||||||
} catch (java.text.ParseException e) {
|
|
||||||
date = new Date(0L);
|
|
||||||
}
|
|
||||||
GalleryComment comment = new GalleryComment();
|
|
||||||
comment.time = date.getTime();
|
|
||||||
comment.user = ParserUtils.trim(m.group(2));
|
|
||||||
comment.comment = m.group(3);
|
|
||||||
list.add(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.toArray(new GalleryComment[list.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse preview pages with html parser
|
* Parse preview pages with html parser
|
||||||
*/
|
*/
|
||||||
public static int parsePreviewPages(Document document, String body) throws ParseException {
|
public static int parsePreviewPages(Document document, String body) throws ParseException {
|
||||||
try {
|
try {
|
||||||
Elements elements = document.getElementsByClass("ptt").first().child(0).child(0).children();
|
Element ptt = document.getElementsByClass("ptt").first();
|
||||||
|
assert ptt != null;
|
||||||
|
Elements elements = ptt.child(0).child(0).children();
|
||||||
return Integer.parseInt(elements.get(elements.size() - 2).text());
|
return Integer.parseInt(elements.get(elements.size() - 2).text());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
ExceptionUtils.throwIfFatal(e);
|
ExceptionUtils.throwIfFatal(e);
|
||||||
@@ -636,9 +605,10 @@ public class GalleryDetailParser {
|
|||||||
try {
|
try {
|
||||||
LargePreviewSet largePreviewSet = new LargePreviewSet();
|
LargePreviewSet largePreviewSet = new LargePreviewSet();
|
||||||
Element gdt = d.getElementById("gdt");
|
Element gdt = d.getElementById("gdt");
|
||||||
|
assert gdt != null;
|
||||||
Elements gdtls = gdt.getElementsByClass("gdtl");
|
Elements gdtls = gdt.getElementsByClass("gdtl");
|
||||||
int n = gdtls.size();
|
int n = gdtls.size();
|
||||||
if (n <= 0) {
|
if (n == 0) {
|
||||||
throw new ParseException("Can't parse large preview", body);
|
throw new ParseException("Can't parse large preview", body);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
@@ -646,9 +616,6 @@ public class GalleryDetailParser {
|
|||||||
String pageUrl = element.attr("href");
|
String pageUrl = element.attr("href");
|
||||||
element = element.child(0);
|
element = element.child(0);
|
||||||
String imageUrl = element.attr("src");
|
String imageUrl = element.attr("src");
|
||||||
if (Settings.getFixThumbUrl()) {
|
|
||||||
imageUrl = EhUrl.getFixedPreviewThumbUrl(imageUrl);
|
|
||||||
}
|
|
||||||
int index = Integer.parseInt(element.attr("alt")) - 1;
|
int index = Integer.parseInt(element.attr("alt")) - 1;
|
||||||
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
||||||
}
|
}
|
||||||
@@ -674,9 +641,6 @@ public class GalleryDetailParser {
|
|||||||
}
|
}
|
||||||
String imageUrl = ParserUtils.trim(m.group(3));
|
String imageUrl = ParserUtils.trim(m.group(3));
|
||||||
String pageUrl = ParserUtils.trim(m.group(1));
|
String pageUrl = ParserUtils.trim(m.group(1));
|
||||||
if (Settings.getFixThumbUrl()) {
|
|
||||||
imageUrl = EhUrl.getFixedPreviewThumbUrl(imageUrl);
|
|
||||||
}
|
|
||||||
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import androidx.annotation.NonNull;
|
|||||||
import com.hippo.ehviewer.EhDB;
|
import com.hippo.ehviewer.EhDB;
|
||||||
import com.hippo.ehviewer.client.EhUtils;
|
import com.hippo.ehviewer.client.EhUtils;
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||||
import com.hippo.ehviewer.client.data.GalleryTagGroup;
|
|
||||||
import com.hippo.ehviewer.client.exception.ParseException;
|
import com.hippo.ehviewer.client.exception.ParseException;
|
||||||
import com.hippo.util.ExceptionUtils;
|
import com.hippo.util.ExceptionUtils;
|
||||||
import com.hippo.util.JsoupUtils;
|
import com.hippo.util.JsoupUtils;
|
||||||
@@ -51,7 +50,8 @@ public class GalleryListParser {
|
|||||||
private static final Pattern PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px");
|
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_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_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[][]{
|
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
|
||||||
new String[]{"0", "0", "0"},
|
new String[]{"0", "0", "0"},
|
||||||
@@ -66,16 +66,6 @@ public class GalleryListParser {
|
|||||||
new String[]{"224", "128", "224"},
|
new String[]{"224", "128", "224"},
|
||||||
};
|
};
|
||||||
|
|
||||||
private static int parsePages(Document d, String body) throws ParseException {
|
|
||||||
try {
|
|
||||||
Elements es = d.getElementsByClass("ptt").first().child(0).child(0).children();
|
|
||||||
return Integer.parseInt(es.get(es.size() - 2).text().trim());
|
|
||||||
} catch (Throwable e) {
|
|
||||||
ExceptionUtils.throwIfFatal(e);
|
|
||||||
throw new ParseException("Can't parse gallery list pages", body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String parseRating(String ratingStyle) {
|
private static String parseRating(String ratingStyle) {
|
||||||
Matcher m = PATTERN_RATING.matcher(ratingStyle);
|
Matcher m = PATTERN_RATING.matcher(ratingStyle);
|
||||||
int num1 = Integer.MIN_VALUE;
|
int num1 = Integer.MIN_VALUE;
|
||||||
@@ -146,23 +136,21 @@ public class GalleryListParser {
|
|||||||
children = child.children();
|
children = child.children();
|
||||||
}
|
}
|
||||||
gi.title = child.text().trim();
|
gi.title = child.text().trim();
|
||||||
|
|
||||||
Element tbody = JsoupUtils.getElementByTag(glname, "tbody");
|
|
||||||
if (tbody != null) {
|
|
||||||
ArrayList<String> tags = new ArrayList<>();
|
|
||||||
GalleryTagGroup[] groups = GalleryDetailParser.parseTagGroups(tbody.children());
|
|
||||||
for (GalleryTagGroup group : groups) {
|
|
||||||
for (int j = 0; j < group.size(); j++) {
|
|
||||||
tags.add(group.groupName + ":" + group.getTagAt(j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gi.simpleTags = tags.toArray(new String[tags.size()]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (gi.title == null) {
|
if (gi.title == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
Elements gts = e.select(".gt, .gtl");
|
||||||
|
if (gts.size() != 0) {
|
||||||
|
ArrayList<String> tags = new ArrayList<>();
|
||||||
|
for (Element gt : gts) {
|
||||||
|
tags.add(gt.attr("title"));
|
||||||
|
}
|
||||||
|
gi.simpleTags = tags.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
// Category
|
// Category
|
||||||
gi.category = EhUtils.UNKNOWN;
|
gi.category = EhUtils.UNKNOWN;
|
||||||
Element ce = JsoupUtils.getElementByClass(e, "cn");
|
Element ce = JsoupUtils.getElementByClass(e, "cn");
|
||||||
@@ -266,10 +254,12 @@ public class GalleryListParser {
|
|||||||
if (children.size() > uploaderIndex) {
|
if (children.size() > uploaderIndex) {
|
||||||
Element div = children.get(uploaderIndex);
|
Element div = children.get(uploaderIndex);
|
||||||
if (div != null) {
|
if (div != null) {
|
||||||
gi.disowned = "opacity:0.5".equals(div.attr("style"));
|
gi.disowned = div.attr("style").contains("opacity:0.5");
|
||||||
Element a = div.children().first();
|
Element a = div.children().first();
|
||||||
if (a != null) {
|
if (a != null) {
|
||||||
gi.uploader = a.text().trim();
|
gi.uploader = a.text().trim();
|
||||||
|
} else {
|
||||||
|
gi.uploader = div.text().trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,37 +288,33 @@ public class GalleryListParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Result parse(@NonNull String body) throws Exception {
|
public static Result parse(@NonNull String body) throws Exception {
|
||||||
|
|
||||||
Result result = new Result();
|
Result result = new Result();
|
||||||
Document d = Jsoup.parse(body);
|
Document d = Jsoup.parse(body);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Element ptt = d.getElementsByClass("ptt").first();
|
Element prev = d.getElementById("uprev");
|
||||||
Elements es = ptt.child(0).child(0).children();
|
Element next = d.getElementById("unext");
|
||||||
result.pages = Integer.parseInt(es.get(es.size() - 2).text().trim());
|
assert prev != null;
|
||||||
|
assert next != null;
|
||||||
Element e = es.get(es.size() - 1);
|
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
|
||||||
if (e != null) {
|
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
|
||||||
e = e.children().first();
|
if (matcherPrev.find())
|
||||||
if (e != null) {
|
result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
|
||||||
String href = e.attr("href");
|
if (matcherNext.find())
|
||||||
Matcher matcher = PATTERN_NEXT_PAGE.matcher(href);
|
result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
|
||||||
if (matcher.find()) {
|
result.founds = Integer.MAX_VALUE;
|
||||||
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
ExceptionUtils.throwIfFatal(e);
|
ExceptionUtils.throwIfFatal(e);
|
||||||
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
|
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
|
||||||
if (body.contains("No hits found</p>")) {
|
if (body.contains("No hits found</p>")) {
|
||||||
result.pages = 0;
|
result.founds = 0;
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
result.galleryInfoList = Collections.EMPTY_LIST;
|
result.galleryInfoList = Collections.EMPTY_LIST;
|
||||||
return result;
|
return result;
|
||||||
} else if (d.getElementsByClass("ptt").isEmpty()) {
|
} else if (d.getElementsByClass("ptt").isEmpty()) {
|
||||||
result.pages = 1;
|
result.founds = 1;
|
||||||
} else {
|
} else {
|
||||||
result.pages = Integer.MAX_VALUE;
|
result.founds = Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +336,7 @@ public class GalleryListParser {
|
|||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (es.size() < 2 || !NO_UNFILTERED_TEXT.equals(es.get(1).text())) {
|
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;
|
result.galleryInfoList = list;
|
||||||
@@ -364,8 +350,9 @@ public class GalleryListParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Result {
|
public static class Result {
|
||||||
public int pages;
|
public int founds;
|
||||||
public int nextPage;
|
public int prevGid;
|
||||||
|
public int nextGid;
|
||||||
public boolean noWatchedTags;
|
public boolean noWatchedTags;
|
||||||
public List<GalleryInfo> galleryInfoList;
|
public List<GalleryInfo> galleryInfoList;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.hippo.ehviewer.client.parser;
|
package com.hippo.ehviewer.client.parser;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.hippo.ehviewer.client.EhUrl;
|
import com.hippo.ehviewer.client.EhUrl;
|
||||||
@@ -27,6 +28,7 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public final class GalleryListUrlParser {
|
public final class GalleryListUrlParser {
|
||||||
|
|
||||||
@@ -91,10 +93,15 @@ public final class GalleryListUrlParser {
|
|||||||
uploader = path.substring(prefixLength, index);
|
uploader = path.substring(prefixLength, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
uploader = URLDecoder.decode(uploader, "utf-8");
|
uploader = URLDecoder.decode(uploader, StandardCharsets.UTF_8);
|
||||||
} catch (UnsupportedEncodingException e) {
|
} else {
|
||||||
return null;
|
try {
|
||||||
|
uploader = URLDecoder.decode(uploader, StandardCharsets.UTF_8.displayName());
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(uploader)) {
|
if (TextUtils.isEmpty(uploader)) {
|
||||||
@@ -120,10 +127,15 @@ public final class GalleryListUrlParser {
|
|||||||
tag = path.substring(prefixLength, index);
|
tag = path.substring(prefixLength, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
tag = URLDecoder.decode(tag, "utf-8");
|
tag = URLDecoder.decode(tag, StandardCharsets.UTF_8);
|
||||||
} catch (UnsupportedEncodingException e) {
|
} else {
|
||||||
return null;
|
try {
|
||||||
|
tag = URLDecoder.decode(tag, StandardCharsets.UTF_8.displayName());
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(tag)) {
|
if (TextUtils.isEmpty(tag)) {
|
||||||
|
|||||||
@@ -19,17 +19,18 @@ package com.hippo.ehviewer.client.parser;
|
|||||||
import com.hippo.yorozuya.NumberUtils;
|
import com.hippo.yorozuya.NumberUtils;
|
||||||
import com.hippo.yorozuya.StringUtils;
|
import com.hippo.yorozuya.StringUtils;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.time.Instant;
|
||||||
import java.text.SimpleDateFormat;
|
import java.time.ZoneOffset;
|
||||||
import java.util.Date;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class ParserUtils {
|
public class ParserUtils {
|
||||||
|
|
||||||
public static final DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US);
|
public static final DateTimeFormatter formatter = DateTimeFormatter
|
||||||
|
.ofPattern("yyyy-MM-dd HH:mm", Locale.US).withZone(ZoneOffset.UTC);
|
||||||
|
|
||||||
public static synchronized String formatDate(long time) {
|
public static synchronized String formatDate(long time) {
|
||||||
return sDateFormat.format(new Date(time));
|
return formatter.format(Instant.ofEpochMilli(time));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String trim(String str) {
|
public static String trim(String str) {
|
||||||
@@ -48,7 +49,4 @@ public class ParserUtils {
|
|||||||
return NumberUtils.parseLongSafely(trim(str).replace(",", ""), defValue);
|
return NumberUtils.parseLongSafely(trim(str).replace(",", ""), defValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float parseFloat(String str, float defValue) {
|
|
||||||
return NumberUtils.parseFloatSafely(trim(str).replace(",", ""), defValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,6 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface BasicDao<T> {
|
public interface BasicDao<T> {
|
||||||
List<T> list();
|
List<T> list();
|
||||||
|
|
||||||
long insert(T t);
|
long insert(T t);
|
||||||
}
|
}
|
||||||
@@ -58,10 +58,9 @@ public class Filter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!(o instanceof Filter)) {
|
if (!(o instanceof Filter filter)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Filter filter = (Filter) o;
|
|
||||||
return filter.mode == mode && ObjectUtils.equal(filter.text, text);
|
return filter.mode == mode && ObjectUtils.equal(filter.text, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,10 +20,11 @@ public class HistoryInfo extends GalleryInfo {
|
|||||||
return new HistoryInfo[size];
|
return new HistoryInfo[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ColumnInfo(name = "MODE")
|
|
||||||
public int mode;
|
|
||||||
@ColumnInfo(name = "TIME")
|
@ColumnInfo(name = "TIME")
|
||||||
public long time;
|
public long time;
|
||||||
|
// Use MODE for favoriteSlot
|
||||||
|
@ColumnInfo(name = "MODE")
|
||||||
|
private int mode;
|
||||||
|
|
||||||
protected HistoryInfo(Parcel in) {
|
protected HistoryInfo(Parcel in) {
|
||||||
super(in);
|
super(in);
|
||||||
@@ -43,10 +44,11 @@ public class HistoryInfo extends GalleryInfo {
|
|||||||
this.rating = galleryInfo.rating;
|
this.rating = galleryInfo.rating;
|
||||||
this.simpleTags = galleryInfo.simpleTags;
|
this.simpleTags = galleryInfo.simpleTags;
|
||||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||||
|
this.favoriteSlot = galleryInfo.favoriteSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HistoryInfo(int mode, long time) {
|
public HistoryInfo(int mode, long time) {
|
||||||
this.mode = mode;
|
this.favoriteSlot = mode - 2;
|
||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,11 +133,11 @@ public class HistoryInfo extends GalleryInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getMode() {
|
public int getMode() {
|
||||||
return mode;
|
return favoriteSlot + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMode(int mode) {
|
public void setMode(int mode) {
|
||||||
this.mode = mode;
|
this.favoriteSlot = mode - 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTime() {
|
public long getTime() {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public class LocalFavoriteInfo extends GalleryInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public LocalFavoriteInfo() {
|
public LocalFavoriteInfo() {
|
||||||
|
this.favoriteSlot = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getGid() {
|
public long getGid() {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
|
|||||||
.setName(getString(R.string.download_service))
|
.setName(getString(R.string.download_service))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
mDownloadManager = EhApplication.getDownloadManager(applicationContext)
|
mDownloadManager = EhApplication.downloadManager
|
||||||
mDownloadManager!!.setDownloadListener(this)
|
mDownloadManager!!.setDownloadListener(this)
|
||||||
ensureDownloadingBuilder()
|
ensureDownloadingBuilder()
|
||||||
mDownloadingBuilder!!.setContentTitle(getString(R.string.download_service))
|
mDownloadingBuilder!!.setContentTitle(getString(R.string.download_service))
|
||||||
@@ -498,6 +498,7 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
|
|||||||
}
|
}
|
||||||
mNotifyManager.notify(mId, mBuilder.build())
|
mNotifyManager.notify(mId, mBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
OPS_CANCEL -> mNotifyManager.cancel(mId)
|
OPS_CANCEL -> mNotifyManager.cancel(mId)
|
||||||
OPS_START_FOREGROUND -> mService.startForeground(mId, mBuilder.build())
|
OPS_START_FOREGROUND -> mService.startForeground(mId, mBuilder.build())
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-38
@@ -17,6 +17,7 @@
|
|||||||
package com.hippo.ehviewer.gallery;
|
package com.hippo.ehviewer.gallery;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.ImageDecoder;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
@@ -24,15 +25,20 @@ import android.os.Process;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.hippo.Native;
|
||||||
import com.hippo.UriArchiveAccessor;
|
import com.hippo.UriArchiveAccessor;
|
||||||
import com.hippo.ehviewer.GetText;
|
import com.hippo.ehviewer.GetText;
|
||||||
import com.hippo.ehviewer.R;
|
import com.hippo.ehviewer.R;
|
||||||
import com.hippo.ehviewer.Settings;
|
import com.hippo.ehviewer.Settings;
|
||||||
import com.hippo.image.Image;
|
import com.hippo.image.Image;
|
||||||
import com.hippo.unifile.UniFile;
|
import com.hippo.unifile.UniFile;
|
||||||
|
import com.hippo.yorozuya.FileUtils;
|
||||||
import com.hippo.yorozuya.thread.PVLock;
|
import com.hippo.yorozuya.thread.PVLock;
|
||||||
import com.hippo.yorozuya.thread.PriorityThread;
|
import com.hippo.yorozuya.thread.PriorityThread;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -40,26 +46,27 @@ import java.util.Set;
|
|||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class ArchiveGalleryProvider extends GalleryProvider2 {
|
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||||
|
|
||||||
|
public class ArchivePageLoader extends PageLoader2 {
|
||||||
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
||||||
public static Handler showPasswd;
|
public static Handler showPasswd;
|
||||||
public static String passwd;
|
public static String passwd;
|
||||||
public static PVLock pv = new PVLock(0);
|
public static PVLock pv = new PVLock(0);
|
||||||
private final UriArchiveAccessor archiveAccessor;
|
private final UriArchiveAccessor archiveAccessor;
|
||||||
private final Stack<Integer> requests = new Stack<>();
|
private final Stack<Integer> requests = new Stack<>();
|
||||||
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
|
private final LinkedHashMap<Integer, ByteBuffer> streams = new LinkedHashMap<>();
|
||||||
private Thread archiveThread;
|
private final Thread[] decodeThread = new Thread[]{
|
||||||
private Thread[] decodeThread = new Thread[] {
|
|
||||||
new Thread(new DecodeTask()),
|
new Thread(new DecodeTask()),
|
||||||
new Thread(new DecodeTask()),
|
new Thread(new DecodeTask()),
|
||||||
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 volatile int size = PageLoader.STATE_WAIT;
|
||||||
private String error;
|
private String error;
|
||||||
|
|
||||||
public ArchiveGalleryProvider(Context context, Uri uri) {
|
public ArchivePageLoader(Context context, Uri uri) {
|
||||||
UriArchiveAccessor archiveAccessor1;
|
UriArchiveAccessor archiveAccessor1;
|
||||||
try {
|
try {
|
||||||
archiveAccessor1 = new UriArchiveAccessor(context, uri);
|
archiveAccessor1 = new UriArchiveAccessor(context, uri);
|
||||||
@@ -72,8 +79,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
|
||||||
|
|
||||||
int id = sIdGenerator.incrementAndGet();
|
int id = sIdGenerator.incrementAndGet();
|
||||||
|
|
||||||
archiveThread = new PriorityThread(
|
archiveThread = new PriorityThread(
|
||||||
@@ -86,7 +91,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
super.stop();
|
|
||||||
if (archiveThread != null) {
|
if (archiveThread != null) {
|
||||||
archiveThread.interrupt();
|
archiveThread.interrupt();
|
||||||
archiveThread = null;
|
archiveThread = null;
|
||||||
@@ -143,32 +147,47 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getImageFilename(int index) {
|
public String getImageFilename(int index) {
|
||||||
// TODO
|
return FileUtils.getNameFromFilename(getImageFilenameWithExtension(index));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getImageFilenameWithExtension(int index) {
|
public String getImageFilenameWithExtension(int index) {
|
||||||
// TODO
|
return FileUtils.sanitizeFilename(archiveAccessor.getFilename(index));
|
||||||
return Integer.toString(index);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean save(int index, @NonNull UniFile file) {
|
||||||
|
int fd;
|
||||||
|
FileOutputStream stream;
|
||||||
|
try {
|
||||||
|
stream = (FileOutputStream) file.openOutputStream();
|
||||||
|
fd = Native.getFd(stream.getFD());
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
archiveAccessor.extractToFd(index, fd);
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 {
|
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()),
|
new Thread(new ArchiveTask()),
|
||||||
new Thread(new ArchiveTask()),
|
new Thread(new ArchiveTask()),
|
||||||
@@ -197,7 +216,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
|||||||
size = 0;
|
size = 0;
|
||||||
}
|
}
|
||||||
if (size <= 0) {
|
if (size <= 0) {
|
||||||
size = STATE_ERROR;
|
size = PageLoader.STATE_ERROR;
|
||||||
error = GetText.getString(R.string.error_reading_failed);
|
error = GetText.getString(R.string.error_reading_failed);
|
||||||
notifyDataChanged();
|
notifyDataChanged();
|
||||||
return;
|
return;
|
||||||
@@ -269,7 +288,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
|||||||
|
|
||||||
// Check index valid
|
// Check index valid
|
||||||
if (index < 0 || index >= size) {
|
if (index < 0 || index >= size) {
|
||||||
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
|
notifyPageFailed(index);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,10 +298,10 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long addr = archiveAccessor.extracttoOutputStream(index);
|
ByteBuffer buffer = archiveAccessor.extractToByteBuffer(index);
|
||||||
|
|
||||||
synchronized (streams) {
|
synchronized (streams) {
|
||||||
streams.put(index, addr);
|
streams.put(index, buffer);
|
||||||
streams.notify();
|
streams.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,7 +313,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
|||||||
public void run() {
|
public void run() {
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
int index;
|
int index;
|
||||||
Long addr;
|
ByteBuffer buffer;
|
||||||
synchronized (streams) {
|
synchronized (streams) {
|
||||||
if (streams.isEmpty()) {
|
if (streams.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
@@ -305,18 +324,29 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
|
Iterator<Map.Entry<Integer, ByteBuffer>> iterator = streams.entrySet().iterator();
|
||||||
Map.Entry<Integer, Long> entry = iterator.next();
|
Map.Entry<Integer, ByteBuffer> entry = iterator.next();
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
index = entry.getKey();
|
index = entry.getKey();
|
||||||
addr = entry.getValue();
|
buffer = entry.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
Image image = Image.decodeAddr(addr, true);
|
Image image = null;
|
||||||
|
if (buffer != null) {
|
||||||
|
try {
|
||||||
|
image = Image.decode(buffer, () -> {
|
||||||
|
archiveAccessor.releaseByteBuffer(buffer);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (ImageDecoder.DecodeException e) {
|
||||||
|
archiveAccessor.releaseByteBuffer(buffer);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
notifyPageSucceed(index, image);
|
notifyPageSucceed(index, image);
|
||||||
} else {
|
} else {
|
||||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
notifyPageFailed(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
-23
@@ -24,7 +24,6 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import com.hippo.ehviewer.GetText;
|
import com.hippo.ehviewer.GetText;
|
||||||
import com.hippo.ehviewer.R;
|
import com.hippo.ehviewer.R;
|
||||||
import com.hippo.glgallery.GalleryPageView;
|
|
||||||
import com.hippo.image.Image;
|
import com.hippo.image.Image;
|
||||||
import com.hippo.unifile.FilenameFilter;
|
import com.hippo.unifile.FilenameFilter;
|
||||||
import com.hippo.unifile.UniFile;
|
import com.hippo.unifile.UniFile;
|
||||||
@@ -44,9 +43,11 @@ import java.util.Stack;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||||
|
|
||||||
private static final String TAG = DirGalleryProvider.class.getSimpleName();
|
public class DirPageLoader extends PageLoader2 implements Runnable {
|
||||||
|
|
||||||
|
private static final String TAG = DirPageLoader.class.getSimpleName();
|
||||||
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
||||||
private static final FilenameFilter imageFilter =
|
private static final FilenameFilter imageFilter =
|
||||||
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
|
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
|
||||||
@@ -60,21 +61,19 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
|||||||
};
|
};
|
||||||
private final UniFile mDir;
|
private final UniFile mDir;
|
||||||
private final Stack<Integer> mRequests = new Stack<>();
|
private final Stack<Integer> mRequests = new Stack<>();
|
||||||
private final AtomicInteger mDecodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
private final AtomicInteger mDecodingIndex = new AtomicInteger(-1);
|
||||||
private final AtomicReference<UniFile[]> mFileList = new AtomicReference<>();
|
private final AtomicReference<UniFile[]> mFileList = new AtomicReference<>();
|
||||||
@Nullable
|
@Nullable
|
||||||
private Thread mBgThread;
|
private Thread mBgThread;
|
||||||
private volatile int mSize = STATE_WAIT;
|
private volatile int mSize = PageLoader.STATE_WAIT;
|
||||||
private String mError;
|
private String mError;
|
||||||
|
|
||||||
public DirGalleryProvider(@NonNull UniFile dir) {
|
public DirPageLoader(@NonNull UniFile dir) {
|
||||||
mDir = dir;
|
mDir = dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
|
||||||
|
|
||||||
mBgThread = new PriorityThread(this, TAG + '-' + sIdGenerator.incrementAndGet(),
|
mBgThread = new PriorityThread(this, TAG + '-' + sIdGenerator.incrementAndGet(),
|
||||||
Process.THREAD_PRIORITY_BACKGROUND);
|
Process.THREAD_PRIORITY_BACKGROUND);
|
||||||
mBgThread.start();
|
mBgThread.start();
|
||||||
@@ -82,8 +81,6 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
super.stop();
|
|
||||||
|
|
||||||
if (mBgThread != null) {
|
if (mBgThread != null) {
|
||||||
mBgThread.interrupt();
|
mBgThread.interrupt();
|
||||||
mBgThread = null;
|
mBgThread = null;
|
||||||
@@ -213,7 +210,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
|||||||
UniFile[] files = mDir.listFiles(imageFilter);
|
UniFile[] files = mDir.listFiles(imageFilter);
|
||||||
|
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
mSize = STATE_ERROR;
|
mSize = PageLoader.STATE_ERROR;
|
||||||
mError = GetText.getString(R.string.error_not_folder_path);
|
mError = GetText.getString(R.string.error_not_folder_path);
|
||||||
|
|
||||||
// Notify to to show error
|
// Notify to to show error
|
||||||
@@ -251,28 +248,24 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
|||||||
|
|
||||||
// Check index valid
|
// Check index valid
|
||||||
if (index < 0 || index >= files.length) {
|
if (index < 0 || index >= files.length) {
|
||||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
mDecodingIndex.lazySet(-1);
|
||||||
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
|
notifyPageFailed(index);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
is = files[index].openInputStream();
|
is = files[index].openInputStream();
|
||||||
Image image = Image.decode(((FileInputStream) is).getFD(), true);
|
Image image = Image.decode((FileInputStream) is);
|
||||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
mDecodingIndex.lazySet(-1);
|
||||||
if (image != null) {
|
notifyPageSucceed(index, image);
|
||||||
notifyPageSucceed(index, image);
|
|
||||||
} else {
|
|
||||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
mDecodingIndex.lazySet(-1);
|
||||||
notifyPageFailed(index, GetText.getString(R.string.error_reading_failed));
|
notifyPageFailed(index);
|
||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(is);
|
IOUtils.closeQuietly(is);
|
||||||
}
|
}
|
||||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
mDecodingIndex.lazySet(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear file list
|
// Clear file list
|
||||||
+11
-12
@@ -23,37 +23,36 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||||
import com.hippo.ehviewer.spider.SpiderQueen;
|
import com.hippo.ehviewer.spider.SpiderQueen;
|
||||||
import com.hippo.glgallery.GalleryProvider;
|
|
||||||
import com.hippo.image.Image;
|
import com.hippo.image.Image;
|
||||||
import com.hippo.unifile.UniFile;
|
import com.hippo.unifile.UniFile;
|
||||||
import com.hippo.yorozuya.SimpleHandler;
|
import com.hippo.yorozuya.SimpleHandler;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.OnSpiderListener {
|
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||||
|
|
||||||
|
public class EhPageLoader extends PageLoader2 implements SpiderQueen.OnSpiderListener {
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final GalleryInfo mGalleryInfo;
|
private final GalleryInfo mGalleryInfo;
|
||||||
@Nullable
|
@Nullable
|
||||||
private SpiderQueen mSpiderQueen;
|
private SpiderQueen mSpiderQueen;
|
||||||
|
|
||||||
public EhGalleryProvider(Context context, GalleryInfo galleryInfo) {
|
public EhPageLoader(Context context, GalleryInfo galleryInfo) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mGalleryInfo = galleryInfo;
|
mGalleryInfo = galleryInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
|
||||||
|
|
||||||
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mContext, mGalleryInfo, SpiderQueen.MODE_READ);
|
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mContext, mGalleryInfo, SpiderQueen.MODE_READ);
|
||||||
mSpiderQueen.addOnSpiderListener(this);
|
mSpiderQueen.addOnSpiderListener(this);
|
||||||
|
if (mSpiderQueen.size() > 0)
|
||||||
|
notifyDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
super.stop();
|
|
||||||
|
|
||||||
if (mSpiderQueen != null) {
|
if (mSpiderQueen != null) {
|
||||||
mSpiderQueen.removeOnSpiderListener(this);
|
mSpiderQueen.removeOnSpiderListener(this);
|
||||||
// Activity recreate may called, so wait 3000s
|
// Activity recreate may called, so wait 3000s
|
||||||
@@ -120,7 +119,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
|||||||
if (mSpiderQueen != null) {
|
if (mSpiderQueen != null) {
|
||||||
return mSpiderQueen.size();
|
return mSpiderQueen.size();
|
||||||
} else {
|
} else {
|
||||||
return GalleryProvider.STATE_ERROR;
|
return PageLoader.STATE_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +130,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
|||||||
if (object instanceof Float) {
|
if (object instanceof Float) {
|
||||||
notifyPagePercent(index, (Float) object);
|
notifyPagePercent(index, (Float) object);
|
||||||
} else if (object instanceof String) {
|
} else if (object instanceof String) {
|
||||||
notifyPageFailed(index, (String) object);
|
notifyPageFailed(index);
|
||||||
} else if (object == null) {
|
} else if (object == null) {
|
||||||
notifyPageWait(index);
|
notifyPageWait(index);
|
||||||
}
|
}
|
||||||
@@ -145,7 +144,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
|||||||
if (object instanceof Float) {
|
if (object instanceof Float) {
|
||||||
notifyPagePercent(index, (Float) object);
|
notifyPagePercent(index, (Float) object);
|
||||||
} else if (object instanceof String) {
|
} else if (object instanceof String) {
|
||||||
notifyPageFailed(index, (String) object);
|
notifyPageFailed(index);
|
||||||
} else if (object == null) {
|
} else if (object == null) {
|
||||||
notifyPageWait(index);
|
notifyPageWait(index);
|
||||||
}
|
}
|
||||||
@@ -192,7 +191,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageFailure(int index, String error, int finished, int downloaded, int total) {
|
public void onPageFailure(int index, String error, int finished, int downloaded, int total) {
|
||||||
notifyPageFailed(index, error);
|
notifyPageFailed(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -206,7 +205,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onGetImageFailure(int index, String error) {
|
public void onGetImageFailure(int index, String error) {
|
||||||
notifyPageFailed(index, error);
|
notifyPageFailed(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ReleaseTask implements Runnable {
|
private static class ReleaseTask implements Runnable {
|
||||||
+3
-2
@@ -19,10 +19,11 @@ package com.hippo.ehviewer.gallery;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.hippo.glgallery.GalleryProvider;
|
|
||||||
import com.hippo.unifile.UniFile;
|
import com.hippo.unifile.UniFile;
|
||||||
|
|
||||||
public abstract class GalleryProvider2 extends GalleryProvider {
|
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||||
|
|
||||||
|
public abstract class PageLoader2 extends PageLoader {
|
||||||
|
|
||||||
// With dot
|
// With dot
|
||||||
public static final String[] SUPPORT_IMAGE_EXTENSIONS = {
|
public static final String[] SUPPORT_IMAGE_EXTENSIONS = {
|
||||||
@@ -15,9 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.hippo.ehviewer.preference
|
package com.hippo.ehviewer.preference
|
||||||
|
|
||||||
import android.content.*
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.content.DialogInterface
|
||||||
import android.os.PersistableBundle
|
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
@@ -29,9 +28,10 @@ import com.hippo.ehviewer.client.EhUtils
|
|||||||
import com.hippo.ehviewer.ui.SettingsActivity
|
import com.hippo.ehviewer.ui.SettingsActivity
|
||||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||||
import com.hippo.preference.MessagePreference
|
import com.hippo.preference.MessagePreference
|
||||||
|
import com.hippo.util.addTextToClipboard
|
||||||
import okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import java.util.*
|
import java.util.LinkedList
|
||||||
|
|
||||||
class AccountPreference @JvmOverloads constructor(
|
class AccountPreference @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null
|
context: Context, attrs: AttributeSet? = null
|
||||||
@@ -40,7 +40,7 @@ class AccountPreference @JvmOverloads constructor(
|
|||||||
private var message: String? = null
|
private var message: String? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val store = EhApplication.getEhCookieStore(context)
|
val store = EhApplication.ehCookieStore
|
||||||
val eCookies = store.getCookies(EhUrl.HOST_E.toHttpUrl())
|
val eCookies = store.getCookies(EhUrl.HOST_E.toHttpUrl())
|
||||||
val exCookies = store.getCookies(EhUrl.HOST_EX.toHttpUrl())
|
val exCookies = store.getCookies(EhUrl.HOST_EX.toHttpUrl())
|
||||||
val cookies: MutableList<Cookie> = LinkedList(eCookies)
|
val cookies: MutableList<Cookie> = LinkedList(eCookies)
|
||||||
@@ -81,25 +81,12 @@ class AccountPreference @JvmOverloads constructor(
|
|||||||
super.onPrepareDialogBuilder(builder)
|
super.onPrepareDialogBuilder(builder)
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
builder.setNeutralButton(R.string.settings_eh_identity_cookies_copy) { dialog: DialogInterface?, which: Int ->
|
builder.setNeutralButton(R.string.settings_eh_identity_cookies_copy) { dialog: DialogInterface?, which: Int ->
|
||||||
val clipboardManager =
|
mActivity.addTextToClipboard(message, true)
|
||||||
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@AccountPreference.onClick(dialog, which)
|
this@AccountPreference.onClick(dialog, which)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.setPositiveButton(R.string.settings_eh_sign_out) { _: DialogInterface?, _: Int ->
|
builder.setPositiveButton(R.string.settings_eh_sign_out) { _: DialogInterface?, _: Int ->
|
||||||
EhUtils.signOut(context)
|
EhUtils.signOut()
|
||||||
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT)
|
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class CleanRedundancyPreference extends TaskPreference {
|
|||||||
|
|
||||||
public ClearTask(@NonNull Context context) {
|
public ClearTask(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
mManager = EhApplication.getDownloadManager(context.getApplicationContext());
|
mManager = EhApplication.getDownloadManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
// True for cleared
|
// True for cleared
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ class ProxyPreference @JvmOverloads constructor(
|
|||||||
Settings.putProxyIp(ip)
|
Settings.putProxyIp(ip)
|
||||||
Settings.putProxyPort(port)
|
Settings.putProxyPort(port)
|
||||||
updateSummary(type, ip, port)
|
updateSummary(type, ip, port)
|
||||||
EhApplication.getEhProxySelector(getContext()).updateProxy()
|
EhApplication.ehProxySelector.updateProxy()
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,8 +74,8 @@ public class RestoreDownloadPreference extends TaskPreference {
|
|||||||
|
|
||||||
public RestoreTask(@NonNull Context context) {
|
public RestoreTask(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
mManager = EhApplication.getDownloadManager(context.getApplicationContext());
|
mManager = EhApplication.getDownloadManager();
|
||||||
mHttpClient = EhApplication.getOkHttpClient(context.getApplicationContext());
|
mHttpClient = EhApplication.getOkHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
private RestoreItem getRestoreItem(UniFile file) {
|
private RestoreItem getRestoreItem(UniFile file) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import com.hippo.ehviewer.client.EhCookieStore;
|
|||||||
import com.hippo.ehviewer.client.EhUrl;
|
import com.hippo.ehviewer.client.EhUrl;
|
||||||
import com.hippo.ehviewer.ui.SettingsActivity;
|
import com.hippo.ehviewer.ui.SettingsActivity;
|
||||||
import com.hippo.ehviewer.ui.scene.BaseScene;
|
import com.hippo.ehviewer.ui.scene.BaseScene;
|
||||||
import com.hippo.util.WebViewUtil;
|
|
||||||
|
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
@@ -59,11 +58,7 @@ public class SignInRequiredPreference extends Preference implements Preference.O
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
if (!WebViewUtil.available(getContext())) {
|
EhCookieStore store = EhApplication.getEhCookieStore();
|
||||||
mActivity.showTip(R.string.error_no_webview, BaseScene.LENGTH_SHORT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
EhCookieStore store = EhApplication.getEhCookieStore(getContext());
|
|
||||||
HttpUrl e = HttpUrl.get(EhUrl.HOST_E);
|
HttpUrl e = HttpUrl.get(EhUrl.HOST_E);
|
||||||
HttpUrl ex = HttpUrl.get(EhUrl.HOST_EX);
|
HttpUrl ex = HttpUrl.get(EhUrl.HOST_EX);
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import com.hippo.ehviewer.Settings;
|
|||||||
import com.hippo.ehviewer.client.EhCacheKeyFactory;
|
import com.hippo.ehviewer.client.EhCacheKeyFactory;
|
||||||
import com.hippo.ehviewer.client.EhUtils;
|
import com.hippo.ehviewer.client.EhUtils;
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
import com.hippo.ehviewer.gallery.PageLoader2;
|
||||||
import com.hippo.io.UniFileInputStreamPipe;
|
import com.hippo.io.UniFileInputStreamPipe;
|
||||||
import com.hippo.io.UniFileOutputStreamPipe;
|
import com.hippo.io.UniFileOutputStreamPipe;
|
||||||
import com.hippo.streampipe.InputStreamPipe;
|
import com.hippo.streampipe.InputStreamPipe;
|
||||||
@@ -118,7 +118,7 @@ public final class SpiderDen {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static UniFile findImageFile(UniFile dir, int index) {
|
private static UniFile findImageFile(UniFile dir, int index) {
|
||||||
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
|
for (String extension : PageLoader2.SUPPORT_IMAGE_EXTENSIONS) {
|
||||||
String filename = generateImageFilename(index, extension);
|
String filename = generateImageFilename(index, extension);
|
||||||
UniFile file = dir.findFile(filename);
|
UniFile file = dir.findFile(filename);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
@@ -179,10 +179,10 @@ public final class SpiderDen {
|
|||||||
* @param extension with dot
|
* @param extension with dot
|
||||||
*/
|
*/
|
||||||
private String fixExtension(String extension) {
|
private String fixExtension(String extension) {
|
||||||
if (Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
if (Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||||
return extension;
|
return extension;
|
||||||
} else {
|
} else {
|
||||||
return GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
|
return PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,8 +261,8 @@ public final class SpiderDen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
for (int i = 0, n = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
|
for (int i = 0, n = PageLoader2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
|
||||||
String filename = generateImageFilename(index, GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[i]);
|
String filename = generateImageFilename(index, PageLoader2.SUPPORT_IMAGE_EXTENSIONS[i]);
|
||||||
UniFile file = dir.subFile(filename);
|
UniFile file = dir.subFile(filename);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
result |= file.delete();
|
result |= file.delete();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.spider;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.ImageDecoder;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -47,9 +48,7 @@ import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser;
|
|||||||
import com.hippo.ehviewer.client.parser.GalleryPageApiParser;
|
import com.hippo.ehviewer.client.parser.GalleryPageApiParser;
|
||||||
import com.hippo.ehviewer.client.parser.GalleryPageParser;
|
import com.hippo.ehviewer.client.parser.GalleryPageParser;
|
||||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
||||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
import com.hippo.ehviewer.gallery.PageLoader2;
|
||||||
import com.hippo.glgallery.GalleryPageView;
|
|
||||||
import com.hippo.glgallery.GalleryProvider;
|
|
||||||
import com.hippo.image.Image;
|
import com.hippo.image.Image;
|
||||||
import com.hippo.streampipe.InputStreamPipe;
|
import com.hippo.streampipe.InputStreamPipe;
|
||||||
import com.hippo.streampipe.OutputStreamPipe;
|
import com.hippo.streampipe.OutputStreamPipe;
|
||||||
@@ -84,6 +83,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
@@ -156,9 +156,9 @@ public final class SpiderQueen implements Runnable {
|
|||||||
// For download, when it go to mPageStateArray.size(), done
|
// For download, when it go to mPageStateArray.size(), done
|
||||||
private volatile int mDownloadPage = -1;
|
private volatile int mDownloadPage = -1;
|
||||||
|
|
||||||
private SpiderQueen(EhApplication application, @NonNull GalleryInfo galleryInfo) {
|
private SpiderQueen(@NonNull GalleryInfo galleryInfo) {
|
||||||
mHttpClient = EhApplication.getOkHttpClient(application);
|
mHttpClient = EhApplication.getOkHttpClient();
|
||||||
mSpiderInfoCache = EhApplication.getSpiderInfoCache(application);
|
mSpiderInfoCache = EhApplication.getSpiderInfoCache();
|
||||||
mGalleryInfo = galleryInfo;
|
mGalleryInfo = galleryInfo;
|
||||||
mSpiderDen = new SpiderDen(mGalleryInfo);
|
mSpiderDen = new SpiderDen(mGalleryInfo);
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ public final class SpiderQueen implements Runnable {
|
|||||||
mPreloadNumber = MathUtils.clamp(Settings.getPreloadImage(), 0, 100);
|
mPreloadNumber = MathUtils.clamp(Settings.getPreloadImage(), 0, 100);
|
||||||
|
|
||||||
for (int i = 0; i < DECODE_THREAD_NUM; i++) {
|
for (int i = 0; i < DECODE_THREAD_NUM; i++) {
|
||||||
mDecodeIndexArray[i] = GalleryPageView.INVALID_INDEX;
|
mDecodeIndexArray[i] = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
mWorkerPoolExecutor = new ThreadPoolExecutor(mWorkerMaxCount, mWorkerMaxCount,
|
mWorkerPoolExecutor = new ThreadPoolExecutor(mWorkerMaxCount, mWorkerMaxCount,
|
||||||
@@ -182,8 +182,8 @@ public final class SpiderQueen implements Runnable {
|
|||||||
|
|
||||||
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
|
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
|
||||||
if (queen == null) {
|
if (queen == null) {
|
||||||
EhApplication application = (EhApplication) context.getApplicationContext();
|
context.getApplicationContext();
|
||||||
queen = new SpiderQueen(application, galleryInfo);
|
queen = new SpiderQueen(galleryInfo);
|
||||||
sQueenMap.put(galleryInfo.gid, queen);
|
sQueenMap.put(galleryInfo.gid, queen);
|
||||||
// Set mode
|
// Set mode
|
||||||
queen.setMode(mode);
|
queen.setMode(mode);
|
||||||
@@ -357,12 +357,8 @@ public final class SpiderQueen implements Runnable {
|
|||||||
|
|
||||||
private void setMode(@Mode int mode) {
|
private void setMode(@Mode int mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case MODE_READ:
|
case MODE_READ -> mReadReference++;
|
||||||
mReadReference++;
|
case MODE_DOWNLOAD -> mDownloadReference++;
|
||||||
break;
|
|
||||||
case MODE_DOWNLOAD:
|
|
||||||
mDownloadReference++;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDownloadReference > 1) {
|
if (mDownloadReference > 1) {
|
||||||
@@ -374,12 +370,8 @@ public final class SpiderQueen implements Runnable {
|
|||||||
|
|
||||||
private void clearMode(@Mode int mode) {
|
private void clearMode(@Mode int mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case MODE_READ:
|
case MODE_READ -> mReadReference--;
|
||||||
mReadReference--;
|
case MODE_DOWNLOAD -> mDownloadReference--;
|
||||||
break;
|
|
||||||
case MODE_DOWNLOAD:
|
|
||||||
mDownloadReference--;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mReadReference < 0 || mDownloadReference < 0) {
|
if (mReadReference < 0 || mDownloadReference < 0) {
|
||||||
@@ -406,9 +398,9 @@ public final class SpiderQueen implements Runnable {
|
|||||||
|
|
||||||
public int size() {
|
public int size() {
|
||||||
if (mQueenThread == null) {
|
if (mQueenThread == null) {
|
||||||
return GalleryProvider.STATE_ERROR;
|
return PageLoader.STATE_ERROR;
|
||||||
} else if (mPageStateArray == null) {
|
} else if (mPageStateArray == null) {
|
||||||
return GalleryProvider.STATE_WAIT;
|
return PageLoader.STATE_WAIT;
|
||||||
} else {
|
} else {
|
||||||
return mPageStateArray.length;
|
return mPageStateArray.length;
|
||||||
}
|
}
|
||||||
@@ -522,21 +514,16 @@ public final class SpiderQueen implements Runnable {
|
|||||||
Object result;
|
Object result;
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
default:
|
case STATE_NONE -> result = null;
|
||||||
case STATE_NONE:
|
case STATE_DOWNLOADING -> result = mPagePercentMap.get(index);
|
||||||
result = null;
|
case STATE_FAILED -> {
|
||||||
break;
|
|
||||||
case STATE_DOWNLOADING:
|
|
||||||
result = mPagePercentMap.get(index);
|
|
||||||
break;
|
|
||||||
case STATE_FAILED:
|
|
||||||
String error = mPageErrorMap.get(index);
|
String error = mPageErrorMap.get(index);
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
error = GetText.getString(R.string.error_unknown);
|
error = GetText.getString(R.string.error_unknown);
|
||||||
}
|
}
|
||||||
result = error;
|
result = error;
|
||||||
break;
|
}
|
||||||
case STATE_FINISHED:
|
case STATE_FINISHED -> {
|
||||||
synchronized (mDecodeRequestQueue) {
|
synchronized (mDecodeRequestQueue) {
|
||||||
if (!contain(mDecodeIndexArray, index) && !mDecodeRequestQueue.contains(index)) {
|
if (!contain(mDecodeIndexArray, index) && !mDecodeRequestQueue.contains(index)) {
|
||||||
mDecodeRequestQueue.add(index);
|
mDecodeRequestQueue.add(index);
|
||||||
@@ -544,7 +531,8 @@ public final class SpiderQueen implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = null;
|
result = null;
|
||||||
break;
|
}
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + state);
|
||||||
}
|
}
|
||||||
|
|
||||||
tryToEnsureWorkers();
|
tryToEnsureWorkers();
|
||||||
@@ -644,7 +632,6 @@ public final class SpiderQueen implements Runnable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputStream os = null;
|
|
||||||
try {
|
try {
|
||||||
pipe.obtain();
|
pipe.obtain();
|
||||||
|
|
||||||
@@ -660,7 +647,6 @@ public final class SpiderQueen implements Runnable {
|
|||||||
} finally {
|
} finally {
|
||||||
pipe.close();
|
pipe.close();
|
||||||
pipe.release();
|
pipe.release();
|
||||||
IOUtils.closeQuietly(os);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,14 +736,13 @@ public final class SpiderQueen implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SpiderInfo readSpiderInfoFromInternet() {
|
private SpiderInfo readSpiderInfoFromInternet() {
|
||||||
try {
|
SpiderInfo spiderInfo = new SpiderInfo();
|
||||||
SpiderInfo spiderInfo = new SpiderInfo();
|
spiderInfo.gid = mGalleryInfo.gid;
|
||||||
spiderInfo.gid = mGalleryInfo.gid;
|
spiderInfo.token = mGalleryInfo.token;
|
||||||
spiderInfo.token = mGalleryInfo.token;
|
|
||||||
|
|
||||||
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
|
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
|
||||||
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
|
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
|
||||||
Response response = mHttpClient.newCall(request).execute();
|
try (Response response = mHttpClient.newCall(request).execute()) {
|
||||||
String body = response.body().string();
|
String body = response.body().string();
|
||||||
|
|
||||||
spiderInfo.pages = GalleryDetailParser.parsePages(body);
|
spiderInfo.pages = GalleryDetailParser.parsePages(body);
|
||||||
@@ -776,15 +761,14 @@ public final class SpiderQueen implements Runnable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
String url = EhUrl.getGalleryMultiPageViewerUrl(
|
||||||
String url = EhUrl.getGalleryMultiPageViewerUrl(
|
mGalleryInfo.gid, mGalleryInfo.token);
|
||||||
mGalleryInfo.gid, mGalleryInfo.token);
|
if (DEBUG_PTOKEN) {
|
||||||
if (DEBUG_PTOKEN) {
|
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
|
||||||
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
|
}
|
||||||
}
|
String referer = EhUrl.getReferer();
|
||||||
String referer = EhUrl.getReferer();
|
Request request = new EhRequestBuilder(url, referer).build();
|
||||||
Request request = new EhRequestBuilder(url, referer).build();
|
try (Response response = mHttpClient.newCall(request).execute()) {
|
||||||
Response response = mHttpClient.newCall(request).execute();
|
|
||||||
String body = response.body().string();
|
String body = response.body().string();
|
||||||
|
|
||||||
ArrayList<String> list = GalleryMultiPageViewerPTokenParser.parse(body);
|
ArrayList<String> list = GalleryMultiPageViewerPTokenParser.parse(body);
|
||||||
@@ -823,16 +807,15 @@ public final class SpiderQueen implements Runnable {
|
|||||||
previewIndex = Math.min(previewIndex, spiderInfo.previewPages - 1);
|
previewIndex = Math.min(previewIndex, spiderInfo.previewPages - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
String url = EhUrl.getGalleryDetailUrl(
|
||||||
String url = EhUrl.getGalleryDetailUrl(
|
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
|
||||||
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
|
String referer = EhUrl.getReferer();
|
||||||
String referer = EhUrl.getReferer();
|
if (DEBUG_PTOKEN) {
|
||||||
if (DEBUG_PTOKEN) {
|
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
|
||||||
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
|
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
|
||||||
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
|
}
|
||||||
}
|
Request request = new EhRequestBuilder(url, referer).build();
|
||||||
Request request = new EhRequestBuilder(url, referer).build();
|
try (Response response = mHttpClient.newCall(request).execute()) {
|
||||||
Response response = mHttpClient.newCall(request).execute();
|
|
||||||
String body = response.body().string();
|
String body = response.body().string();
|
||||||
readPreviews(body, previewIndex, spiderInfo);
|
readPreviews(body, previewIndex, spiderInfo);
|
||||||
|
|
||||||
@@ -857,7 +840,7 @@ public final class SpiderQueen implements Runnable {
|
|||||||
UniFile file = downloadDir.createFile(SPIDER_INFO_FILENAME);
|
UniFile file = downloadDir.createFile(SPIDER_INFO_FILENAME);
|
||||||
try {
|
try {
|
||||||
spiderInfo.write(file.openOutputStream());
|
spiderInfo.write(file.openOutputStream());
|
||||||
} catch (Throwable e) {
|
} catch (IOException e) {
|
||||||
ExceptionUtils.throwIfFatal(e);
|
ExceptionUtils.throwIfFatal(e);
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
@@ -1149,7 +1132,7 @@ public final class SpiderQueen implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// false for stop
|
// false for stop
|
||||||
private boolean downloadImage(long gid, int index, String pToken, String previousPToken, boolean force) {
|
private boolean downloadImage(long gid, int index, String pToken, String previousPToken) {
|
||||||
String skipHathKey = null;
|
String skipHathKey = null;
|
||||||
List<String> skipHathKeys = new ArrayList<>(5);
|
List<String> skipHathKeys = new ArrayList<>(5);
|
||||||
String originImageUrl = null;
|
String originImageUrl = null;
|
||||||
@@ -1283,12 +1266,6 @@ public final class SpiderQueen implements Runnable {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseBody == null) {
|
|
||||||
error = "Empty response body";
|
|
||||||
forceHtml = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get extension
|
// Get extension
|
||||||
String extension = null;
|
String extension = null;
|
||||||
MediaType mediaType = responseBody.contentType();
|
MediaType mediaType = responseBody.contentType();
|
||||||
@@ -1296,8 +1273,8 @@ public final class SpiderQueen implements Runnable {
|
|||||||
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mediaType.toString());
|
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mediaType.toString());
|
||||||
}
|
}
|
||||||
// Ensure extension
|
// Ensure extension
|
||||||
if (!Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
if (!Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||||
extension = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
|
extension = PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputStreamPipe osPipe = null;
|
OutputStreamPipe osPipe = null;
|
||||||
@@ -1574,7 +1551,7 @@ public final class SpiderQueen implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get image url
|
// Get image url
|
||||||
return downloadImage(mGid, index, pToken, previousPToken, force);
|
return downloadImage(mGid, index, pToken, previousPToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1595,7 +1572,7 @@ public final class SpiderQueen implements Runnable {
|
|||||||
Log.e(TAG, "WTF, mWorkerCount < 0, not thread safe or something wrong");
|
Log.e(TAG, "WTF, mWorkerCount < 0, not thread safe or something wrong");
|
||||||
mWorkerCount = 0;
|
mWorkerCount = 0;
|
||||||
}
|
}
|
||||||
finish = mWorkerCount <= 0;
|
finish = mWorkerCount == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finish) {
|
if (finish) {
|
||||||
@@ -1618,7 +1595,7 @@ public final class SpiderQueen implements Runnable {
|
|||||||
|
|
||||||
private void resetDecodeIndex() {
|
private void resetDecodeIndex() {
|
||||||
synchronized (mDecodeRequestQueue) {
|
synchronized (mDecodeRequestQueue) {
|
||||||
mDecodeIndexArray[mThreadIndex] = GalleryPageView.INVALID_INDEX;
|
mDecodeIndexArray[mThreadIndex] = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1678,8 +1655,8 @@ public final class SpiderQueen implements Runnable {
|
|||||||
|
|
||||||
if (is != null) {
|
if (is != null) {
|
||||||
try {
|
try {
|
||||||
image = Image.decode(((FileInputStream) is).getFD(), true);
|
image = Image.decode(((FileInputStream) is));
|
||||||
} catch (IOException e) {
|
} catch (ImageDecoder.DecodeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public final class CommonOperations {
|
|||||||
EhDB.putLocalFavorites(galleryInfo);
|
EhDB.putLocalFavorites(galleryInfo);
|
||||||
listener.onSuccess(null);
|
listener.onSuccess(null);
|
||||||
} else if (slot >= 0 && slot <= 9) {
|
} else if (slot >= 0 && slot <= 9) {
|
||||||
EhClient client = EhApplication.getEhClient(activity);
|
EhClient client = EhApplication.getEhClient();
|
||||||
EhRequest request = new EhRequest();
|
EhRequest request = new EhRequest();
|
||||||
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
|
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
|
||||||
request.setArgs(galleryInfo.gid, galleryInfo.token, slot, "");
|
request.setArgs(galleryInfo.gid, galleryInfo.token, slot, "");
|
||||||
@@ -100,7 +100,7 @@ public final class CommonOperations {
|
|||||||
public static void removeFromFavorites(Activity activity, GalleryInfo galleryInfo,
|
public static void removeFromFavorites(Activity activity, GalleryInfo galleryInfo,
|
||||||
final EhClient.Callback<Void> listener) {
|
final EhClient.Callback<Void> listener) {
|
||||||
EhDB.removeLocalFavorites(galleryInfo.gid);
|
EhDB.removeLocalFavorites(galleryInfo.gid);
|
||||||
EhClient client = EhApplication.getEhClient(activity);
|
EhClient client = EhApplication.getEhClient();
|
||||||
EhRequest request = new EhRequest();
|
EhRequest request = new EhRequest();
|
||||||
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
|
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
|
||||||
request.setArgs(galleryInfo.gid, galleryInfo.token, -1, "");
|
request.setArgs(galleryInfo.gid, galleryInfo.token, -1, "");
|
||||||
@@ -114,13 +114,13 @@ public final class CommonOperations {
|
|||||||
|
|
||||||
public static void startDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
public static void startDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
Objects.requireNonNull(EhApplication.getInstance().getTopActivity()).checkAndRequestNotificationPermission();
|
Objects.requireNonNull(EhApplication.getApplication().getTopActivity()).checkAndRequestNotificationPermission();
|
||||||
}
|
}
|
||||||
doStartDownload(activity, galleryInfos, forceDefault);
|
doStartDownload(activity, galleryInfos, forceDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void doStartDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
private static void doStartDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
||||||
final DownloadManager dm = EhApplication.getDownloadManager(activity);
|
final DownloadManager dm = EhApplication.getDownloadManager();
|
||||||
|
|
||||||
LongList toStart = new LongList();
|
LongList toStart = new LongList();
|
||||||
List<GalleryInfo> toAdd = new ArrayList<>();
|
List<GalleryInfo> toAdd = new ArrayList<>();
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import android.content.pm.PackageManager
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.content.res.Resources.Theme
|
import android.content.res.Resources.Theme
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
@@ -32,10 +31,8 @@ import androidx.annotation.StyleRes
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.hippo.ehviewer.EhApplication
|
import com.hippo.ehviewer.EhApplication
|
||||||
import com.hippo.ehviewer.EhApplication.locked_last_leave_time
|
|
||||||
import com.hippo.ehviewer.R
|
import com.hippo.ehviewer.R
|
||||||
import com.hippo.ehviewer.Settings
|
import com.hippo.ehviewer.Settings
|
||||||
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
|
|
||||||
import rikka.core.res.resolveColor
|
import rikka.core.res.resolveColor
|
||||||
import rikka.insets.WindowInsetsHelper
|
import rikka.insets.WindowInsetsHelper
|
||||||
import rikka.layoutinflater.view.LayoutInflaterFactory
|
import rikka.layoutinflater.view.LayoutInflaterFactory
|
||||||
@@ -72,24 +69,15 @@ abstract class EhActivity : AppCompatActivity() {
|
|||||||
WindowInsetsHelper.LISTENER
|
WindowInsetsHelper.LISTENER
|
||||||
)
|
)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val window = window
|
|
||||||
window.statusBarColor = Color.TRANSPARENT
|
|
||||||
window.decorView.post {
|
window.decorView.post {
|
||||||
val rootWindowInsets = window.decorView.rootWindowInsets
|
val rootWindowInsets = window.decorView.rootWindowInsets
|
||||||
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
|
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
|
||||||
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
|
|
||||||
}
|
|
||||||
window.navigationBarColor =
|
window.navigationBarColor =
|
||||||
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
|
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
window.isNavigationBarContrastEnforced = false
|
window.isNavigationBarContrastEnforced = false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
window.navigationBarColor = Color.TRANSPARENT
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
window.isNavigationBarContrastEnforced = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(application as EhApplication).registerActivity(this)
|
(application as EhApplication).registerActivity(this)
|
||||||
@@ -102,10 +90,10 @@ abstract class EhActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
val locked_resume_time = System.currentTimeMillis() / 1000
|
val locked_resume_time = System.currentTimeMillis() / 1000
|
||||||
val locked_delay_time = locked_resume_time - locked_last_leave_time
|
val locked_delay_time = locked_resume_time - EhApplication.locked_last_leave_time
|
||||||
if (Settings.getSecurity() && locked_delay_time >= Settings.getSecurityDelay() * 60
|
if (locked_delay_time < Settings.getSecurityDelay() * 60) {
|
||||||
&& isAuthenticationSupported(this) && EhApplication.locked
|
EhApplication.locked = false
|
||||||
) {
|
} else if (Settings.getSecurity() && isAuthenticationSupported() && EhApplication.locked) {
|
||||||
startActivity(Intent(this, SecurityActivity::class.java))
|
startActivity(Intent(this, SecurityActivity::class.java))
|
||||||
}
|
}
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* 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.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
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,6 @@ import com.hippo.ehviewer.ui.scene.DownloadsScene;
|
|||||||
import com.hippo.ehviewer.ui.scene.FavoritesScene;
|
import com.hippo.ehviewer.ui.scene.FavoritesScene;
|
||||||
import com.hippo.ehviewer.ui.scene.GalleryCommentsScene;
|
import com.hippo.ehviewer.ui.scene.GalleryCommentsScene;
|
||||||
import com.hippo.ehviewer.ui.scene.GalleryDetailScene;
|
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.GalleryListScene;
|
||||||
import com.hippo.ehviewer.ui.scene.GalleryPreviewsScene;
|
import com.hippo.ehviewer.ui.scene.GalleryPreviewsScene;
|
||||||
import com.hippo.ehviewer.ui.scene.HistoryScene;
|
import com.hippo.ehviewer.ui.scene.HistoryScene;
|
||||||
@@ -77,7 +76,7 @@ import com.hippo.scene.SceneFragment;
|
|||||||
import com.hippo.scene.StageActivity;
|
import com.hippo.scene.StageActivity;
|
||||||
import com.hippo.unifile.UniFile;
|
import com.hippo.unifile.UniFile;
|
||||||
import com.hippo.util.BitmapUtils;
|
import com.hippo.util.BitmapUtils;
|
||||||
import com.hippo.util.ClipboardUtil;
|
import com.hippo.util.ClipboardUtilKt;
|
||||||
import com.hippo.widget.DrawerView;
|
import com.hippo.widget.DrawerView;
|
||||||
import com.hippo.yorozuya.IOUtils;
|
import com.hippo.yorozuya.IOUtils;
|
||||||
import com.hippo.yorozuya.SimpleHandler;
|
import com.hippo.yorozuya.SimpleHandler;
|
||||||
@@ -101,7 +100,6 @@ public final class MainActivity extends StageActivity
|
|||||||
registerLaunchMode(SelectSiteScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
registerLaunchMode(SelectSiteScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||||
registerLaunchMode(GalleryListScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TOP);
|
registerLaunchMode(GalleryListScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TOP);
|
||||||
registerLaunchMode(GalleryDetailScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
registerLaunchMode(GalleryDetailScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||||
registerLaunchMode(GalleryInfoScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
|
||||||
registerLaunchMode(GalleryCommentsScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
registerLaunchMode(GalleryCommentsScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||||
registerLaunchMode(GalleryPreviewsScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
registerLaunchMode(GalleryPreviewsScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||||
registerLaunchMode(DownloadsScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
registerLaunchMode(DownloadsScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||||
@@ -138,7 +136,7 @@ public final class MainActivity extends StageActivity
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected Announcer getLaunchAnnouncer() {
|
protected Announcer getLaunchAnnouncer() {
|
||||||
if (EhUtils.needSignedIn(this)) {
|
if (EhUtils.needSignedIn()) {
|
||||||
return new Announcer(SignInScene.class);
|
return new Announcer(SignInScene.class);
|
||||||
} else if (Settings.getSelectSite()) {
|
} else if (Settings.getSelectSite()) {
|
||||||
return new Announcer(SelectSiteScene.class);
|
return new Announcer(SelectSiteScene.class);
|
||||||
@@ -152,7 +150,7 @@ public final class MainActivity extends StageActivity
|
|||||||
// Sometimes scene can't show directly
|
// Sometimes scene can't show directly
|
||||||
private Announcer processAnnouncer(Announcer announcer) {
|
private Announcer processAnnouncer(Announcer announcer) {
|
||||||
if (0 == getSceneCount()) {
|
if (0 == getSceneCount()) {
|
||||||
if (EhUtils.needSignedIn(this)) {
|
if (EhUtils.needSignedIn()) {
|
||||||
Bundle newArgs = new Bundle();
|
Bundle newArgs = new Bundle();
|
||||||
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
||||||
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.getArgs());
|
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.getArgs());
|
||||||
@@ -232,7 +230,6 @@ public final class MainActivity extends StageActivity
|
|||||||
builder.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
builder.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
||||||
builder.setImagePath(temp.getPath());
|
builder.setImagePath(temp.getPath());
|
||||||
builder.setUseSimilarityScan(true);
|
builder.setUseSimilarityScan(true);
|
||||||
builder.setShowExpunged(true);
|
|
||||||
startScene(processAnnouncer(GalleryListScene.getStartAnnouncer(builder)));
|
startScene(processAnnouncer(GalleryListScene.getStartAnnouncer(builder)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -259,7 +256,7 @@ public final class MainActivity extends StageActivity
|
|||||||
String url = intent.getData().toString();
|
String url = intent.getData().toString();
|
||||||
new EditTextDialogBuilder(this, url, "")
|
new EditTextDialogBuilder(this, url, "")
|
||||||
.setTitle(R.string.error_cannot_parse_the_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();
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -456,7 +453,7 @@ public final class MainActivity extends StageActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkClipboardUrlInternal() {
|
private void checkClipboardUrlInternal() {
|
||||||
String text = ClipboardUtil.getUrlFromClipboard();
|
String text = ClipboardUtilKt.getUrlFromClipboard(ClipboardUtilKt.getClipboardManager(this), this);
|
||||||
int hashCode = text != null ? text.hashCode() : 0;
|
int hashCode = text != null ? text.hashCode() : 0;
|
||||||
|
|
||||||
if (text != null && hashCode != 0 && Settings.getClipboardTextHashCode() != hashCode) {
|
if (text != null && hashCode != 0 && Settings.getClipboardTextHashCode() != hashCode) {
|
||||||
@@ -480,8 +477,7 @@ public final class MainActivity extends StageActivity
|
|||||||
|
|
||||||
@SuppressLint("RtlHardcoded")
|
@SuppressLint("RtlHardcoded")
|
||||||
public void createDrawerView(SceneFragment scene) {
|
public void createDrawerView(SceneFragment scene) {
|
||||||
if (scene instanceof BaseScene && mRightDrawer != null && mDrawerLayout != null) {
|
if (scene instanceof BaseScene baseScene && mRightDrawer != null && mDrawerLayout != null) {
|
||||||
BaseScene baseScene = (BaseScene) scene;
|
|
||||||
mRightDrawer.removeAllViews();
|
mRightDrawer.removeAllViews();
|
||||||
View drawerView = baseScene.createDrawerView(
|
View drawerView = baseScene.createDrawerView(
|
||||||
baseScene.getLayoutInflater(), mRightDrawer, null);
|
baseScene.getLayoutInflater(), mRightDrawer, null);
|
||||||
@@ -498,8 +494,7 @@ public final class MainActivity extends StageActivity
|
|||||||
public void onSceneViewDestroyed(SceneFragment scene) {
|
public void onSceneViewDestroyed(SceneFragment scene) {
|
||||||
super.onSceneViewDestroyed(scene);
|
super.onSceneViewDestroyed(scene);
|
||||||
|
|
||||||
if (scene instanceof BaseScene) {
|
if (scene instanceof BaseScene baseScene) {
|
||||||
BaseScene baseScene = (BaseScene) scene;
|
|
||||||
baseScene.destroyDrawerView();
|
baseScene.destroyDrawerView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,56 +30,83 @@ import androidx.fragment.app.FragmentActivity
|
|||||||
import com.hippo.ehviewer.EhApplication
|
import com.hippo.ehviewer.EhApplication
|
||||||
import com.hippo.ehviewer.R
|
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() {
|
class SecurityActivity : AppCompatActivity() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (isAuthenticationSupported(this))
|
if (isAuthenticationSupported())
|
||||||
startAuthentication(getString(R.string.settings_privacy_require_unlock))
|
startAuthentication(getString(R.string.settings_privacy_require_unlock))
|
||||||
else
|
else
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
private fun startAuthentication(
|
private fun startAuthentication(
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
confirmationRequired: Boolean = true,
|
confirmationRequired: Boolean = true,
|
||||||
) {
|
) {
|
||||||
|
if (isAuthenticating) return
|
||||||
|
isAuthenticating = true
|
||||||
startClass2BiometricOrCredentialAuthentication(
|
startClass2BiometricOrCredentialAuthentication(
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
confirmationRequired = confirmationRequired,
|
confirmationRequired = confirmationRequired,
|
||||||
executor = ContextCompat.getMainExecutor(this),
|
executor = ContextCompat.getMainExecutor(this),
|
||||||
callback = AuthenticationCallback()
|
callback = callback(
|
||||||
|
{ _, _, _ -> onFailed() },
|
||||||
|
{ _, _ -> onSuccess() },
|
||||||
|
{ onFailed() }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class AuthenticationCallback : AuthPromptCallback() {
|
inline fun callback(
|
||||||
override fun onAuthenticationSucceeded(
|
crossinline onAuthenticationError: (
|
||||||
|
activity: FragmentActivity?,
|
||||||
|
errorCode: Int,
|
||||||
|
errString: CharSequence
|
||||||
|
) -> Unit,
|
||||||
|
crossinline onAuthenticationSucceeded: (
|
||||||
activity: FragmentActivity?,
|
activity: FragmentActivity?,
|
||||||
result: BiometricPrompt.AuthenticationResult
|
result: BiometricPrompt.AuthenticationResult
|
||||||
) {
|
) -> Unit,
|
||||||
onSuccess()
|
crossinline onAuthenticationFailed: (
|
||||||
}
|
activity: FragmentActivity?
|
||||||
|
) -> Unit
|
||||||
|
) = object : AuthPromptCallback() {
|
||||||
override fun onAuthenticationError(
|
override fun onAuthenticationError(
|
||||||
activity: FragmentActivity?,
|
activity: FragmentActivity?,
|
||||||
errorCode: Int,
|
errorCode: Int,
|
||||||
errString: CharSequence
|
errString: CharSequence
|
||||||
) {
|
) = onAuthenticationError(activity, errorCode, errString)
|
||||||
moveTaskToBack(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
override fun onAuthenticationSucceeded(
|
||||||
fun isAuthenticationSupported(context: Context): Boolean {
|
activity: FragmentActivity?,
|
||||||
val authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
result: BiometricPrompt.AuthenticationResult
|
||||||
return BiometricManager.from(context)
|
) = onAuthenticationSucceeded(activity, result)
|
||||||
.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
|
|
||||||
}
|
override fun onAuthenticationFailed(activity: FragmentActivity?) =
|
||||||
|
onAuthenticationFailed(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSuccess() {
|
private fun onSuccess() {
|
||||||
|
isAuthenticating = false
|
||||||
EhApplication.locked = false
|
EhApplication.locked = false
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onFailed() {
|
||||||
|
moveTaskToBack(true)
|
||||||
|
isAuthenticating = false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var isAuthenticating = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,21 +18,29 @@ package com.hippo.ehviewer.ui
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.fragment.app.FragmentTransaction
|
import androidx.fragment.app.FragmentTransaction
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.hippo.ehviewer.R
|
import com.hippo.ehviewer.R
|
||||||
import com.hippo.ehviewer.ui.fragment.SettingsFragment
|
import com.hippo.ehviewer.ui.fragment.SettingsFragment
|
||||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||||
|
|
||||||
class SettingsActivity : EhActivity() {
|
class SettingsActivity : EhActivity() {
|
||||||
|
private var mFab: FloatingActionButton? = null
|
||||||
|
private var mContentText: TextView? = null
|
||||||
|
private var mAppbarLayout: AppBarLayout? = null
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_preference)
|
setContentView(R.layout.activity_preference)
|
||||||
setSupportActionBar(findViewById(R.id.toolbar))
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
val bar = supportActionBar
|
mContentText = findViewById(R.id.tip)
|
||||||
bar?.setDisplayHomeAsUpEnabled(true)
|
mFab = findViewById(R.id.fab)
|
||||||
|
mAppbarLayout = findViewById(R.id.appbar)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
|
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
|
||||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
|
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
@@ -57,9 +65,21 @@ class SettingsActivity : EhActivity() {
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
if (item.itemId == android.R.id.home) {
|
if (item.itemId == android.R.id.home) {
|
||||||
onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getMFab(): FloatingActionButton {
|
||||||
|
return mFab!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMContentText(): TextView {
|
||||||
|
return mContentText!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetAppbarLiftStatus() {
|
||||||
|
mAppbarLayout?.setExpanded(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package com.hippo.ehviewer.ui.fragment;
|
package com.hippo.ehviewer.ui.fragment;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -59,22 +59,19 @@ import java.io.FileInputStream;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
public class AdvancedFragment extends BasePreferenceFragment {
|
public class AdvancedFragment extends BasePreferenceFragment {
|
||||||
|
|
||||||
private static final String KEY_DUMP_LOGCAT = "dump_logcat";
|
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_APP_LANGUAGE = "app_language";
|
||||||
private static final String KEY_IMPORT_DATA = "import_data";
|
private static final String KEY_IMPORT_DATA = "import_data";
|
||||||
private static final String KEY_EXPORT_DATA = "export_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_OPEN_BY_DEFAULT = "open_by_default";
|
||||||
private static final String KEY_BACKUP_FAVORITE = "backup_favorite";
|
private static final String KEY_BACKUP_FAVORITE = "backup_favorite";
|
||||||
|
|
||||||
ActivityResultLauncher<String> exportLauncher = registerForActivityResult(
|
ActivityResultLauncher<String> exportLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.CreateDocument(),
|
new ActivityResultContracts.CreateDocument("application/vnd.sqlite3"),
|
||||||
uri -> {
|
uri -> {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
try {
|
try {
|
||||||
@@ -111,7 +108,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
ActivityResultLauncher<String> dumpLogcatLauncher = registerForActivityResult(
|
ActivityResultLauncher<String> dumpLogcatLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.CreateDocument(),
|
new ActivityResultContracts.CreateDocument("application/zip"),
|
||||||
uri -> {
|
uri -> {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
try {
|
try {
|
||||||
@@ -228,20 +225,21 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
private int favTotal;
|
||||||
|
private int favIndex;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.advanced_settings);
|
addPreferencesFromResource(R.xml.advanced_settings);
|
||||||
|
|
||||||
Preference dumpLogcat = findPreference(KEY_DUMP_LOGCAT);
|
Preference dumpLogcat = findPreference(KEY_DUMP_LOGCAT);
|
||||||
Preference clearMemoryCache = findPreference(KEY_CLEAR_MEMORY_CACHE);
|
|
||||||
Preference appLanguage = findPreference(KEY_APP_LANGUAGE);
|
Preference appLanguage = findPreference(KEY_APP_LANGUAGE);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||||
appLanguage.setVisible(false);
|
appLanguage.setVisible(false);
|
||||||
Preference importData = findPreference(KEY_IMPORT_DATA);
|
Preference importData = findPreference(KEY_IMPORT_DATA);
|
||||||
Preference exportData = findPreference(KEY_EXPORT_DATA);
|
Preference exportData = findPreference(KEY_EXPORT_DATA);
|
||||||
Preference openByDefault = findPreference(KEY_OPEN_BY_DEFAULT);
|
|
||||||
Preference backupFavorite = findPreference(KEY_BACKUP_FAVORITE);
|
Preference backupFavorite = findPreference(KEY_BACKUP_FAVORITE);
|
||||||
|
Preference openByDefault = findPreference(KEY_OPEN_BY_DEFAULT);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
openByDefault.setVisible(false);
|
openByDefault.setVisible(false);
|
||||||
@@ -250,7 +248,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dumpLogcat.setOnPreferenceClickListener(this);
|
dumpLogcat.setOnPreferenceClickListener(this);
|
||||||
clearMemoryCache.setOnPreferenceClickListener(this);
|
|
||||||
importData.setOnPreferenceClickListener(this);
|
importData.setOnPreferenceClickListener(this);
|
||||||
exportData.setOnPreferenceClickListener(this);
|
exportData.setOnPreferenceClickListener(this);
|
||||||
backupFavorite.setOnPreferenceClickListener(this);
|
backupFavorite.setOnPreferenceClickListener(this);
|
||||||
@@ -269,10 +266,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
|||||||
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
||||||
}
|
}
|
||||||
return true;
|
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)) {
|
} else if (KEY_IMPORT_DATA.equals(key)) {
|
||||||
try {
|
try {
|
||||||
importDataLauncher.launch(new String[]{"*/*"});
|
importDataLauncher.launch(new String[]{"*/*"});
|
||||||
@@ -289,9 +282,17 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
|||||||
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
} else if (KEY_BACKUP_FAVORITE.equals(key)) {
|
||||||
|
try {
|
||||||
|
backupFavorite();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ExceptionUtils.throwIfFatal(e);
|
||||||
|
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
|
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
|
||||||
try {
|
try {
|
||||||
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
@SuppressLint("InlinedApi") Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
||||||
Uri.parse("package:" + requireContext().getPackageName()));
|
Uri.parse("package:" + requireContext().getPackageName()));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
@@ -300,59 +301,69 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void backupFavorite() {
|
private void backupFavorite() {
|
||||||
AtomicInteger page = new AtomicInteger(0);
|
EhClient mClient = EhApplication.getEhClient();
|
||||||
Context context = requireContext();
|
|
||||||
Activity activity = requireActivity();
|
|
||||||
EhClient mClient = EhApplication.getEhClient(context);
|
|
||||||
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
|
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
|
||||||
favListUrlBuilder.setIndex(page.get());
|
favTotal = 0;
|
||||||
|
favIndex = 1;
|
||||||
|
|
||||||
EhRequest request = new EhRequest();
|
EhRequest request = new EhRequest();
|
||||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||||
request.setCallback(new EhClient.Callback() {
|
request.setCallback(new EhClient.Callback<FavoritesParser.Result>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Object result) {
|
public void onSuccess(FavoritesParser.Result result) {
|
||||||
try {
|
try {
|
||||||
FavoritesParser.Result result1 = (FavoritesParser.Result) result;
|
if (result.galleryInfoList.isEmpty()) {
|
||||||
EhDB.putLocalFavorites(result1.galleryInfoList);
|
showTip(R.string.settings_advanced_backup_favorite_nothing, BaseScene.LENGTH_SHORT);
|
||||||
Log.d("LocalFavorites", "now backup page " + page.get());
|
|
||||||
if (!result1.galleryInfoList.isEmpty()) {
|
|
||||||
favListUrlBuilder.setIndex(page.incrementAndGet());
|
|
||||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
|
||||||
mClient.execute(request);
|
|
||||||
} else {
|
} else {
|
||||||
showTip(R.string.backup_favorite_success, BaseScene.LENGTH_SHORT);
|
if (favTotal == 0 && result.countArray != null) {
|
||||||
|
int totalFav = 0;
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
totalFav = totalFav + result.countArray[i];
|
||||||
|
}
|
||||||
|
favTotal = (int) Math.ceil((double) totalFav / result.galleryInfoList.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
String status = "(" + favIndex + "/" + favTotal + ")";
|
||||||
|
showTip(GetText.getString(R.string.settings_advanced_backup_favorite_start, status), BaseScene.LENGTH_SHORT);
|
||||||
|
Log.d("LocalFavorites", "now backup page " + status);
|
||||||
|
EhDB.putLocalFavorites(result.galleryInfoList);
|
||||||
|
|
||||||
|
if (result.nextPage != null) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(Settings.getDownloadDelay());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
favIndex++;
|
||||||
|
favListUrlBuilder.setNext(result.nextPage);
|
||||||
|
request.setArgs(favListUrlBuilder.build());
|
||||||
|
mClient.execute(request);
|
||||||
|
} else {
|
||||||
|
showTip(R.string.settings_advanced_backup_favorite_success, BaseScene.LENGTH_SHORT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Exception e) {
|
public void onFailure(Exception e) {
|
||||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel() {
|
public void onCancel() {
|
||||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
|
||||||
|
request.setArgs(favListUrlBuilder.build());
|
||||||
mClient.execute(request);
|
mClient.execute(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 Tarsin Norbin
|
|
||||||
*
|
|
||||||
* This file is part of EhViewer
|
|
||||||
*
|
|
||||||
* EhViewer is free software: you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* EhViewer is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.hippo.ehviewer.ui.fragment;
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.ui.SettingsActivity;
|
|
||||||
|
|
||||||
public class BaseFragment extends Fragment {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
setTitle(getFragmentTitle());
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
public int getFragmentTitle() {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTitle(@StringRes int string) {
|
|
||||||
requireActivity().setTitle(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showTip(@StringRes int id, int length) {
|
|
||||||
((SettingsActivity) requireActivity()).showTip(getString(id), length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showTip(CharSequence message, int length) {
|
|
||||||
((SettingsActivity) requireActivity()).showTip(message, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Tarsin Norbin
|
||||||
|
*
|
||||||
|
* This file is part of EhViewer
|
||||||
|
*
|
||||||
|
* EhViewer is free software: you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* EhViewer is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.hippo.ehviewer.ui.fragment
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
import com.hippo.ehviewer.ui.SettingsActivity
|
||||||
|
|
||||||
|
abstract class BaseFragment : Fragment() {
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
setTitle(getFragmentTitle())
|
||||||
|
(requireActivity() as SettingsActivity).resetAppbarLiftStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
hideFabAndClearTip()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun getFragmentTitle(): Int
|
||||||
|
|
||||||
|
private fun setTitle(@StringRes string: Int) {
|
||||||
|
requireActivity().setTitle(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showTip(@StringRes id: Int, length: Int) {
|
||||||
|
(requireActivity() as SettingsActivity).showTip(getString(id), length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showTip(message: CharSequence?, length: Int) {
|
||||||
|
(requireActivity() as SettingsActivity).showTip(message, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTipView(@StringRes res: Int? = null): TextView {
|
||||||
|
return (requireActivity() as SettingsActivity).getMContentText().apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
res?.let { text = getString(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFabViewAndShow(): FloatingActionButton {
|
||||||
|
return (requireActivity() as SettingsActivity).getMFab().apply { visibility = View.VISIBLE }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideFabAndClearTip() {
|
||||||
|
getFabViewAndShow().apply {
|
||||||
|
visibility = View.GONE
|
||||||
|
setOnClickListener(null)
|
||||||
|
}
|
||||||
|
getTipView().apply {
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import android.content.res.Configuration
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
import com.hippo.ehviewer.EhApplication
|
||||||
import com.hippo.ehviewer.R
|
import com.hippo.ehviewer.R
|
||||||
import com.hippo.ehviewer.Settings
|
import com.hippo.ehviewer.Settings
|
||||||
import com.hippo.ehviewer.client.EhTagDatabase
|
import com.hippo.ehviewer.client.EhTagDatabase
|
||||||
@@ -55,7 +56,7 @@ class EhFragment : BasePreferenceFragment() {
|
|||||||
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
|
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
|
||||||
val key = preference.key
|
val key = preference.key
|
||||||
if (Settings.KEY_THEME == key) {
|
if (Settings.KEY_THEME == key) {
|
||||||
AppCompatDelegate.setDefaultNightMode(newValue as Int)
|
AppCompatDelegate.setDefaultNightMode((newValue as String).toInt())
|
||||||
requireActivity().recreate()
|
requireActivity().recreate()
|
||||||
return true
|
return true
|
||||||
} else if (Settings.KEY_GALLERY_SITE == key) {
|
} else if (Settings.KEY_GALLERY_SITE == key) {
|
||||||
@@ -78,7 +79,7 @@ class EhFragment : BasePreferenceFragment() {
|
|||||||
}
|
}
|
||||||
} else if (Settings.KEY_BLACK_DARK_THEME == key) {
|
} else if (Settings.KEY_BLACK_DARK_THEME == key) {
|
||||||
if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) {
|
if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) {
|
||||||
requireActivity().recreate()
|
EhApplication.application.recreateAllActivity()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 100 of 452 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user