Compare commits

..
141 Commits
Author SHA1 Message Date
tarsin aea75d44e3 Happy New Year 2023!
Releases / Build (push) Has been cancelled
2023-01-01 08:38:50 +08:00
tarsin 80481f1778 Update copyleft year 2023-01-01 08:37:32 +08:00
FooIbar 3ea7ff8202 Resolve #598 2022-12-31 19:37:33 +08:00
小白-白 b5979c0528 Fix comments covered by fab 2022-12-31 16:47:02 +08:00
小白-白 f0f79a52b8 Support filter comments
Resolve #516
2022-12-31 16:47:02 +08:00
小白-白 b549acc1de Add page number limit 2022-12-31 16:47:02 +08:00
小白-白 2505cc5d00 Don't show progress bar on last page 2022-12-31 16:47:02 +08:00
小白-白 d5af66b3fc Fix search bar set selection 2022-12-31 16:47:02 +08:00
小白-白 f680dc7e46 Fix copy trans 2022-12-31 16:47:02 +08:00
tarsin ffd8bbb1ce Refine missing comments 2022-12-31 12:38:50 +08:00
tarsin 216a043094 Set bar style correctly 2022-12-31 12:35:20 +08:00
tarsin 021eb5f828 Fix 3-button navigation 2022-12-31 12:27:47 +08:00
tarsin f3bf8849e9 Cleanup unused res 2022-12-31 11:48:58 +08:00
tarsin 128f7100d7 Minor cleanup 2022-12-31 11:19:34 +08:00
FooIbar 2cf53dd00f Disable landscape zoom by default 2022-12-30 17:27:01 +08:00
tarsin 4a674e46e6 Fixup! Handle drawer on back-pressed 2022-12-30 16:40:46 +08:00
tarsin f6b0134fdb Remove redundant comment 2022-12-30 14:33:20 +08:00
tarsin 675a6b863e Handle searchview backpress in separated callback 2022-12-30 13:43:39 +08:00
tarsin b570eeef93 Don't allow open drawer in search mode 2022-12-30 13:12:45 +08:00
tarsin 71a8b4e607 Handle drawer on back-pressed 2022-12-30 12:35:19 +08:00
renovate[bot] 89b866b0fc Update dependency gradle to v8.0-rc-1 2022-12-29 18:34:17 +00:00
tarsin bcce20d9ac Remove unnecessary okhttpclient rebuild 2022-12-29 21:18:47 +08:00
tarsin 420e760fb3 fixup! Convert spiderInfo cache to Coil DiskCache 2022-12-29 20:38:48 +08:00
FooIbar a168c95d79 fixup! Refactor getPageData 2022-12-29 20:28:16 +08:00
tarsin d8ccec45f9 Convert spiderInfo cache to Coil DiskCache 2022-12-29 20:27:33 +08:00
renovate[bot] 1316875602 Update plugin com.google.devtools.ksp to v1.8.0-1.0.8 2022-12-29 09:05:19 +08:00
FooIbar a85387c7c8 Add edit button for download label 2022-12-28 22:20:53 +08:00
renovate[bot] d357e83d06 Update plugin org.jetbrains.kotlin.android to v1.8.0 2022-12-28 14:17:07 +00:00
FooIbar 51546f6fea Add option for saving progress in quick search 2022-12-28 16:32:05 +08:00
tarsin b4aa5ec593 Revert "Get download dir in download mode only"
This reverts commit 241d9daae9.
2022-12-28 12:20:49 +08:00
tarsin 909380f592 Remove unused dirloader 2022-12-28 12:09:13 +08:00
tarsin d5f26643bf Support wide-color-gamut as config 2022-12-28 11:34:17 +08:00
tarsin a15dc4afdf Minor cleanup image decode codes 2022-12-28 09:57:50 +08:00
FooIbar b317bc334e Store GID in quick search name
Resolve #447
2022-12-27 15:44:02 +08:00
FooIbar d0a8d08b26 fixup! Support jump to last page 2022-12-27 15:43:01 +08:00
FooIbar f73a922a8e Cancel current task before saving instance state
Resolve #210
2022-12-26 20:33:19 +08:00
tarsin c6682ce718 Fix SwipeRefreshLayout theme 2022-12-26 19:01:35 +08:00
tarsin 764d1137ae Fixup sync with tachiyomi res 2022-12-26 17:29:02 +08:00
tarsin 02fc972a79 Remove confusing hint 2022-12-26 17:11:55 +08:00
tarsin 7fc727cf2e Resolve #579 2022-12-26 17:05:17 +08:00
FooIbar d77e1e6820 Remove redundant context parameter 2022-12-26 16:38:22 +08:00
FooIbar 241d9daae9 Get download dir in download mode only
Resolve? #381
2022-12-26 16:38:22 +08:00
FooIbar 462e0257a9 Save images to download location by default 2022-12-26 16:37:38 +08:00
renovate[bot] 66fbc7e821 Update dependency com.squareup.okhttp3:okhttp-bom to v5.0.0-alpha.11 2022-12-24 22:23:31 +00:00
FooIbar 3c37f22027 Use getBindingAdapterPosition() to get item position
Better solution for #201
2022-12-24 16:52:37 +08:00
tarsin 0d24bc6602 Thou shalt not neutralize nullability check 2022-12-24 12:20:55 +08:00
tarsin 5a50df8179 Upgrade dependencies 2022-12-24 11:53:58 +08:00
tarsin f1251fd150 Remove gradle properties enabled by default 2022-12-24 11:52:05 +08:00
tarsin 16202efc7b Allow obfuscate settings 2022-12-24 10:35:37 +08:00
tarsin 26c6052286 Rename .java to .kt 2022-12-24 10:35:37 +08:00
tarsin 7ce386a7b8 Cleanup OpenGL usage
Our bitmap/hwui may use vulkan ctx
2022-12-24 09:42:16 +08:00
tarsin e43cd0a039 Minor refactor 2022-12-23 19:12:39 +08:00
tarsin 70be405032 Free native source in advance if image is not Animated 2022-12-23 18:58:03 +08:00
tarsin 9f3ad8142a Remove largeHeap flag
Now that pixels are stored in GraphicBuffer, we no longer need large dalvik or native heap
2022-12-23 18:52:48 +08:00
FooIbar 51d9ece30c Init readingModeSettings before showing
Resolve #539
2022-12-22 21:45:07 +08:00
FooIbar d8e79f879f Fix missing space in copyright 2022-12-22 21:45:07 +08:00
FooIbar 76d50b56ee Show progress on goTo 2022-12-22 13:37:34 +08:00
renovate[bot] c8d1e957a1 Update plugin com.android.application to v8.0.0-alpha10 2022-12-22 03:14:33 +00:00
FooIbar b287af4824 Refactor getPageData 2022-12-21 18:28:52 +08:00
FooIbar 416ef8d6d7 fixup! Remove unused fab 2022-12-21 18:28:52 +08:00
renovate[bot] 7c71abb985 Update plugin com.google.devtools.ksp to v1.8.0-RC2-1.0.8 2022-12-21 11:37:05 +08:00
renovate[bot] 900f2462a7 Update plugin org.jetbrains.kotlin.android to v1.8.0-RC2 2022-12-20 18:56:50 +00:00
txyyh dd982c6cb9 Release 1.8.5.1
Releases / Build (push) Has been cancelled
2022-12-20 14:01:10 +00:00
tarsin 927a011c9d Upload mapping for release as well 2022-12-20 21:58:50 +08:00
tarsin ad0ad47a40 Support RTL Slider
But Idk whether we really support RTL :(
2022-12-20 21:30:20 +08:00
tarsin 71a424c15d Remove unused fab 2022-12-20 21:20:42 +08:00
FooIbar 145b7049a8 Support jump to last page
Resolve #466
2022-12-20 21:03:43 +08:00
tarsin 323d258bc2 Fix RTL load 2022-12-20 20:57:45 +08:00
tarsin 56ba52d7b9 Do not recycle bitmap in pager, add back lrucache
TODO: use coil instead
2022-12-20 20:43:15 +08:00
tarsin 54bd6f6ed8 Fix memleak for archive 2022-12-20 19:58:40 +08:00
tarsin f06167ba4b Sync with tachiyomi, support load progress 2022-12-20 19:03:57 +08:00
renovate[bot] 60438ae1ed Update dependency gradle to v8.0-milestone-6 (#562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-20 17:23:02 +08:00
FooIbar 1b87750b87 fixup! Fix isTranslatable (#555)
* Use appcompat to auto store locales

* fixup! Fix isTranslatable #553

* Code cleanup
2022-12-20 08:36:55 +08:00
FooIbar 40cdc566c8 Upload mapping files in ci 2022-12-20 08:33:56 +08:00
txyyh 428ae07e7f Release 1.8.5.0
Releases / Build (push) Has been cancelled
2022-12-19 14:59:35 +00:00
tarsin 481ad22b53 Code cleanup 2022-12-19 13:00:06 +08:00
FooIbar 65f58b93ed Update mCurrentIndex on page move
Resolve #538
2022-12-19 12:09:31 +08:00
FooIbar 45411fa14d Use notifyAll() instead of notify() on mDecodeRequestQueue
Resolve #549
2022-12-19 08:13:31 +08:00
tarsin 8ba1ba4fb1 fixup! Minor Refactor 2022-12-18 14:06:55 +08:00
tarsin 8427e1b3a5 fixup! Minor Refactor 2022-12-18 13:55:16 +08:00
tarsin 32f0f2a0cf Fix isTranslatable 2022-12-18 13:02:12 +08:00
tarsin 8b94997cde Minor Refactor 2022-12-18 12:52:30 +08:00
tarsin bd2d54f774 fixup! Rewrite search suggestion with kotlin flow, remove suggestion limit 2022-12-18 11:31:49 +08:00
FooIbar 34be285510 Do not add neighbors on page success
Might resolve #536 #540
2022-12-18 09:13:38 +08:00
FooIbar 7b906f3c3e Update tag database format 2022-12-18 07:55:37 +08:00
tarsin 82ba22cda2 Resolve #542 2022-12-17 14:24:24 +08:00
tarsin b1a9601ae9 fixup! Rewrite search suggestion with kotlin flow, remove suggestion limit 2022-12-17 13:08:08 +08:00
tarsin 7c1a297a99 Remove unused shared libs 2022-12-17 12:59:02 +08:00
FooIbar a8195c0e7c Map archive index with filename order
Try to resolve #427
2022-12-17 12:53:13 +08:00
FooIbar 08713e3922 Use SummaryProvider instead of String formatting marker 2022-12-17 12:53:13 +08:00
tarsin 0485d52e3e Rewrite search suggestion with kotlin flow, remove suggestion limit 2022-12-17 12:47:37 +08:00
tarsin 8b1723e19b Rename .java to .kt 2022-12-17 12:47:37 +08:00
tarsin 9cb683dc55 Fix crash when zoomed and try to swipe to next page 2022-12-16 22:07:09 +08:00
tarsin dce1589c19 Fix remaining request not cancelled for detached image 2022-12-16 21:35:28 +08:00
FooIbar 107cf16d36 Remove unused maven repo due to a677d95 2022-12-16 03:50:35 +08:00
FooIbar 2bab97c829 Better way to handle tag translation flag 2022-12-16 03:50:35 +08:00
tarsin 5242d825c2 Change location 2022-12-15 11:43:42 +08:00
FooIbar 11d3799b03 Change tag database format to json for better maintainability 2022-12-15 11:38:32 +08:00
FooIbar 55c7f6b896 Support tag suggestion for all languages 2022-12-15 06:43:50 +08:00
tarsin a09d607177 Do colorspace trans if needed 2022-12-14 17:24:19 +08:00
txyyh 626a101208 Rename Repository 2022-12-14 07:39:04 +00:00
tarsin f4ca3acbf6 Fix statusbar 2022-12-14 14:32:40 +08:00
tarsin 86835a3bd7 Remove toolbar and fix theme 2022-12-14 14:26:15 +08:00
tarsin 0ef46e2e11 Move image blobs to another repo 2022-12-14 12:24:17 +08:00
tarsin 04878447b7 Fixup translation import 2022-12-14 12:07:35 +08:00
tarsin 3669d5d90e Resolve #527 2022-12-14 11:25:06 +08:00
tarsin ee5e222a13 Resolve #525 2022-12-14 11:11:20 +08:00
tarsin 9ea44ff59a Remove blur when adjust color filter 2022-12-14 10:58:32 +08:00
tarsin 0474e2c0f5 Kill read settings in preference, clean up res 2022-12-14 10:39:06 +08:00
tarsin 4a909d4b93 Support readWithVolumeKeys 2022-12-14 10:32:46 +08:00
txyyh 8dbe24ab55 Release 1.8.5.0-preview
Releases / Build (push) Has been cancelled
2022-12-13 14:34:43 +00:00
tarsin 25f7b005cb Mark reader support wideColorGamut 2022-12-13 21:36:26 +08:00
tarsin 23f55ea208 Update gradle wrapper 2022-12-13 21:31:43 +08:00
tarsin 5f19fa0f1f Restore config 2022-12-13 21:18:11 +08:00
tarsin 58fbdcfcdd Sync tachiyomi reader translations 2022-12-13 21:02:54 +08:00
tarsin e33cca9fd9 Reformat code 2022-12-13 20:54:45 +08:00
tarsin b25e3d5454 Remove unused res 2022-12-13 20:51:01 +08:00
tarsin 583f272daa Remove unsupported setting 2022-12-13 20:46:22 +08:00
tarsin 2a83ad63be Inline DirectionalViewPager 2022-12-13 20:29:44 +08:00
tarsin 6fdc908ad1 Support save cursor 2022-12-13 18:48:48 +08:00
tarsin 5213caac49 Drop glgallery (#519)
* Neutralize GlGallery

* Enable dataBinding and viewBinding

* Add tachiyomi reader layout

* Cleanup Dead-code

* Rename .java to .kt

* Fix activity rename

* Nullability changes

* Add basic tachiyomi reader imageviews

* Fix size increase

* Let's cleanup later

* Add some tachiyomi webtoon widgets

* Import tachiyomi webtoon pager without chapter transition

* Remove rx dependency

* Rewrite GalleryProvider interface with flow

* groundwork for image load

* Refine webtoon pageholder

* Force us hw bitmap, refine recycle

* Strip tachiyomi decoder usage

* Keep our own SubsamplingScaleImageView, strip tachiyomi decoder to avoid apk bloated

* Import other PageViewers

* Add webtoon viewer config

* groundFix ReaderActivity

* Add tachiyomi Preferences

* Readpreference

* Fix fullscreen

* Move related class to tachiyomi package

* Fix pageload

* Add readerconfig

* Add ViewerConfig hook

* Add Reader bottomsheet settings

* Fix reader setting apply

* Refine

* Fix save share image

* Refine theme
2022-12-13 15:51:27 +08:00
FooIbar ca0518c747 Verify cookies through getProfile (#523)
Verify cookies through getProfile, Resolve? #283
2022-12-13 09:00:56 +08:00
FooIbar 298cd29537 Disable proxy when domain fronting is enabled (#522) 2022-12-13 08:26:53 +08:00
FooIbar 1a963a3239 Notify next items changed after items inserted (#520)
* Notify next items changed after items inserted, Resolve #201

* Code cleanup
2022-12-12 19:09:05 +08:00
FooIbar 683c8dc5c5 Fix onApplySearch getting called twice 2022-12-12 08:42:21 +08:00
FooIbar 2087970de6 Use MODE for favoriteSlot in HistoryInfo 2022-12-12 01:03:09 +08:00
FooIbar 5bd270fe6a Parse favoriteSlot for GalleryDetail 2022-12-12 01:03:09 +08:00
FooIbar 0992fcfcb0 Fix local favorites search 2022-12-11 20:11:30 +08:00
tarsin fc12395f7e Limit coil threads 2022-12-11 17:21:02 +08:00
tarsin 57b291a4e8 Checkout xz-utils to 5.2.9 2022-12-11 16:54:17 +08:00
tarsin 3adf391478 Checkout libarchive to 3.8.2 2022-12-11 16:49:57 +08:00
tarsin a859941845 Reformat Code 2022-12-11 12:26:22 +08:00
tarsin 8d1fc85638 Fix filter chip 2022-12-11 12:22:31 +08:00
tarsin 51ece1c472 Adjust block ips 2022-12-11 11:59:33 +08:00
tarsin 45f67ec37f Fix webtoon down sampling 2022-12-11 11:57:39 +08:00
FooIbar 85b559c989 Generate simpleLanguage for local favorites 2022-12-11 11:50:26 +08:00
FooIbar 1382050b5f Request UConfig on login 2022-12-11 11:50:26 +08:00
FooIbar fb970752fd Remove unused parameter 2022-12-11 11:50:26 +08:00
FooIbar 3d256a402f Auto close response to avoid resource leak 2022-12-11 11:50:26 +08:00
tarsin a677d95d34 Use mca 1.8.0beta01 2022-12-11 08:11:16 +08:00
FooIbar 452c0f2846 Use compose BOM instead 2022-12-10 07:36:24 +08:00
355 changed files with 17432 additions and 23473 deletions

No files matched your search

+7
View File
@@ -56,3 +56,10 @@ jobs:
with:
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
- name: Update mapping
uses: actions/upload-artifact@v3
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/release/mapping.txt
retention-days: 5
+1
View File
@@ -49,6 +49,7 @@ jobs:
app/build/outputs/apk/release/EhViewer-x86_64.apk
app/build/outputs/apk/release/EhViewer-x86.apk
app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
app/build/outputs/mapping/release/mapping.txt
- name: Collect Apks
run: |
+5 -5
View File
@@ -11,7 +11,7 @@
</p>
<h1 align="center">
<img src="art/launcher_icon-web.svg" width="200">
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200">
<br>EhViewer<br>
</h1>
@@ -66,12 +66,12 @@ with Material Design 3 and Dynamic Color Support
# Download
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="art/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Git it on F-Droid" src="art/get-it-on-fdroid.svg" width="200px"/></a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/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="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-fdroid.svg" width="200px"/></a>
# Screenshot
![screenshot-01](art/screenshot-01.png)
![screenshot-02](art/screenshot-02.png)
![screenshot-01](https://github.com/Ehviewer-Overhauled/Art/blob/master/screenshot-01.png)
![screenshot-02](https://github.com/Ehviewer-Overhauled/Art/blob/master/screenshot-02.png)
# Thanks
@@ -94,7 +94,7 @@ Here is the libraries
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
Copyright 2022 Tarsin Norbin
Copyright 2022-2023 Tarsin Norbin
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.
+19 -18
View File
@@ -31,8 +31,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 28
targetSdkVersion 33
versionCode 180026
versionName "1.8.4.0-alpha02"
versionCode 180030
versionName "1.8.5.2"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -52,10 +52,6 @@ android {
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"
]
@@ -94,6 +90,8 @@ android {
buildFeatures {
buildConfig true
compose true
dataBinding true
viewBinding true
}
composeOptions {
@@ -110,7 +108,7 @@ dependencies {
implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.annotation:annotation-experimental:1.3.0"
implementation "androidx.appcompat:appcompat:1.6.0-rc01"
implementation "androidx.appcompat:appcompat:1.7.0-alpha01"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
@@ -123,15 +121,14 @@ dependencies {
implementation "androidx.collection:collection:$collection_version"
implementation "androidx.collection:collection-ktx:$collection_version"
def compose_version = "1.3.1"
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
implementation "androidx.compose.runtime:runtime:$compose_version"
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material3:material3:1.0.1"
implementation platform("androidx.compose:compose-bom:2022.12.00")
implementation "androidx.compose.animation:animation"
implementation "androidx.compose.foundation:foundation"
implementation "androidx.compose.foundation:foundation-layout"
implementation "androidx.compose.material3:material3"
implementation "androidx.compose.runtime:runtime"
implementation "androidx.compose.runtime:runtime-saveable"
implementation "androidx.compose.ui:ui"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
@@ -182,9 +179,9 @@ dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.8.0-SNAPSHOT'
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.11'
implementation 'com.squareup.okhttp3:okhttp'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
@@ -195,6 +192,10 @@ dependencies {
implementation 'io.coil-kt:coil:2.2.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
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 {
+4 -9
View File
@@ -2,20 +2,15 @@
public static final ** CREATOR;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void check*(...);
public static void throw*(...);
}
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
-renamesourcefileattribute
-repackageclasses
-allowaccessmodification
-mergeinterfacesaggressively
-overloadaggressively
+15 -8
View File
@@ -14,7 +14,8 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
@@ -32,10 +33,6 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<application
android:name="com.hippo.ehviewer.EhApplication"
android:allowBackup="true"
@@ -45,12 +42,13 @@
android:fullBackupContent="@xml/backup_scheme"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:label="@string/app_name"
android:largeHeap="true"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:targetApi="tiramisu">
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
@@ -102,7 +100,7 @@
</activity>
<activity
android:name="com.hippo.ehviewer.ui.GalleryActivity"
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask">
@@ -160,6 +158,15 @@
android:taskAffinity=""
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
<service
android:name="com.hippo.ehviewer.download.DownloadService"
android:foregroundServiceType="dataSync"
+2 -2
View File
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
add_subdirectory(libarchive)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED archive.c image.c fdutils.c)
add_library(${PROJECT_NAME} SHARED archive.c fdutils.c)
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 -Wl,--gc-sections)
+53 -2
View File
@@ -43,6 +43,11 @@ typedef struct {
struct archive_entry *entry;
} archive_ctx;
typedef struct {
const char *filename;
int index;
} entry;
pthread_mutex_t ctx_lock;
static archive_ctx **ctx_pool;
#define CTX_POOL_SIZE 20
@@ -51,6 +56,8 @@ static bool need_encrypt = false;
static char *passwd = NULL;
static void *archiveAddr = NULL;
static size_t archiveSize = 0;
static entry *entries = NULL;
static size_t entryCount = 0;
#define POPULATE_THRESHOLD (512 * 1024 * 1024) // 512 MiB
@@ -77,6 +84,30 @@ static inline int filename_is_playable_file(const char *name) {
return false;
}
static inline int compare_entries(const void *a, const void *b) {
const char *fa = ((entry *) a)->filename;
const char *fb = ((entry *) b)->filename;
int a_len = (int) strlen(fa);
int b_len = (int) strlen(fb);
return a_len == b_len ? strcmp(fa, fb) : a_len - b_len;
}
static long archive_map_entries_index(archive_ctx *ctx) {
long count = 0;
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
const char *name = archive_entry_pathname(ctx->entry);
if (filename_is_playable_file(name)) {
entries[count].filename = strdup(name);
entries[count].index = count;
count++;
}
}
if (!count)
LOGE("%s", archive_error_string(ctx->arc));
qsort(entries, entryCount, sizeof(entry), compare_entries);
return count;
}
static long archive_list_all_entries(archive_ctx *ctx) {
long count = 0;
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
@@ -221,10 +252,9 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
if (r) {
if (r == -ENOMEM)
LOGE("%s", "Mem alloc failed");
r = 0;
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
} else {
r = archive_list_all_entries(ctx);
entryCount = archive_list_all_entries(ctx);
LOGI("%s%ld%s", "Found ", r, " image entries in archive");
// We must read through the file|vm then we can know whether it is encrypted
@@ -251,6 +281,19 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
}
}
archive_release_ctx(ctx);
r = archive_alloc_ctx(&ctx);
if (r) {
if (r == -ENOMEM)
LOGE("%s", "Mem alloc failed");
r = 0;
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
} else {
entries = calloc(entryCount, sizeof(entry));
r = archive_map_entries_index(ctx);
}
archive_release_ctx(ctx);
return r;
}
@@ -259,6 +302,7 @@ JNIEXPORT jobject JNICALL
Java_com_hippo_UriArchiveAccessor_extractToByteBuffer(JNIEnv *env, jobject thiz, jint index) {
EH_UNUSED(env);
EH_UNUSED(thiz);
index = entries[index].index;
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
@@ -295,6 +339,10 @@ Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
passwd = NULL;
need_encrypt = false;
munmap(archiveAddr, archiveSize);
for (int i = 0; i < entryCount; ++i) {
free((void *) entries[i].filename);
}
free(entries);
}
JNIEXPORT jboolean JNICALL
@@ -338,6 +386,7 @@ JNIEXPORT jstring JNICALL
Java_com_hippo_UriArchiveAccessor_getFilename(JNIEnv *env, jobject thiz, jint index) {
EH_UNUSED(env);
EH_UNUSED(thiz);
index = entries[index].index;
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
@@ -352,6 +401,7 @@ JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint index, jint fd) {
EH_UNUSED(env);
EH_UNUSED(thiz);
index = entries[index].index;
archive_ctx *ctx = NULL;
int ret;
ret = archive_get_ctx(&ctx, index);
@@ -363,6 +413,7 @@ Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint in
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_releaseByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) {
EH_UNUSED(thiz);
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
free(addr);
}
-143
View File
@@ -1,143 +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/bitmap.h>
#include <android/data_space.h>
#include <android/log.h>
#include <GLES2/gl2.h>
#include <jni.h>
#define TAG "ImageDecoder_wrapper"
#include "ehviewer.h"
#define IMAGE_TILE_MAX_SIZE (512 * 512)
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
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;
}
JNIEXPORT void JNICALL
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jobject bitmap, jboolean init,
jint offset_x, jint offset_y, jint width, jint height) {
if (width * height > IMAGE_TILE_MAX_SIZE)
return;
AndroidBitmapInfo info;
void *pixels = NULL;
AndroidBitmap_lockPixels(env, bitmap, &pixels);
AndroidBitmap_getInfo(env, bitmap, &info);
copy_pixels(pixels, info.width, info.height, offset_x, offset_y, tile_buffer, width, height, 0, 0, width, height);
AndroidBitmap_unlockPixels(env, bitmap);
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);
}
}
@@ -0,0 +1,25 @@
package androidx.swiperefreshlayout.widget
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.ShapeDrawable
import android.util.AttributeSet
import androidx.core.graphics.ColorUtils
import rikka.core.res.resolveColor
@SuppressLint("PrivateResource")
class ThemedSwipeRefreshLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : SwipeRefreshLayout(context, attrs) {
init {
val surfaceColor =
context.theme.resolveColor(com.google.android.material.R.attr.colorSurface)
val overlayColor =
context.getColor(com.google.android.material.R.color.m3_popupmenu_overlay_color)
val backgroundColor = ColorUtils.compositeColors(overlayColor, surfaceColor)
(mCircleView.background as ShapeDrawable).paint.color = backgroundColor
setColorSchemeColors(context.theme.resolveColor(com.google.android.material.R.attr.colorAccent))
}
}
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
}
@@ -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)
}
@@ -29,8 +29,6 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
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
@@ -40,4 +38,9 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
closeArchive()
pfd.close()
}
companion object {
@JvmStatic
external fun releaseByteBuffer(buffer: ByteBuffer)
}
}
@@ -250,21 +250,7 @@ public final class DiskLruCache implements Closeable {
private static String inputStreamToString(InputStream in) throws IOException {
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
} 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 void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
@@ -343,7 +329,21 @@ public final class DiskLruCache implements Closeable {
} else {
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
@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
import com.hippo.util.HashCodeUtils;
import java.io.File;
import java.io.IOException;
@@ -85,32 +86,13 @@ public class SimpleDiskCache {
try {
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(key.getBytes());
cacheKey = bytesToHexString(digest.digest());
cacheKey = HashCodeUtils.bytesToHexString(digest.digest());
} catch (final NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
/**
* http://stackoverflow.com/questions/332079
*
* @param bytes The bytes to convert.
* @return A {@link String} converted from the bytes of a hashable key used
* to store a filename on the disk, to hex digits.
*/
private static String bytesToHexString(final byte[] bytes) {
final StringBuilder builder = new StringBuilder();
for (final byte b : bytes) {
final String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
builder.append('0');
}
builder.append(hex);
}
return builder.toString();
}
public synchronized boolean isValid() {
return mDiskLruCache != null;
}
@@ -1,176 +0,0 @@
/*
* Copyright (C) 2014 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.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import com.hippo.yorozuya.MathUtils;
public class BatteryDrawable extends Drawable {
public static final int WARN_LIMIT = 15;
@SuppressWarnings("unused")
private static final String TAG = BatteryDrawable.class.getSimpleName();
private final Paint mPaint;
private final Rect mTopRect;
private final Rect mBottomRect;
private final Rect mRightRect;
private final Rect mHeadRect;
private final Rect mElectRect;
private int mColor = Color.WHITE;
private int mWarningColor = Color.RED;
private int mElect = -1;
private int mStart;
private int mStop;
public BatteryDrawable() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setStyle(Paint.Style.FILL);
mTopRect = new Rect();
mBottomRect = new Rect();
mRightRect = new Rect();
mHeadRect = new Rect();
mElectRect = new Rect();
updatePaint();
}
@Override
@SuppressWarnings("SuspiciousNameCombination")
protected void onBoundsChange(Rect bounds) {
int width = bounds.width();
int height = bounds.height();
int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f);
int turn1 = width * 6 / 7;
int turn2 = height / 3;
int secBottom = height - strokeWidth;
mStart = strokeWidth;
mStop = turn1 - strokeWidth;
mTopRect.set(0, 0, turn1, strokeWidth);
mBottomRect.set(0, secBottom, turn1, height);
mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom);
mHeadRect.set(turn1, turn2, width, height - turn2);
mElectRect.set(0, strokeWidth, mStop, secBottom);
}
/**
* How to draw:<br>
* |------------------------------|<br>
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
* |------------------------------|---|<br>
* |/////////////////| |//|\\\|<br>
* |/////////////////| |//|\\\|<br>
* |------------------------------|---|<br>
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
* |------------------------------|
*/
@Override
public void draw(Canvas canvas) {
if (mElect == -1) {
return;
}
mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f);
canvas.drawRect(mTopRect, mPaint);
canvas.drawRect(mBottomRect, mPaint);
canvas.drawRect(mRightRect, mPaint);
canvas.drawRect(mHeadRect, mPaint);
canvas.drawRect(mElectRect, mPaint);
}
private boolean isWarn() {
return mElect <= WARN_LIMIT;
}
public void setColor(int color) {
if (mColor == color) {
return;
}
mColor = color;
if (!isWarn()) {
mPaint.setColor(mColor);
invalidateSelf();
}
}
public void setWarningColor(int color) {
if (mWarningColor == color) {
return;
}
mWarningColor = color;
if (isWarn()) {
mPaint.setColor(mWarningColor);
invalidateSelf();
}
}
public void setElect(int elect) {
if (mElect == elect) {
return;
}
mElect = elect;
updatePaint();
}
public void setElect(int elect, boolean warn) {
if (mElect == elect) {
return;
}
mElect = elect;
updatePaint(warn);
}
private void updatePaint() {
updatePaint(isWarn());
}
private void updatePaint(boolean warn) {
if (warn) {
mPaint.setColor(mWarningColor);
} else {
mPaint.setColor(mColor);
}
invalidateSelf();
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
@@ -18,29 +18,30 @@ 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.os.LocaleListCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import coil.Coil
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
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.EhTagDatabase
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhX509TrustManager
import com.hippo.ehviewer.client.data.GalleryDetail
@@ -52,24 +53,31 @@ 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.IoThreadPoolExecutor
import com.hippo.util.ReadableTime
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IntIdGenerator
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
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() {
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
@@ -81,9 +89,10 @@ class EhApplication : SceneApplication() {
mActivityList.forEach { it.recreate() }
}
@OptIn(DelicateCoroutinesApi::class)
@SuppressLint("StaticFieldLeak")
override fun onCreate() {
ProcessLifecycleOwner.get().lifecycle.addObserver(EhlifecycleObserver())
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
application = this
val handler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
@@ -95,50 +104,50 @@ class EhApplication : SceneApplication() {
}
handler?.uncaughtException(t, e)
}
super.onCreate()
super<SceneApplication>.onCreate()
Native.initialize()
GetText.initialize(this)
Settings.initialize(this)
Settings.initialize()
ReadableTime.initialize(this)
AppConfig.initialize(this)
SpiderDen.initialize(this)
EhDB.initialize(this)
EhEngine.initialize()
BitmapUtils.initialize(this)
Coil.setImageLoader(ImageLoader.Builder(this).okHttpClient(okHttpClient).build())
EhTagDatabase.update()
// 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())
IoThreadPoolExecutor.getInstance().execute {
try {
val downloadLocation = Settings.getDownloadLocation()
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation)
} else {
CommonOperations.ensureNoMediaFile(downloadLocation)
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)
}
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
// Clear temp files
try {
clearTempDir()
} 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
theDawnOfNewDay()
}
private fun theDawnOfNewDay() {
private suspend fun theDawnOfNewDay() {
if (!Settings.getRequestNews()) {
return
}
@@ -150,17 +159,18 @@ class EhApplication : SceneApplication() {
EhCookieStore.KEY_IPD_PASS_HASH
)
) {
IoThreadPoolExecutor.getInstance().execute {
withContext(Dispatchers.IO) {
val referer = EhUrl.REFERER_E
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
val call = okHttpClient.newCall(request)
try {
val response = call.execute()
val responseBody = response.body
val body = responseBody.string()
val html = EventPaneParser.parse(body)
if (html != null) {
showEventPane(html)
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()
@@ -171,7 +181,7 @@ class EhApplication : SceneApplication() {
fun showEventPane(html: String) {
if (Settings.getHideHvEvents() && html.contains("You have encountered a monster!")) {
return;
return
}
val activity = topActivity
activity?.runOnUiThread {
@@ -242,12 +252,22 @@ class EhApplication : SceneApplication() {
mActivityList.remove(activity)
}
internal class EhlifecycleObserver : DefaultLifecycleObserver {
override fun onPause(owner: LifecycleOwner) {
if (!locked)
locked_last_leave_time = System.currentTimeMillis() / 1000
locked = true
}
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 {
@@ -263,7 +283,7 @@ class EhApplication : SceneApplication() {
val ehCookieStore by lazy { EhCookieStore(application) }
@JvmStatic
val ehClient by lazy { EhClient(application) }
val ehClient by lazy { EhClient() }
@JvmStatic
val ehProxySelector by lazy { EhProxySelector() }
@@ -273,7 +293,7 @@ class EhApplication : SceneApplication() {
val builder = OkHttpClient.Builder()
.cookieJar(ehCookieStore)
.cache(Cache(File(application.cacheDir, "http_cache"), 50L * 1024L * 1024L))
.dns(EhDns())
.dns(EhDns)
.proxySelector(ehProxySelector)
if (Settings.getDF()) {
@@ -292,10 +312,11 @@ class EhApplication : SceneApplication() {
trustManager = trustManagers[0] as X509TrustManager
} catch (e: Exception) {
e.printStackTrace()
trustManager = EhX509TrustManager()
trustManager = EhX509TrustManager
}
builder.sslSocketFactory(EhSSLSocketFactory(), trustManager)
builder.sslSocketFactory(EhSSLSocketFactory, trustManager)
builder.proxy(Proxy.NO_PROXY)
}
builder.build()
}
@@ -311,19 +332,20 @@ class EhApplication : SceneApplication() {
@JvmStatic
val spiderInfoCache by lazy {
SimpleDiskCache(
File(application.cacheDir, "spider_info"),
20 * 1024 * 1024
)
DiskCache.Builder().directory(File(application.cacheDir, "spider_info"))
.maxSizeBytes(20 * 1024 * 1024).build()
}
@JvmStatic
val downloadManager by lazy { DownloadManager(application) }
val downloadManager by lazy { DownloadManager() }
@JvmStatic
val hosts by lazy { Hosts(application, "hosts.db") }
@JvmStatic
val favouriteStatusRouter by lazy { FavouriteStatusRouter() }
@JvmStatic
val readerPreferences by lazy { ReaderPreferences(AndroidPreferenceStore(application)) }
}
}
+9 -17
View File
@@ -234,7 +234,7 @@ public class EhDB {
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
LocalFavoritesDao dao = db.localFavoritesDao();
List<LocalFavoriteInfo> list = dao.list();
List<LocalFavoriteInfo> list = dao.list("%" + query + "%");
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) {
QuickSearchDao dao = db.quickSearchDao();
dao.delete(quickSearch);
@@ -327,25 +322,22 @@ public class EhDB {
dao.update(list);
}
public static synchronized List<HistoryInfo> getHistoryList() {
return db.historyDao().list();
}
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
return db.historyDao().listLazy();
}
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
HistoryDao dao = db.historyDao();
HistoryInfo info = dao.load(galleryInfo.gid);
if (null != info) {
// Update time
info.time = System.currentTimeMillis();
HistoryInfo info;
if (galleryInfo instanceof HistoryInfo) {
info = (HistoryInfo) galleryInfo;
} else {
info = new HistoryInfo(galleryInfo);
}
info.time = System.currentTimeMillis();
if (null != dao.load(info.gid)) {
dao.update(info);
} else {
// New history
info = new HistoryInfo(galleryInfo);
info.time = System.currentTimeMillis();
dao.insert(info);
}
}
@@ -16,7 +16,6 @@
package com.hippo.ehviewer;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
@@ -27,19 +26,13 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.scene.GalleryListScene;
import com.hippo.glgallery.GalleryView;
import com.hippo.unifile.UniFile;
import com.hippo.util.ExceptionUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.NumberUtils;
import java.io.File;
import java.util.Locale;
import java.util.Set;
@@ -53,10 +46,7 @@ public class Settings {
public static final String KEY_ACCOUNT = "account";
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
public static final int THEME_LIGHT = 1;
public static final int THEME_SYSTEM = -1;
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_LIST_MODE = "list_mode";
public static final String KEY_DETAIL_SIZE = "detail_size";
@@ -67,8 +57,6 @@ public class Settings {
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_LANGUAGES = "excluded_languages";
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
/********************
****** Privacy and Security
********************/
@@ -87,45 +75,35 @@ public class Settings {
public static final String KEY_IMAGE_RESOLUTION = "image_size";
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
public static final String KEY_ENABLE_ANALYTICS = "enable_analytics";
/********************
****** Advanced
********************/
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
public static final String KEY_SECURITY = "require_unlock";
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
public static final String DEFAULT_SECURITY = "";
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
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_DOMAIN_FRONTING = "domain_fronting";
public static final String KEY_APP_LANGUAGE = "app_language";
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
private static final String 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 KEY_VERSION_CODE = "version_code";
private static final int DEFAULT_VERSION_CODE = 0;
private static final String KEY_DISPLAY_NAME = "display_name";
private static final String DEFAULT_DISPLAY_NAME = null;
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
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 boolean DEFAULT_REMOVE_IMAGE_FILES = true;
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
private static final boolean DEFAULT_NEED_SIGN_IN = true;
private static final String KEY_SELECT_SITE = "select_site";
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 boolean DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
private static final int DEFAULT_GALLERY_SITE = 1;
private static final String KEY_LAUNCH_PAGE = "launch_page";
private static final int DEFAULT_LAUNCH_PAGE = 0;
@@ -144,41 +122,6 @@ public class Settings {
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 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
********************/
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
private static final int DEFAULT_SCREEN_ROTATION = 0;
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 int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
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 boolean DEFAULT_KEEP_SCREEN_ON = false;
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
private static final boolean DEFAULT_SHOW_CLOCK = true;
private static final String KEY_SHOW_PROGRESS = "gallery_show_progress";
private static final boolean DEFAULT_SHOW_PROGRESS = true;
private static final String KEY_SHOW_BATTERY = "gallery_show_battery";
private static final boolean DEFAULT_SHOW_BATTERY = true;
private static final String KEY_SHOW_PAGE_INTERVAL = "gallery_show_page_interval";
private static final boolean DEFAULT_SHOW_PAGE_INTERVAL = true;
private static final String KEY_VOLUME_PAGE = "volume_page";
private static final boolean DEFAULT_VOLUME_PAGE = false;
private static final String KEY_REVERSE_VOLUME_PAGE = "reserve_volume_page";
private static final boolean DEFAULT_REVERSE_VOLUME_PAGE = false;
private static final String KEY_READING_FULLSCREEN = "reading_fullscreen";
private static final boolean VALUE_READING_FULLSCREEN = true;
private static final String KEY_CUSTOM_SCREEN_LIGHTNESS = "custom_screen_lightness";
private static final boolean DEFAULT_CUSTOM_SCREEN_LIGHTNESS = false;
private static final String KEY_SCREEN_LIGHTNESS = "screen_lightness";
private static final int DEFAULT_SCREEN_LIGHTNESS = 50;
private static final boolean DEFAULT_MEDIA_SCAN = false;
private static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label";
private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null;
@@ -192,8 +135,6 @@ public class Settings {
private static final int DEFAULT_PRELOAD_IMAGE = 5;
private static final String KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image";
private static final boolean DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false;
private static final String KEY_READ_THEME = "read_theme";
private static final int DEFAULT_READ_THEME = 1;
/********************
****** Favorites
********************/
@@ -235,46 +176,17 @@ public class Settings {
// -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 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 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 String KEY_SAVE_CRASH_LOG = "save_crash_log";
private static final boolean DEFAULT_SAVE_CRASH_LOG = true;
private static final boolean DEFAULT_BUILT_IN_HOSTS = false;
private static final boolean DEFAULT_FRONTING = false;
private static final String DEFAULT_APP_LANGUAGE = "system";
private static final String KEY_PROXY_TYPE = "proxy_type";
private static final int DEFAULT_PROXY_TYPE = EhProxySelector.TYPE_SYSTEM;
private static final String KEY_PROXY_IP = "proxy_ip";
private static final String DEFAULT_PROXY_IP = null;
private static final String KEY_PROXY_PORT = "proxy_port";
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 boolean DEFAULT_GUIDE_GALLERY = true;
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
private static final int DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0;
private static final String KEY_DOWNLOAD_DELAY = "download_delay";
@@ -282,20 +194,20 @@ public class Settings {
private static final String KEY_REQUEST_NEWS = "request_news";
private static final boolean DEFAULT_REQUEST_NEWS = true;
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
private static final String KEY_QS_SAVE_PROGRESS = "qs_save_progress";
private static final boolean DEFAULT_QS_SAVE_PROGRESS = true;
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 SharedPreferences sSettingsPre;
private static EhConfig sEhConfig;
public static void initialize(Context context) {
sContext = context.getApplicationContext();
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
public static void initialize() {
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(EhApplication.getApplication());
sEhConfig = loadEhConfig();
fixDefaultValue(context);
fixDefaultValue();
}
private static void fixDefaultValue(Context context) {
private static void fixDefaultValue() {
if ("CN".equals(Locale.getDefault().getCountry())) {
// Enable domain fronting if the country is CN
if (!sSettingsPre.contains(KEY_BUILT_IN_HOSTS)) {
@@ -312,16 +224,6 @@ public class Settings {
}
}
public static Locale getLocale() {
Locale locale = null;
String language = Settings.getAppLanguage();
if (language != null && !language.equals("system")) {
return Locale.forLanguageTag(language);
} else {
return Locale.getDefault();
}
}
private static EhConfig loadEhConfig() {
EhConfig ehConfig = new EhConfig();
ehConfig.imageSize = getImageResolution();
@@ -358,32 +260,6 @@ public class Settings {
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) {
try {
return sSettingsPre.getString(key, defValue);
@@ -434,22 +310,10 @@ public class Settings {
putString(KEY_DISPLAY_NAME, value);
}
public static String getAvatar() {
return getString(KEY_AVATAR, DEFAULT_AVATAR);
}
public static void putAvatar(String 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() {
return getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES);
}
@@ -482,14 +346,6 @@ public class Settings {
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() {
return getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE);
}
@@ -500,17 +356,13 @@ public class Settings {
public static String getLaunchPageGalleryListSceneAction() {
int value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE);
switch (value) {
default:
case 0:
return GalleryListScene.ACTION_HOMEPAGE;
case 1:
return GalleryListScene.ACTION_SUBSCRIPTION;
case 2:
return GalleryListScene.ACTION_WHATS_HOT;
case 3:
return GalleryListScene.ACTION_TOP_LIST;
}
return switch (value) {
case 0 -> GalleryListScene.ACTION_HOMEPAGE;
case 1 -> GalleryListScene.ACTION_SUBSCRIPTION;
case 2 -> GalleryListScene.ACTION_WHATS_HOT;
case 3 -> GalleryListScene.ACTION_TOP_LIST;
default -> throw new IllegalStateException("Unexpected value: " + value);
};
}
public static int getListMode() {
@@ -523,13 +375,11 @@ public class Settings {
@DimenRes
public static int getDetailSizeResId() {
switch (getDetailSize()) {
default:
case 0:
return R.dimen.gallery_list_column_width_long;
case 1:
return R.dimen.gallery_list_column_width_short;
}
return switch (getDetailSize()) {
case 0 -> R.dimen.gallery_list_column_width_long;
case 1 -> R.dimen.gallery_list_column_width_short;
default -> throw new IllegalStateException("Unexpected value: " + getDetailSize());
};
}
public static int getThumbSize() {
@@ -537,7 +387,7 @@ public class Settings {
}
public static int dip2px(int dpValue) {
final float scale = sContext.getResources().getDisplayMetrics().density;
final float scale = EhApplication.getApplication().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@@ -581,32 +431,14 @@ public class Settings {
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
}
public static void putDefaultCategories(int value) {
sEhConfig.defaultCategories = value;
sEhConfig.setDirty();
putInt(KEY_DEFAULT_CATEGORIES, value);
}
public static int getExcludedTagNamespaces() {
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() {
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() {
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
}
@@ -619,152 +451,20 @@ public class Settings {
putBoolean(KEY_APP_LINK_VERIFY_TIP, value);
}
public static int getScreenRotation() {
return getIntFromStr(KEY_SCREEN_ROTATION, DEFAULT_SCREEN_ROTATION);
}
public static void putScreenRotation(int 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) {
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) {
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) {
putIntToStr(KEY_START_POSITION, value);
}
public static boolean getKeepScreenOn() {
return getBoolean(KEY_KEEP_SCREEN_ON, DEFAULT_KEEP_SCREEN_ON);
}
public static void putKeepScreenOn(boolean value) {
putBoolean(KEY_KEEP_SCREEN_ON, value);
}
public static boolean getShowClock() {
return getBoolean(KEY_SHOW_CLOCK, DEFAULT_SHOW_CLOCK);
}
public static void putShowClock(boolean value) {
putBoolean(KEY_SHOW_CLOCK, value);
}
public static boolean getShowProgress() {
return getBoolean(KEY_SHOW_PROGRESS, DEFAULT_SHOW_PROGRESS);
}
public static void putShowProgress(boolean value) {
putBoolean(KEY_SHOW_PROGRESS, value);
}
public static boolean getShowBattery() {
return getBoolean(KEY_SHOW_BATTERY, DEFAULT_SHOW_BATTERY);
}
public static void putShowBattery(boolean value) {
putBoolean(KEY_SHOW_BATTERY, value);
}
public static boolean getShowPageInterval() {
return getBoolean(KEY_SHOW_PAGE_INTERVAL, DEFAULT_SHOW_PAGE_INTERVAL);
}
public static void putShowPageInterval(boolean value) {
putBoolean(KEY_SHOW_PAGE_INTERVAL, value);
}
public static boolean getVolumePage() {
return getBoolean(KEY_VOLUME_PAGE, DEFAULT_VOLUME_PAGE);
}
public static void putVolumePage(boolean value) {
putBoolean(KEY_VOLUME_PAGE, value);
}
public static boolean getReverseVolumePage() {
return getBoolean(KEY_REVERSE_VOLUME_PAGE, DEFAULT_REVERSE_VOLUME_PAGE);
}
public static void putReverseVolumePage(boolean value) {
putBoolean(KEY_REVERSE_VOLUME_PAGE, value);
}
public static boolean getReadingFullscreen() {
return getBoolean(KEY_READING_FULLSCREEN, VALUE_READING_FULLSCREEN);
}
public static void putReadingFullscreen(boolean value) {
putBoolean(KEY_READING_FULLSCREEN, value);
}
public static boolean getCustomScreenLightness() {
return getBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, DEFAULT_CUSTOM_SCREEN_LIGHTNESS);
}
public static void putCustomScreenLightness(boolean value) {
putBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, value);
}
public static int getScreenLightness() {
return getInt(KEY_SCREEN_LIGHTNESS, DEFAULT_SCREEN_LIGHTNESS);
}
public static void putScreenLightness(int value) {
putInt(KEY_SCREEN_LIGHTNESS, value);
}
public static int getReadTheme() {
return getIntFromStr(KEY_READ_THEME, DEFAULT_READ_THEME);
}
public static void putReadTheme(int value) {
putIntToStr(KEY_READ_THEME, value);
}
public static boolean getEnabledSecurity() {
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
}
public static void putEnabledSecurity(boolean value) {
putBoolean(KEY_READING_FULLSCREEN, value);
}
@Nullable
public static UniFile getDownloadLocation() {
UniFile dir = null;
try {
Uri.Builder builder = new Uri.Builder();
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
dir = UniFile.fromUri(sContext, builder.build());
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
UniFile dir;
Uri.Builder builder = new Uri.Builder();
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
dir = UniFile.fromUri(EhApplication.getApplication(), builder.build());
return dir != null ? dir : UniFile.fromFile(AppConfig.getDefaultDownloadDir());
}
@@ -815,26 +515,14 @@ public class Settings {
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() {
return getIntFromStr(KEY_DOWNLOAD_DELAY, DEFAULT_DOWNLOAD_DELAY);
}
public static void putDownloadDelay(int value) {
putIntToStr(KEY_DOWNLOAD_DELAY, value);
}
public static int getPreloadImage() {
return getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE);
}
public static void putPreloadImage(int value) {
putIntToStr(KEY_PRELOAD_IMAGE, value);
}
public static String getImageResolution() {
return getString(KEY_IMAGE_RESOLUTION, DEFAULT_IMAGE_RESOLUTION);
}
@@ -849,10 +537,6 @@ public class Settings {
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() {
String[] favCat = new String[10];
favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0);
@@ -947,100 +631,18 @@ public class Settings {
putInt(KEY_DEFAULT_FAV_SLOT, value);
}
public static boolean getAskAnalytics() {
return getBoolean(KEY_ASK_ANALYTICS, DEFAULT_ASK_ANALYTICS);
public static boolean getQSSaveProgress() {
return getBoolean(KEY_QS_SAVE_PROGRESS, DEFAULT_QS_SAVE_PROGRESS);
}
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 (!isValidUserID(userID)) {
writeXml = true;
// Get use ID from out sd card file
userID = FileUtils.read(file);
if (!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 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 void putQSSaveProgress(boolean value) {
putBoolean(KEY_QS_SAVE_PROGRESS, value);
}
public static boolean getSaveParseErrorBody() {
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() {
return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG);
}
@@ -1057,10 +659,6 @@ public class Settings {
return getInt(KEY_SECURITY_DELAY, 0);
}
public static boolean getEnableFingerprint() {
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
}
public static int getReadCacheSize() {
return getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE);
}
@@ -1081,14 +679,6 @@ public class Settings {
putBoolean(KEY_DOMAIN_FRONTING, value);
}
public static String getAppLanguage() {
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
}
public static void putAppLanguage(String value) {
putString(KEY_APP_LANGUAGE, value);
}
public static int getProxyType() {
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
}
@@ -1113,46 +703,6 @@ public class Settings {
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() {
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
}
public static void putGuideGallery(boolean value) {
putBoolean(KEY_GUIDE_GALLERY, value);
}
public static int getClipboardTextHashCode() {
return getInt(KEY_CLIPBOARD_TEXT_HASH_CODE, DEFAULT_CLIPBOARD_TEXT_HASH_CODE);
}
@@ -29,11 +29,12 @@ import androidx.browser.customtabs.CustomTabsIntent;
import com.hippo.ehviewer.client.EhUrlOpener;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
import com.hippo.scene.StageActivity;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
public final class UrlOpener {
private UrlOpener() {
@@ -56,19 +57,19 @@ public final class UrlOpener {
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
if (result != null) {
if (result.gid == galleryDetail.gid) {
intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(GalleryActivity.KEY_PAGE, result.page);
intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(ReaderActivity.KEY_PAGE, result.page);
context.startActivity(intent);
return;
}
} else if (url.startsWith("#c")) {
try {
intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(GalleryActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(ReaderActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
context.startActivity(intent);
return;
} catch (NumberFormatException e) {
@@ -16,7 +16,6 @@
package com.hippo.ehviewer.client;
import android.content.Context;
import android.os.AsyncTask;
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_DOWNLOAD_ARCHIVE = 18;
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 OkHttpClient mOkHttpClient;
public EhClient(Context context) {
public EhClient() {
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
mOkHttpClient = EhApplication.getOkHttpClient();
}
@SuppressWarnings("unchecked")
public void execute(EhRequest request) {
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());
request.task = task;
} else {
@@ -83,18 +82,17 @@ public class EhClient {
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 AtomicReference<Call> mCall = new AtomicReference<>();
private final AtomicBoolean mStop = new AtomicBoolean();
private Callback mCallback;
private EhConfig mEhConfig;
private Callback<Object> mCallback;
public Task(int method, Callback callback, EhConfig ehConfig) {
public Task(int method, Callback<Object> callback) {
mMethod = method;
mCallback = callback;
mEhConfig = ehConfig;
}
// Called in Job thread
@@ -107,10 +105,6 @@ public class EhClient {
}
}
public EhConfig getEhConfig() {
return mEhConfig;
}
public void stop() {
if (!mStop.get()) {
mStop.lazySet(true);
@@ -133,7 +127,6 @@ public class EhClient {
// Clear
mCallback = null;
mEhConfig = null;
mCall.lazySet(null);
}
}
@@ -141,53 +134,51 @@ public class EhClient {
@Override
protected Object doInBackground(Object... params) {
try {
switch (mMethod) {
case METHOD_SIGN_IN:
return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
case METHOD_GET_GALLERY_LIST:
return EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_GALLERY_DETAIL:
return EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_PREVIEW_SET:
return EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
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]);
case METHOD_GET_COMMENT_GALLERY:
return EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
case METHOD_GET_GALLERY_TOKEN:
return EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
case METHOD_GET_FAVORITES:
return EhEngine.getFavorites(this, mOkHttpClient, (String) params[0], (Boolean) params[1]);
case METHOD_ADD_FAVORITES:
return EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
case METHOD_ADD_FAVORITES_RANGE:
return EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
case METHOD_MODIFY_FAVORITES:
return EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2], (Boolean) params[3]);
case METHOD_GET_TORRENT_LIST:
return EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_GET_PROFILE:
return EhEngine.getProfile(this, mOkHttpClient);
case METHOD_VOTE_COMMENT:
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:
return EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
case METHOD_ARCHIVE_LIST:
return EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_DOWNLOAD_ARCHIVE:
return EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
case METHOD_VOTE_TAG:
return EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
default:
return new IllegalStateException("Can't detect method " + mMethod);
}
return switch (mMethod) {
case METHOD_SIGN_IN ->
EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
case METHOD_GET_GALLERY_LIST ->
EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_GALLERY_DETAIL ->
EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_PREVIEW_SET ->
EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_RATE_GALLERY ->
EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
case METHOD_GET_COMMENT_GALLERY ->
EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
case METHOD_GET_GALLERY_TOKEN ->
EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
case METHOD_GET_FAVORITES ->
EhEngine.getFavorites(this, mOkHttpClient, (String) params[0]);
case METHOD_ADD_FAVORITES ->
EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
case METHOD_ADD_FAVORITES_RANGE ->
EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
case METHOD_MODIFY_FAVORITES ->
EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2]);
case METHOD_GET_TORRENT_LIST ->
EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_GET_PROFILE -> EhEngine.getProfile(this, mOkHttpClient);
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]);
case METHOD_IMAGE_SEARCH ->
EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
case METHOD_ARCHIVE_LIST ->
EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_DOWNLOAD_ARCHIVE ->
EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
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]);
case METHOD_GET_UCONFIG -> EhEngine.getUConfig(this, mOkHttpClient);
default -> new IllegalStateException("Can't detect method " + mMethod);
};
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
return e;
}
}
@SuppressWarnings("unchecked")
@Override
protected void onPostExecute(Object result) {
if (mCallback != null) {
@@ -205,7 +196,6 @@ public class EhClient {
// Clear
mCallback = null;
mEhConfig = null;
mCall.lazySet(null);
}
}
@@ -22,7 +22,7 @@ import okhttp3.Dns
import java.net.InetAddress
import java.net.UnknownHostException
class EhDns : Dns {
object EhDns : Dns {
private val hosts = EhApplication.hosts
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
@@ -42,7 +42,7 @@ class EhDns : Dns {
put(
builtInHosts,
"e-hentai.org",
Pair("104.20.134.21", true),
Pair("104.20.134.21", false),
Pair("104.20.135.21", false),
Pair("172.67.0.127", false),
)
@@ -185,8 +185,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -263,8 +262,7 @@ public class EhEngine {
Headers headers = null;
GalleryListParser.Result result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -326,8 +324,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -354,8 +351,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -387,8 +383,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -429,8 +424,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -466,8 +460,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -510,8 +503,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -524,7 +516,7 @@ public class EhEngine {
}
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();
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
@@ -539,8 +531,7 @@ public class EhEngine {
Headers headers = null;
FavoritesParser.Result result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -592,8 +583,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -617,7 +607,7 @@ public class EhEngine {
}
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;
if (dstCat == -1) {
catStr = "delete";
@@ -648,8 +638,7 @@ public class EhEngine {
Headers headers = null;
FavoritesParser.Result result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -681,8 +670,7 @@ public class EhEngine {
Headers headers = null;
Pair<String, String>[] result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -712,8 +700,7 @@ public class EhEngine {
Headers headers = null;
Pair<String, Pair<String, String>[]> result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -754,8 +741,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -788,8 +774,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -816,8 +801,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -829,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,
long apiUid, String apiKey, long gid, String token, long commentId, int commentVote) throws Throwable {
final JSONObject json = new JSONObject();
@@ -857,8 +890,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -898,8 +930,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -962,8 +993,7 @@ public class EhEngine {
Headers headers = null;
GalleryListParser.Result result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
Log.d(TAG, "" + response.request().url());
@@ -997,8 +1027,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -1039,8 +1068,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -24,6 +24,8 @@ import com.hippo.ehviewer.dao.Filter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class EhFilter {
@@ -31,36 +33,38 @@ public final class EhFilter {
public static final int MODE_UPLOADER = 1;
public static final int MODE_TAG = 2;
public static final int MODE_TAG_NAMESPACE = 3;
public static final int MODE_COMMENTER = 4;
public static final int MODE_COMMENT = 5;
private static final String TAG = EhFilter.class.getSimpleName();
private static EhFilter sInstance;
private final List<Filter> mTitleFilterList = new ArrayList<>();
private final List<Filter> mUploaderFilterList = new ArrayList<>();
private final List<Filter> mTagFilterList = new ArrayList<>();
private final List<Filter> mTagNamespaceFilterList = new ArrayList<>();
private final List<Filter> mCommenterFilterList = new ArrayList<>();
private final List<Filter> mCommentFilterList = new ArrayList<>();
private EhFilter() {
List<Filter> list = EhDB.getAllFilter();
for (int i = 0, n = list.size(); i < n; i++) {
Filter filter = list.get(i);
switch (filter.mode) {
case MODE_TITLE:
case MODE_TITLE -> {
filter.text = filter.text.toLowerCase();
mTitleFilterList.add(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.add(filter);
break;
case MODE_TAG:
}
case MODE_TAG -> {
filter.text = filter.text.toLowerCase();
mTagFilterList.add(filter);
break;
case MODE_TAG_NAMESPACE:
}
case MODE_TAG_NAMESPACE -> {
filter.text = filter.text.toLowerCase();
mTagNamespaceFilterList.add(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
}
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
case MODE_COMMENT -> mCommentFilterList.add(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
}
}
@@ -88,30 +92,36 @@ public final class EhFilter {
return mTagNamespaceFilterList;
}
public List<Filter> getCommenterFilterList() {
return mCommenterFilterList;
}
public List<Filter> getCommentFilterList() {
return mCommentFilterList;
}
public synchronized boolean addFilter(Filter filter) {
// enable filter by default before it is added to database
filter.enable = true;
if (!EhDB.addFilter(filter)) return false;
switch (filter.mode) {
case MODE_TITLE:
case MODE_TITLE -> {
filter.text = filter.text.toLowerCase();
mTitleFilterList.add(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.add(filter);
break;
case MODE_TAG:
}
case MODE_TAG -> {
filter.text = filter.text.toLowerCase();
mTagFilterList.add(filter);
break;
case MODE_TAG_NAMESPACE:
}
case MODE_TAG_NAMESPACE -> {
filter.text = filter.text.toLowerCase();
mTagNamespaceFilterList.add(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
}
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
case MODE_COMMENT -> mCommentFilterList.add(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
return true;
}
@@ -124,21 +134,13 @@ public final class EhFilter {
EhDB.deleteFilter(filter);
switch (filter.mode) {
case MODE_TITLE:
mTitleFilterList.remove(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.remove(filter);
break;
case MODE_TAG:
mTagFilterList.remove(filter);
break;
case MODE_TAG_NAMESPACE:
mTagNamespaceFilterList.remove(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
case MODE_TITLE -> mTitleFilterList.remove(filter);
case MODE_TAG -> mTagFilterList.remove(filter);
case MODE_TAG_NAMESPACE -> mTagNamespaceFilterList.remove(filter);
case MODE_UPLOADER -> mUploaderFilterList.remove(filter);
case MODE_COMMENTER -> mCommenterFilterList.remove(filter);
case MODE_COMMENT -> mCommentFilterList.remove(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
}
@@ -272,4 +274,40 @@ public final class EhFilter {
return true;
}
public synchronized boolean filterCommenter(String commenter) {
if (null == commenter) {
return false;
}
List<Filter> filters = mCommenterFilterList;
if (filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && commenter.equals(filters.get(i).text)) {
return false;
}
}
}
return true;
}
public synchronized boolean filterComment(String comment) {
if (null == comment) {
return false;
}
List<Filter> filters = mCommentFilterList;
if (filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable) {
Pattern p = Pattern.compile(filters.get(i).text);
Matcher m = p.matcher(comment);
if (m.find()) return false;
}
}
}
return true;
}
}
@@ -23,7 +23,7 @@ import java.net.Socket
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
class EhSSLSocketFactory : SSLSocketFactory() {
object EhSSLSocketFactory : SSLSocketFactory() {
private val sslSocketFactory = getDefault() as SSLSocketFactory
override fun getDefaultCipherSuites(): Array<String> {
return sslSocketFactory.defaultCipherSuites
@@ -1,436 +0,0 @@
/*
* Copyright 2019 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import android.content.Context;
import android.util.Base64;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSource;
import okio.Okio;
public class EhTagDatabase {
private static final Map<String, String> NAMESPACE_TO_PREFIX = new HashMap<>();
// TODO more lock for different language
private static final Lock lock = new ReentrantLock();
private static volatile EhTagDatabase instance;
static {
NAMESPACE_TO_PREFIX.put("artist", "a:");
NAMESPACE_TO_PREFIX.put("cosplayer", "cos:");
NAMESPACE_TO_PREFIX.put("character", "c:");
NAMESPACE_TO_PREFIX.put("female", "f:");
NAMESPACE_TO_PREFIX.put("group", "g:");
NAMESPACE_TO_PREFIX.put("language", "l:");
NAMESPACE_TO_PREFIX.put("male", "m:");
NAMESPACE_TO_PREFIX.put("mixed", "x:");
NAMESPACE_TO_PREFIX.put("other", "o:");
NAMESPACE_TO_PREFIX.put("parody", "p:");
NAMESPACE_TO_PREFIX.put("reclass", "r:");
}
private final String name;
private final byte[] tags;
public EhTagDatabase(String name, BufferedSource source) throws IOException {
this.name = name;
String[] tmp;
StringBuilder buffer = new StringBuilder();
source.readInt();
for (String i : source.readUtf8().split("\n")) {
tmp = i.split("\r", 2);
buffer.append(tmp[0]);
buffer.append("\r");
try {
buffer.append(new String(Base64.decode(tmp[1], Base64.DEFAULT), StandardCharsets.UTF_8));
} catch (Exception e) {
buffer.append(tmp[1]);
}
buffer.append("\n");
}
byte[] b = buffer.toString().getBytes(StandardCharsets.UTF_8);
int totalBytes = b.length;
tags = new byte[totalBytes];
System.arraycopy(b, 0, tags, 0, totalBytes);
}
@Nullable
public static EhTagDatabase getInstance(Context context) {
if (isPossible(context)) {
return instance;
} else {
instance = null;
return null;
}
}
@Nullable
public static String namespaceToPrefix(String namespace) {
return NAMESPACE_TO_PREFIX.get(namespace);
}
private static String[] getMetadata(Context context) {
String[] metadata = context.getResources().getStringArray(R.array.tag_translation_metadata);
if (metadata.length == 4) {
return metadata;
} else {
return null;
}
}
public static boolean isPossible(Context context) {
return getMetadata(context) != null;
}
@Nullable
private static byte[] getFileContent(File file, int length) {
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
byte[] content = new byte[length];
source.readFully(content);
return content;
} catch (IOException e) {
return null;
}
}
@Nullable
private static byte[] getFileSha1(File file) {
try (InputStream is = new FileInputStream(file)) {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
int n;
byte[] buffer = new byte[4 * 1024];
while ((n = is.read(buffer)) != -1) {
digest.update(buffer, 0, n);
}
return digest.digest();
} catch (IOException | NoSuchAlgorithmException e) {
return null;
}
}
private static boolean equals(byte[] b1, byte[] b2) {
if (b1 == null && b2 == null) {
return true;
}
if (b1 == null || b2 == null) {
return false;
}
if (b1.length != b2.length) {
return false;
}
for (int i = 0; i < b1.length; i++) {
if (b1[i] != b2[i]) {
return false;
}
}
return true;
}
private static boolean checkData(File sha1File, File dataFile) {
byte[] s1 = getFileContent(sha1File, 20);
if (s1 == null) {
return false;
}
byte[] s2 = getFileSha1(dataFile);
if (s2 == null) {
return false;
}
return equals(s1, s2);
}
private static boolean save(OkHttpClient client, String url, File file) {
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
try (Response response = call.execute()) {
if (!response.isSuccessful()) {
return false;
}
ResponseBody body = response.body();
if (body == null) {
return false;
}
try (InputStream is = body.byteStream(); OutputStream os = new FileOutputStream(file)) {
IOUtils.copy(is, os);
}
return true;
} catch (Throwable t) {
t.printStackTrace();
ExceptionUtils.throwIfFatal(t);
return false;
}
}
public static void update(Context context) {
String[] urls = getMetadata(context);
if (urls == null || urls.length != 4) {
// Clear tags if it's not possible
instance = null;
return;
}
String sha1Name = urls[0];
String sha1Url = urls[1];
String dataName = urls[2];
String dataUrl = urls[3];
// Clear tags if name if different
EhTagDatabase tmp = instance;
if (tmp != null && !tmp.name.equals(dataName)) {
instance = null;
}
IoThreadPoolExecutor.getInstance().execute(() -> {
if (!lock.tryLock()) {
return;
}
try {
File dir = AppConfig.getFilesDir("tag-translations");
if (dir == null) {
return;
}
// Check current sha1 and current data
File sha1File = new File(dir, sha1Name);
File dataFile = new File(dir, dataName);
if (!checkData(sha1File, dataFile)) {
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
}
// Read current EhTagDatabase
if (instance == null && dataFile.exists()) {
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
instance = new EhTagDatabase(dataName, source);
} catch (IOException e) {
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
}
}
OkHttpClient client = EhApplication.getOkHttpClient();
// Save new sha1
File tempSha1File = new File(dir, sha1Name + ".tmp");
if (!save(client, sha1Url, tempSha1File)) {
FileUtils.delete(tempSha1File);
return;
}
// Check new sha1 and current data
if (checkData(tempSha1File, dataFile)) {
// The data is the same
FileUtils.delete(tempSha1File);
return;
}
// Save new data
File tempDataFile = new File(dir, dataName + ".tmp");
if (!save(client, dataUrl, tempDataFile)) {
FileUtils.delete(tempDataFile);
return;
}
// Check new sha1 and new data
if (!checkData(tempSha1File, tempDataFile)) {
FileUtils.delete(tempSha1File);
FileUtils.delete(tempDataFile);
return;
}
// Replace current sha1 and current data with new sha1 and new data
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
tempSha1File.renameTo(sha1File);
tempDataFile.renameTo(dataFile);
// Read new EhTagDatabase
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
instance = new EhTagDatabase(dataName, source);
} catch (IOException e) {
// Ignore
}
} finally {
lock.unlock();
}
});
}
public String getTranslation(String tag) {
return search(tags, tag.getBytes(StandardCharsets.UTF_8));
}
@Nullable
private String search(byte[] tags, byte[] tag) {
int low = 0;
int high = tags.length;
while (low < high) {
int start = (low + high) / 2;
// Look for the starting '\n'
while (start > -1 && tags[start] != '\n') {
start--;
}
start++;
// Look for the middle '\r'.
int middle = 1;
while (tags[start + middle] != '\r') {
middle++;
}
// Look for the ending '\n'
int end = middle + 1;
while (tags[start + end] != '\n') {
end++;
}
int compare;
int tagIndex = 0;
int curIndex = start;
for (; ; ) {
int tagByte = tag[tagIndex] & 0xff;
int curByte = tags[curIndex] & 0xff;
compare = tagByte - curByte;
if (compare != 0) {
break;
}
tagIndex++;
curIndex++;
if (tagIndex == tag.length && curIndex == start + middle) {
break;
}
if (tagIndex == tag.length) {
compare = -1;
break;
}
if (curIndex == start + middle) {
compare = 1;
break;
}
}
if (compare < 0) {
high = start - 1;
} else if (compare > 0) {
low = start + end + 1;
} else {
return new String(tags, start + middle + 1, end - middle - 1, StandardCharsets.UTF_8);
}
}
return null;
}
public ArrayList<Pair<String, String>> suggest(String keyword) {
return searchTag(tags, keyword);
}
private ArrayList<Pair<String, String>> searchTag(byte[] tags, String keyword) {
ArrayList<Pair<String, String>> searchHints = new ArrayList<>();
int begin = 0;
while (begin < tags.length - 1) {
int start = begin;
// Look for the starting '\n'
while (tags[start] != '\n') {
start++;
}
// Look for the middle '\r'.
int middle = 1;
while (tags[start + middle] != '\r') {
middle++;
}
// Look for the ending '\n'
int end = middle + 1;
while (tags[start + end] != '\n') {
end++;
}
begin = start + end;
byte[] hintBytes = new byte[end - middle - 1];
System.arraycopy(tags, start + middle + 1, hintBytes, 0, end - middle - 1);
String hint = new String(hintBytes, StandardCharsets.UTF_8);
byte[] tagBytes = new byte[middle];
System.arraycopy(tags, start + 1, tagBytes, 0, middle);
String tag = new String(tagBytes, StandardCharsets.UTF_8);
int index = tag.indexOf(':');
boolean keywordMatches;
if (index == -1 || index >= tag.length() - 1 || keyword.length() > 2) {
keywordMatches = containsIgnoreSpace(tag, keyword);
} else {
keywordMatches = containsIgnoreSpace(tag.substring(index + 1), keyword);
}
if (keywordMatches || containsIgnoreSpace(hint, keyword)) {
Pair<String, String> pair = new Pair<>(hint, tag);
if (!searchHints.contains(pair)) {
searchHints.add(pair);
}
}
if (searchHints.size() > 20) {
break;
}
}
return searchHints;
}
private boolean containsIgnoreSpace(String text, String key) {
return text.replace(" ", "").contains(key.replace(" ", ""));
}
}
@@ -0,0 +1,271 @@
/*
* Copyright 2019 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client
import android.content.Context
import com.hippo.ehviewer.AppConfig
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
import com.hippo.ehviewer.R
import com.hippo.util.ExceptionUtils
import com.hippo.util.HashCodeUtils
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IOUtils
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.BufferedSource
import okio.buffer
import okio.source
import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
object EhTagDatabase {
private lateinit var tags: JSONObject
private lateinit var tagList: ArrayList<Pair<String, String>>
fun isInitialized(): Boolean {
return this::tags.isInitialized && this::tagList.isInitialized
}
private fun updateData(source: BufferedSource) {
try {
val tmpTags = JSONObject(source.readString(StandardCharsets.UTF_8))
val tmpTagList = arrayListOf<Pair<String, String>>()
tmpTags.keys().forEachRemaining { prefix: String ->
val values = tmpTags.optJSONObject(prefix)
values?.let {
it.keys().forEachRemaining { tag: String ->
tmpTagList.add(Pair("$prefix:$tag", values.optString(tag)))
}
}
}
tags = tmpTags
tagList = tmpTagList
} catch (e: JSONException) {
e.printStackTrace()
}
}
fun getTranslation(prefix: String?, tag: String?): String? {
return tags.optJSONObject(prefix)?.optString(tag)?.trim()?.takeIf { it.isNotEmpty() }
}
/* Construct a cold flow for tag database suggestions */
fun suggestFlow(
keyword: String,
translate: Boolean,
exactly: Boolean = false
): Flow<Pair<String?, String>> = flow {
tagList.forEach {
val tag = it.first
val hint = if (translate) it.second else null
val index = tag.indexOf(':')
if (tag.substring(index + 1).trim() == keyword) {
if (exactly)
emit(Pair(hint, tag))
return@forEach
}
if (exactly)
return@forEach
val keywordMatches: Boolean =
if (index == -1 || index >= tag.length - 1 || keyword.length > 2) {
containsIgnoreSpace(tag, keyword)
} else {
containsIgnoreSpace(tag.substring(index + 1), keyword)
}
if (keywordMatches || containsIgnoreSpace(hint, keyword)) {
emit(Pair(hint, tag))
}
}
}
private fun containsIgnoreSpace(text: String?, key: String): Boolean {
return text != null && text.replace(" ", "").contains(key.replace(" ", ""))
}
private val NAMESPACE_TO_PREFIX = HashMap<String, String>().also {
it["artist"] = "a"
it["cosplayer"] = "cos"
it["character"] = "c"
it["female"] = "f"
it["group"] = "g"
it["language"] = "l"
it["male"] = "m"
it["mixed"] = "x"
it["other"] = "o"
it["parody"] = "p"
it["reclass"] = "r"
}
@JvmStatic
fun namespaceToPrefix(namespace: String): String? {
return NAMESPACE_TO_PREFIX[namespace]
}
private fun getMetadata(context: Context): Array<String>? {
return context.resources.getStringArray(R.array.tag_translation_metadata)
.takeIf { it.size == 4 }
}
fun isTranslatable(context: Context): Boolean {
return context.resources.getBoolean(R.bool.tag_translatable)
}
private fun getFileContent(file: File): String? {
try {
file.source().buffer().use { return it.readString(StandardCharsets.UTF_8) }
} catch (e: IOException) {
return null
}
}
private fun getFileSha1(file: File): String? {
try {
FileInputStream(file).use { stream ->
val digest = MessageDigest.getInstance("SHA-1")
var n: Int
val buffer = ByteArray(4 * 1024)
while (stream.read(buffer).also { n = it } != -1) {
digest.update(buffer, 0, n)
}
return HashCodeUtils.bytesToHexString(digest.digest())
}
} catch (e: IOException) {
return null
} catch (e: NoSuchAlgorithmException) {
return null
}
}
private fun checkData(sha1File: File, dataFile: File): Boolean {
val s1 = getFileContent(sha1File) ?: return false
val s2 = getFileSha1(dataFile) ?: return false
return s1 == s2
}
private fun save(client: OkHttpClient, url: String, file: File): Boolean {
val request: Request = Request.Builder().url(url).build()
val call = client.newCall(request)
try {
call.execute().use { response ->
if (!response.isSuccessful) {
return false
}
val body = response.body
body.byteStream()
.use { ins -> FileOutputStream(file).use { os -> IOUtils.copy(ins, os) } }
return true
}
} catch (t: Throwable) {
FileUtils.delete(file)
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
return false
}
}
@OptIn(DelicateCoroutinesApi::class)
@JvmStatic
fun update() {
GlobalScope.launch(Dispatchers.IO) {
updateInternal()
}
}
@Synchronized
private fun updateInternal() {
val urls = getMetadata(EhApplication.application)
urls?.let {
val sha1Name = urls[0]
val sha1Url = urls[1]
val dataName = urls[2]
val dataUrl = urls[3]
try {
val dir = AppConfig.getFilesDir("tag-translations")
checkNotNull(dir)
// Check current sha1 and current data
val sha1File = File(dir, sha1Name)
val dataFile = File(dir, dataName)
if (!checkData(sha1File, dataFile)) {
FileUtils.delete(sha1File)
FileUtils.delete(dataFile)
}
// Read current EhTagDatabase
if (!isInitialized() && dataFile.exists()) {
try {
dataFile.source().buffer().use { updateData(it) }
} catch (e: IOException) {
FileUtils.delete(sha1File)
FileUtils.delete(dataFile)
}
}
val client = okHttpClient
// Save new sha1
val tempSha1File = File(dir, "$sha1Name.tmp")
check(save(client, sha1Url, tempSha1File))
// Check new sha1 and current data
if (checkData(tempSha1File, dataFile)) {
// The data is the same
FileUtils.delete(tempSha1File)
return
}
// Save new data
val tempDataFile = File(dir, "$dataName.tmp")
check(save(client, dataUrl, tempDataFile))
// Check new sha1 and new data
if (!checkData(tempSha1File, tempDataFile)) {
FileUtils.delete(tempSha1File)
FileUtils.delete(tempDataFile)
return
}
// Replace current sha1 and current data with new sha1 and new data
FileUtils.delete(sha1File)
FileUtils.delete(dataFile)
tempSha1File.renameTo(sha1File)
tempDataFile.renameTo(dataFile)
// Read new EhTagDatabase
try {
dataFile.source().buffer().use { updateData(it) }
} catch (_: IOException) {
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}
@@ -16,7 +16,6 @@
package com.hippo.ehviewer.client;
import android.content.Context;
import android.graphics.Color;
import android.text.TextUtils;
@@ -49,10 +48,10 @@ public class EhUtils {
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
public static final Pattern PATTERN_TITLE_PREFIX = Pattern.compile(
"^(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*");
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*");
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
public static final Pattern PATTERN_TITLE_SUFFIX = Pattern.compile(
"(?:\\s+ch.[\\s\\d-]+)?(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*$",
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
Pattern.CASE_INSENSITIVE);
private static final int[] CATEGORY_VALUES = {
@@ -106,40 +105,29 @@ public class EhUtils {
}
public static int getCategoryColor(int category) {
switch (category) {
case EhConfig.DOUJINSHI:
return BG_COLOR_DOUJINSHI;
case EhConfig.MANGA:
return BG_COLOR_MANGA;
case EhConfig.ARTIST_CG:
return BG_COLOR_ARTIST_CG;
case EhConfig.GAME_CG:
return BG_COLOR_GAME_CG;
case EhConfig.WESTERN:
return BG_COLOR_WESTERN;
case EhConfig.NON_H:
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;
}
return switch (category) {
case EhConfig.DOUJINSHI -> BG_COLOR_DOUJINSHI;
case EhConfig.MANGA -> BG_COLOR_MANGA;
case EhConfig.ARTIST_CG -> BG_COLOR_ARTIST_CG;
case EhConfig.GAME_CG -> BG_COLOR_GAME_CG;
case EhConfig.WESTERN -> BG_COLOR_WESTERN;
case EhConfig.NON_H -> BG_COLOR_NON_H;
case EhConfig.IMAGE_SET -> BG_COLOR_IMAGE_SET;
case EhConfig.COSPLAY -> BG_COLOR_COSPLAY;
case EhConfig.ASIAN_PORN -> BG_COLOR_ASIAN_PORN;
case EhConfig.MISC -> BG_COLOR_MISC;
default -> BG_COLOR_UNKNOWN;
};
}
public static void signOut(Context context) {
public static void signOut() {
EhApplication.getEhCookieStore().signOut();
Settings.putAvatar(null);
Settings.putDisplayName(null);
Settings.putNeedSignIn(true);
}
public static boolean needSignedIn(Context context) {
public static boolean needSignedIn() {
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore().hasSignedIn();
}
@@ -22,7 +22,7 @@ import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
@SuppressLint("CustomX509TrustManager")
class EhX509TrustManager : X509TrustManager {
object EhX509TrustManager : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}
@@ -32,7 +32,7 @@ public class FavListUrlBuilder implements Parcelable {
public static final int FAV_CAT_ALL = -1;
public static final int FAV_CAT_LOCAL = -2;
public static final Parcelable.Creator<FavListUrlBuilder> CREATOR = new Parcelable.Creator<FavListUrlBuilder>() {
public static final Parcelable.Creator<FavListUrlBuilder> CREATOR = new Parcelable.Creator<>() {
@Override
public FavListUrlBuilder createFromParcel(Parcel source) {
@@ -66,12 +66,14 @@ public class FavListUrlBuilder implements Parcelable {
return favCat >= 0 && favCat <= 9;
}
public void setPrev(String prev) {
mPrev = prev;
}
public void setNext(String next) {
mNext = next;
public void setIndex(String index, boolean isNext) {
if (isNext) {
mNext = index;
mPrev = null;
} else {
mPrev = index;
mNext = null;
}
}
public void setJumpTo(String jumpTo) {
@@ -115,12 +117,6 @@ public class FavListUrlBuilder implements Parcelable {
e.printStackTrace();
}
}
// Name
ub.addQuery("sn", "on");
// Tags
ub.addQuery("st", "on");
// Note
ub.addQuery("sf", "on");
}
if (mPrev != null && !mPrev.isEmpty()) {
ub.addQuery("prev", mPrev);
@@ -36,8 +36,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
@get:Mode
@Mode
var mode = MODE_NORMAL
private var mPrevGid = 0
private var mNextGid = 0
private var mPrev: String? = null
private var mNext: String? = null
private var mJumpTo: String? = null
var category = EhUtils.NONE
private var mKeyword: String? = null
@@ -54,8 +54,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
constructor()
protected constructor(parcel: Parcel) {
mode = parcel.readInt()
mPrevGid = parcel.readInt()
mNextGid = parcel.readInt()
mPrev = parcel.readString()
mNext = parcel.readString()
mJumpTo = parcel.readString()
this.category = parcel.readInt()
mKeyword = parcel.readString()
@@ -72,8 +72,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
fun reset() {
mode = MODE_NORMAL
mPrevGid = 0
mNextGid = 0
mPrev = null
mNext = null
mJumpTo = null
this.category = EhUtils.NONE
mKeyword = null
@@ -96,12 +96,14 @@ open class ListUrlBuilder : Cloneable, Parcelable {
}
}
fun setPrevGid(prevGid: Int) {
mPrevGid = prevGid
}
fun setNextGid(nextGid: Int) {
mNextGid = nextGid
fun setIndex(index: String?, isNext: Boolean = true) {
if (isNext) {
mNext = index
mPrev = null
} else {
mPrev = index
mNext = null
}
}
fun setJumpTo(jumpTo: String?) {
@@ -121,8 +123,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
*/
fun set(lub: ListUrlBuilder) {
mode = lub.mode
mPrevGid = lub.mPrevGid
mNextGid = lub.mNextGid
mPrev = lub.mPrev
mNext = lub.mNext
mJumpTo = lub.mJumpTo
this.category = lub.category
mKeyword = lub.mKeyword
@@ -196,7 +198,7 @@ open class ListUrlBuilder : Cloneable, Parcelable {
if (TextUtils.isEmpty(query)) {
return
}
val querys = StringUtils.split(query, '&')
val queries = StringUtils.split(query, '&')
var category = 0
var keyword: String? = null
var enableAdvanceSearch = false
@@ -206,7 +208,7 @@ open class ListUrlBuilder : Cloneable, Parcelable {
var enablePage = false
var pageFrom = -1
var pageTo = -1
for (str in querys) {
for (str in queries) {
val index = str.indexOf('=')
if (index < 0) {
continue
@@ -348,19 +350,17 @@ open class ListUrlBuilder : Cloneable, Parcelable {
}
}
}
if (mSHash != null) {
ub.addQuery("f_shash", mSHash!!)
mSHash?.let {
ub.addQuery("f_shash", it)
}
mJumpTo?.let {
ub.addQuery("seek", mJumpTo!!)
mPrevGid = 0
mNextGid = 0
ub.addQuery("seek", it)
}
if (mPrevGid != 0) {
ub.addQuery("prev", mPrevGid)
mPrev?.let {
ub.addQuery("prev", it)
}
if (mNextGid != 0) {
ub.addQuery("next", mNextGid)
mNext?.let {
ub.addQuery("next", it)
}
// Advance search
if (advanceSearch != -1) {
@@ -393,14 +393,14 @@ open class ListUrlBuilder : Cloneable, Parcelable {
} catch (e: UnsupportedEncodingException) {
// Empty
}
if (mPrevGid != 0) {
sb.append("?prev=").append(mPrevGid)
mPrev?.let {
sb.append("?prev=").append(it)
}
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid)
mNext?.let {
sb.append("?next=").append(it)
}
if (mJumpTo != null) {
sb.append("&seek=").append(mJumpTo)
mJumpTo?.let {
sb.append("&seek=").append(it)
}
sb.toString()
}
@@ -413,14 +413,14 @@ open class ListUrlBuilder : Cloneable, Parcelable {
} catch (e: UnsupportedEncodingException) {
// Empty
}
if (mPrevGid != 0) {
sb.append("?prev=").append(mPrevGid)
mPrev?.let {
sb.append("?prev=").append(it)
}
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid)
mNext?.let {
sb.append("?next=").append(it)
}
if (mJumpTo != null) {
sb.append("&seek=").append(mJumpTo)
mJumpTo?.let {
sb.append("&seek=").append(it)
}
sb.toString()
}
@@ -433,61 +433,15 @@ open class ListUrlBuilder : Cloneable, Parcelable {
try {
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
e.printStackTrace()
}
if (mNextGid != 0) {
sb.append("&p=").append(mNextGid)
mJumpTo?.let {
sb.append("&p=").append(it)
}
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()
}
else -> throw java.lang.IllegalStateException("Unexpected value: $mode")
}
}
@@ -497,8 +451,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(mode)
dest.writeInt(mPrevGid)
dest.writeInt(mNextGid)
dest.writeString(mPrev)
dest.writeString(mNext)
dest.writeString(mJumpTo)
dest.writeInt(this.category)
dest.writeString(mKeyword)
@@ -31,22 +31,17 @@ import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FavoritesParser {
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+(-\\d+)?)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+(-\\d+)?)");
public static Result parse(String body) throws Exception {
if (body.contains("This page requires you to log on.</p>")) {
throw new EhException(GetText.getString(R.string.need_sign_in));
}
Document d = Jsoup.parse(body);
String[] catArray = new String[10];
int[] countArray = new int[10];
try {
Document d = Jsoup.parse(body);
Element ido = JsoupUtils.getElementByClass(d, "ido");
//noinspection ConstantConditions
Elements fps = ido.getElementsByClass("fp");
@@ -64,24 +59,13 @@ public class FavoritesParser {
throw new ParseException("Parse favorites error", body);
}
Result re = new Result();
try {
Element prev = d.getElementById("uprev");
Element next = d.getElementById("unext");
assert prev != null;
assert next != null;
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
if (matcherPrev.find()) re.prevPage = matcherPrev.group(1);
if (matcherNext.find()) re.nextPage = matcherNext.group(1);
} catch (Throwable e) {
e.printStackTrace();
}
GalleryListParser.Result result = GalleryListParser.parse(body);
Result re = new Result();
re.catArray = catArray;
re.countArray = countArray;
re.prev = result.prev;
re.next = result.next;
re.galleryInfoList = result.galleryInfoList;
return re;
@@ -90,8 +74,8 @@ public class FavoritesParser {
public static class Result {
public String[] catArray; // Size 10
public int[] countArray; // Size 10
public String prevPage;
public String nextPage;
public String prev;
public String next;
public List<GalleryInfo> galleryInfoList;
}
}
@@ -19,6 +19,8 @@ package com.hippo.ehviewer.client.parser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryCommentList;
@@ -67,6 +69,7 @@ public class GalleryDetailParser {
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_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 GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
@@ -78,6 +81,23 @@ public class GalleryDetailParser {
private static final String PINING_STRING =
"<p>This gallery is pining for the fjords.</p>";
private static final String[] LANGUAGES = {
"English",
"Chinese",
"Spanish",
"Korean",
"Russian",
"French",
"Portuguese",
"Thai",
"German",
"Italian",
"Vietnamese",
"Polish",
"Hungarian",
"Dutch",
};
public static GalleryDetail parse(String body) throws EhException {
if (body.contains(OFFENSIVE_STRING)) {
throw new OffensiveException();
@@ -100,6 +120,15 @@ public class GalleryDetailParser {
galleryDetail.comments = parseComments(document);
galleryDetail.previewPages = parsePreviewPages(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;
}
@@ -191,14 +220,9 @@ public class GalleryDetailParser {
gd.size = "";
gd.pages = 0;
gd.favoriteCount = 0;
try {
Elements es = gdd.child(0).child(0).children();
for (int i = 0, n = es.size(); i < n; i++) {
parseDetailInfo(gd, es.get(i));
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
Elements es = gdd.child(0).child(0).children();
for (int i = 0, n = es.size(); i < n; i++) {
parseDetailInfo(gd, es.get(i));
}
// Rating count
@@ -230,15 +254,24 @@ public class GalleryDetailParser {
// isFavorited
Element gdf = gm.getElementById("gdf");
gd.isFavorited = null != gdf && !StringUtils.trim(gdf.text()).equals("Add to Favorites");
gd.isFavorited = false;
if (gdf != null) {
final String favoriteName = StringUtils.trim(gdf.text());
if (favoriteName.equals("Add to Favorites")) {
gd.favoriteName = null;
} else {
gd.isFavorited = true;
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) {
ExceptionUtils.throwIfFatal(e);
throw new ParseException("Can't parse gallery detail", body);
@@ -441,6 +474,13 @@ public class GalleryDetailParser {
comment.user = c3.child(0).text();
// comment
comment.comment = JsoupUtils.getElementByClass(element, "c6").html();
// filter comment
if (!comment.uploader) {
EhFilter sEhFilter = EhFilter.getInstance();
if (!sEhFilter.filterCommenter(comment.user) || !sEhFilter.filterComment(comment.comment)) {
return null;
}
}
// last edited
Element c8 = JsoupUtils.getElementByClass(element, "c8");
if (c8 != null) {
@@ -50,8 +50,9 @@ public class GalleryListParser {
private static final Pattern PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px");
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("background-color:rgba\\((\\d+),(\\d+),(\\d+),");
private static final Pattern PATTERN_PAGES = Pattern.compile("(\\d+) page");
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("p=(\\d+)");
private static final Pattern PATTERN_PREV = Pattern.compile("prev=(\\d+(-\\d+)?)");
private static final Pattern PATTERN_NEXT = Pattern.compile("next=(\\d+(-\\d+)?)");
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
new String[]{"0", "0", "0"},
@@ -256,11 +257,7 @@ public class GalleryListParser {
if (div != null) {
gi.disowned = div.attr("style").contains("opacity:0.5");
Element a = div.children().first();
if (a != null) {
gi.uploader = a.text().trim();
} else {
gi.uploader = div.text().trim();
}
gi.uploader = a == null ? div.text().trim() : a.text().trim();
}
}
if (children.size() > pagesIndex) {
@@ -296,28 +293,44 @@ public class GalleryListParser {
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"));
Matcher matcherPrev = PATTERN_PREV.matcher(prev.attr("href"));
Matcher matcherNext = PATTERN_NEXT.matcher(next.attr("href"));
if (matcherPrev.find())
result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
result.prev = matcherPrev.group(1);
if (matcherNext.find())
result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
result.founds = Integer.MAX_VALUE;
result.next = matcherNext.group(1);
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
if (body.contains("No hits found</p>")) {
result.founds = 0;
//noinspection unchecked
result.galleryInfoList = Collections.EMPTY_LIST;
return result;
} else if (d.getElementsByClass("ptt").isEmpty()) {
result.founds = 1;
} else {
result.founds = Integer.MAX_VALUE;
}
}
try { // For toplists
Element ptt = d.getElementsByClass("ptt").first();
if (ptt != null) {
Elements es = ptt.child(0).child(0).children();
result.pages = Integer.parseInt(es.get(es.size() - 2).text().trim());
Element e = es.get(es.size() - 1);
if (e != null) {
e = e.children().first();
if (e != null) {
String href = e.attr("href");
Matcher matcher = PATTERN_NEXT_PAGE.matcher(href);
if (matcher.find()) {
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0);
}
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
try {
Element itg = d.getElementsByClass("itg").first();
Elements es;
@@ -350,9 +363,10 @@ public class GalleryListParser {
}
public static class Result {
public int founds;
public int prevGid;
public int nextGid;
public int pages;
public int nextPage;
public String prev;
public String next;
public boolean noWatchedTags;
public List<GalleryInfo> galleryInfoList;
}
@@ -20,10 +20,11 @@ public class HistoryInfo extends GalleryInfo {
return new HistoryInfo[size];
}
};
@ColumnInfo(name = "MODE")
public int mode;
@ColumnInfo(name = "TIME")
public long time;
// Use MODE for favoriteSlot
@ColumnInfo(name = "MODE")
private int mode;
protected HistoryInfo(Parcel in) {
super(in);
@@ -43,10 +44,11 @@ public class HistoryInfo extends GalleryInfo {
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
this.favoriteSlot = galleryInfo.favoriteSlot;
}
public HistoryInfo(int mode, long time) {
this.mode = mode;
this.favoriteSlot = mode - 2;
this.time = time;
}
@@ -131,11 +133,11 @@ public class HistoryInfo extends GalleryInfo {
}
public int getMode() {
return mode;
return favoriteSlot + 2;
}
public void setMode(int mode) {
this.mode = mode;
this.favoriteSlot = mode - 2;
}
public long getTime() {
@@ -44,6 +44,7 @@ public class LocalFavoriteInfo extends GalleryInfo {
}
public LocalFavoriteInfo() {
this.favoriteSlot = -1;
}
public long getGid() {
@@ -17,7 +17,6 @@
package com.hippo.ehviewer.download;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
@@ -44,7 +43,6 @@ import com.hippo.yorozuya.collect.SparseJLArray;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@@ -55,8 +53,7 @@ import java.util.Map;
public class DownloadManager implements SpiderQueen.OnSpiderListener {
private static final String TAG = DownloadManager.class.getSimpleName();
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = (lhs, rhs) -> lhs.time - rhs.time > 0 ? -1 : 1;
private final Context mContext;
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = (lhs, rhs) -> (int) (rhs.time - lhs.time);
// All download info list
private final LinkedList<DownloadInfo> mAllInfoList;
// All download info map
@@ -79,8 +76,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
@Nullable
private SpiderQueen mCurrentSpider;
public DownloadManager(Context context) {
mContext = context;
public DownloadManager() {
// Get all labels
List<DownloadLabel> labels = EhDB.getAllDownloadLabelList();
@@ -206,7 +202,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Get download from wait list
if (!mWaitList.isEmpty()) {
DownloadInfo info = mWaitList.removeFirst();
SpiderQueen spider = SpiderQueen.obtainSpiderQueen(mContext, info, SpiderQueen.MODE_DOWNLOAD);
SpiderQueen spider = SpiderQueen.obtainSpiderQueen(info, SpiderQueen.MODE_DOWNLOAD);
mCurrentTask = info;
mCurrentSpider = spider;
spider.addOnSpiderListener(this);
@@ -387,7 +383,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
list.add(info);
// Sort
Collections.sort(list, DATE_DESC_COMPARATOR);
list.sort(DATE_DESC_COMPARATOR);
// Add to all download list and map
mAllInfoList.add(info);
@@ -398,7 +394,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Sort all download list
Collections.sort(mAllInfoList, DATE_DESC_COMPARATOR);
mAllInfoList.sort(DATE_DESC_COMPARATOR);
// Notify
if (notify) {
@@ -730,7 +726,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
srcList.remove(info);
dstList.add(info);
info.label = label;
Collections.sort(dstList, DATE_DESC_COMPARATOR);
dstList.sort(DATE_DESC_COMPARATOR);
// Save to DB
EhDB.putDownloadInfo(info);
@@ -830,7 +826,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Sort
Collections.sort(mDefaultInfoList, DATE_DESC_COMPARATOR);
mDefaultInfoList.sort(DATE_DESC_COMPARATOR);
// Notify listener
for (DownloadInfoListener l : mDownloadInfoListeners) {
@@ -1052,7 +1048,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
@Override
public void run() {
switch (mType) {
case TYPE_ON_GET_PAGES: {
case TYPE_ON_GET_PAGES -> {
DownloadInfo info = mCurrentTask;
if (info == null) {
Log.e(TAG, "Current task is null, but it should not be");
@@ -1065,19 +1061,15 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
}
}
break;
}
case TYPE_ON_GET_509: {
case TYPE_ON_GET_509 -> {
if (mDownloadListener != null) {
mDownloadListener.onGet509();
}
break;
}
case TYPE_ON_PAGE_DOWNLOAD: {
mSpeedReminder.onDownload(mIndex, mContentLength, mReceivedSize, mBytesRead);
break;
}
case TYPE_ON_PAGE_SUCCESS: {
case TYPE_ON_PAGE_DOWNLOAD ->
mSpeedReminder.onDownload(mIndex, mContentLength, mReceivedSize, mBytesRead);
case TYPE_ON_PAGE_SUCCESS -> {
mSpeedReminder.onDone(mIndex);
DownloadInfo info = mCurrentTask;
if (info == null) {
@@ -1096,9 +1088,8 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
}
}
break;
}
case TYPE_ON_PAGE_FAILURE: {
case TYPE_ON_PAGE_FAILURE -> {
mSpeedReminder.onDone(mIndex);
DownloadInfo info = mCurrentTask;
if (info == null) {
@@ -1114,9 +1105,8 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
}
}
break;
}
case TYPE_ON_FINISH: {
case TYPE_ON_FINISH -> {
mSpeedReminder.onFinish();
// Download done
DownloadInfo info = mCurrentTask;
@@ -1159,7 +1149,6 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Start next download
ensureDownload();
break;
}
}
@@ -46,7 +46,9 @@ import java.util.Set;
import java.util.Stack;
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();
public static Handler showPasswd;
public static String passwd;
@@ -61,10 +63,10 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
new Thread(new DecodeTask())
};
private Thread archiveThread;
private volatile int size = STATE_WAIT;
private volatile int size = PageLoader.STATE_WAIT;
private String error;
public ArchiveGalleryProvider(Context context, Uri uri) {
public ArchivePageLoader(Context context, Uri uri) {
UriArchiveAccessor archiveAccessor1;
try {
archiveAccessor1 = new UriArchiveAccessor(context, uri);
@@ -77,8 +79,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
@Override
public void start() {
super.start();
int id = sIdGenerator.incrementAndGet();
archiveThread = new PriorityThread(
@@ -217,7 +217,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
size = 0;
}
if (size <= 0) {
size = STATE_ERROR;
size = PageLoader.STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
notifyDataChanged();
return;
@@ -289,7 +289,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
// Check index valid
if (index < 0 || index >= size) {
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
notifyPageFailed(index);
continue;
}
@@ -335,19 +335,16 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
Image image = null;
if (buffer != null) {
try {
image = Image.decode(buffer, () -> {
archiveAccessor.releaseByteBuffer(buffer);
return null;
});
image = Image.decode(buffer.asReadOnlyBuffer());
} catch (ImageDecoder.DecodeException e) {
archiveAccessor.releaseByteBuffer(buffer);
UriArchiveAccessor.releaseByteBuffer(buffer);
e.printStackTrace();
}
}
if (image != null) {
notifyPageSucceed(index, image);
} else {
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
notifyPageFailed(index);
}
}
}
@@ -1,279 +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.gallery;
import android.os.Process;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.image.Image;
import com.hippo.unifile.FilenameFilter;
import com.hippo.unifile.UniFile;
import com.hippo.util.NaturalComparator;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.StringUtils;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
private static final String TAG = DirGalleryProvider.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static final FilenameFilter imageFilter =
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
private static final Comparator<UniFile> naturalComparator = new Comparator<UniFile>() {
private final NaturalComparator comparator = new NaturalComparator();
@Override
public int compare(UniFile o1, UniFile o2) {
return comparator.compare(o1.getName(), o2.getName());
}
};
private final UniFile mDir;
private final Stack<Integer> mRequests = new Stack<>();
private final AtomicInteger mDecodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private final AtomicReference<UniFile[]> mFileList = new AtomicReference<>();
@Nullable
private Thread mBgThread;
private volatile int mSize = STATE_WAIT;
private String mError;
public DirGalleryProvider(@NonNull UniFile dir) {
mDir = dir;
}
@Override
public void start() {
super.start();
mBgThread = new PriorityThread(this, TAG + '-' + sIdGenerator.incrementAndGet(),
Process.THREAD_PRIORITY_BACKGROUND);
mBgThread.start();
}
@Override
public void stop() {
super.stop();
if (mBgThread != null) {
mBgThread.interrupt();
mBgThread = null;
}
}
@Override
public int size() {
return mSize;
}
@Override
protected void onRequest(int index) {
synchronized (mRequests) {
if (!mRequests.contains(index) && index != mDecodingIndex.get()) {
mRequests.add(index);
mRequests.notify();
}
}
notifyPageWait(index);
}
@Override
protected void onForceRequest(int index) {
onRequest(index);
}
@Override
public void onCancelRequest(int index) {
synchronized (mRequests) {
mRequests.remove(Integer.valueOf(index));
}
}
@Override
public String getError() {
return mError;
}
@NonNull
@Override
public String getImageFilename(int index) {
UniFile[] fileList = mFileList.get();
if (null == fileList || index < 0 || index >= fileList.length) {
return Integer.toString(index);
}
UniFile src = fileList[index];
String name = src.getName();
if (name == null) {
return Integer.toString(index);
}
return name;
}
@NonNull
@Override
public String getImageFilenameWithExtension(int index) {
UniFile[] fileList = mFileList.get();
if (null == fileList || index < 0 || index >= fileList.length) {
return Integer.toString(index);
}
UniFile src = fileList[index];
String extension = FileUtils.getExtensionFromFilename(src.getName());
String name = src.getName();
if (name == null) {
return Integer.toString(index);
}
name = null != extension ? name + "." + extension : name;
return name;
}
@Override
public boolean save(int index, @NonNull UniFile file) {
UniFile[] fileList = mFileList.get();
if (null == fileList || index < 0 || index >= fileList.length) {
return false;
}
InputStream is = null;
OutputStream os = null;
try {
is = fileList[index].openInputStream();
os = file.openOutputStream();
IOUtils.copy(is, os);
return true;
} catch (IOException e) {
return false;
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
}
@Nullable
@Override
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
UniFile[] fileList = mFileList.get();
if (null == fileList || index < 0 || index >= fileList.length) {
return null;
}
UniFile src = fileList[index];
String extension = FileUtils.getExtensionFromFilename(src.getName());
UniFile dst = dir.subFile(null != extension ? filename + "." + extension : filename);
if (null == dst) {
return null;
}
InputStream is = null;
OutputStream os = null;
try {
is = src.openInputStream();
os = dst.openOutputStream();
IOUtils.copy(is, os);
return dst;
} catch (IOException e) {
return null;
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
}
@Override
public void run() {
// It may take a long time, so run it in new thread
UniFile[] files = mDir.listFiles(imageFilter);
if (files == null) {
mSize = STATE_ERROR;
mError = GetText.getString(R.string.error_not_folder_path);
// Notify to to show error
notifyDataChanged();
Log.i(TAG, "ImageDecoder end with error");
return;
}
// Sort it
Arrays.sort(files, naturalComparator);
// Put file list
mFileList.lazySet(files);
// Set state normal and notify
mSize = files.length;
notifyDataChanged();
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (mRequests) {
if (mRequests.isEmpty()) {
try {
mRequests.wait();
} catch (InterruptedException e) {
// Interrupted
break;
}
continue;
}
index = mRequests.pop();
mDecodingIndex.lazySet(index);
}
// Check index valid
if (index < 0 || index >= files.length) {
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
continue;
}
InputStream is = null;
try {
is = files[index].openInputStream();
Image image = Image.decode((FileInputStream) is);
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageSucceed(index, image);
} catch (IOException e) {
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_reading_failed));
} finally {
IOUtils.closeQuietly(is);
}
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
}
// Clear file list
mFileList.lazySet(null);
Log.i(TAG, "ImageDecoder end");
}
}
@@ -16,44 +16,40 @@
package com.hippo.ehviewer.gallery;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.spider.SpiderQueen;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.SimpleHandler;
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 GalleryInfo mGalleryInfo;
@Nullable
private SpiderQueen mSpiderQueen;
public EhGalleryProvider(Context context, GalleryInfo galleryInfo) {
mContext = context;
public EhPageLoader(GalleryInfo galleryInfo) {
mGalleryInfo = galleryInfo;
}
@Override
public void start() {
super.start();
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mContext, mGalleryInfo, SpiderQueen.MODE_READ);
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mGalleryInfo, SpiderQueen.MODE_READ);
mSpiderQueen.addOnSpiderListener(this);
if (mSpiderQueen.size() > 0)
notifyDataChanged();
}
@Override
public void stop() {
super.stop();
if (mSpiderQueen != null) {
mSpiderQueen.removeOnSpiderListener(this);
// Activity recreate may called, so wait 3000s
@@ -120,18 +116,18 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
if (mSpiderQueen != null) {
return mSpiderQueen.size();
} else {
return GalleryProvider.STATE_ERROR;
return PageLoader.STATE_ERROR;
}
}
@Override
protected void onRequest(int index) {
if (mSpiderQueen != null) {
Object object = mSpiderQueen.request(index);
Object object = mSpiderQueen.request(index, true);
if (object instanceof Float) {
notifyPagePercent(index, (Float) object);
} else if (object instanceof String) {
notifyPageFailed(index, (String) object);
notifyPageFailed(index);
} else if (object == null) {
notifyPageWait(index);
}
@@ -145,7 +141,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
if (object instanceof Float) {
notifyPagePercent(index, (Float) object);
} else if (object instanceof String) {
notifyPageFailed(index, (String) object);
notifyPageFailed(index);
} else if (object == null) {
notifyPageWait(index);
}
@@ -187,12 +183,14 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
@Override
public void onPageSuccess(int index, int finished, int downloaded, int total) {
notifyDataChanged(index);
if (mSpiderQueen != null) {
mSpiderQueen.request(index, false);
}
}
@Override
public void onPageFailure(int index, String error, int finished, int downloaded, int total) {
notifyPageFailed(index, error);
notifyPageFailed(index);
}
@Override
@@ -206,7 +204,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
@Override
public void onGetImageFailure(int index, String error) {
notifyPageFailed(index, error);
notifyPageFailed(index);
}
private static class ReleaseTask implements Runnable {
@@ -19,10 +19,11 @@ package com.hippo.ehviewer.gallery;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.glgallery.GalleryProvider;
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
public static final String[] SUPPORT_IMAGE_EXTENSIONS = {
@@ -86,7 +86,7 @@ class AccountPreference @JvmOverloads constructor(
}
}
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)
}
}
@@ -28,7 +28,7 @@ import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.ehviewer.gallery.PageLoader2;
import com.hippo.io.UniFileInputStreamPipe;
import com.hippo.io.UniFileOutputStreamPipe;
import com.hippo.streampipe.InputStreamPipe;
@@ -118,7 +118,7 @@ public final class SpiderDen {
@Nullable
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);
UniFile file = dir.findFile(filename);
if (file != null) {
@@ -141,14 +141,11 @@ public final class SpiderDen {
}
public boolean isReady() {
switch (mMode) {
case SpiderQueen.MODE_READ:
return sCache != null;
case SpiderQueen.MODE_DOWNLOAD:
return mDownloadDir != null && mDownloadDir.isDirectory();
default:
return false;
}
return switch (mMode) {
case SpiderQueen.MODE_READ -> sCache != null;
case SpiderQueen.MODE_DOWNLOAD -> mDownloadDir != null && mDownloadDir.isDirectory();
default -> false;
};
}
@Nullable
@@ -179,10 +176,10 @@ public final class SpiderDen {
* @param extension with dot
*/
private String fixExtension(String extension) {
if (Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
if (Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
return extension;
} else {
return GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
return PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
}
}
@@ -261,8 +258,8 @@ public final class SpiderDen {
}
boolean result = false;
for (int i = 0, n = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
String filename = generateImageFilename(index, GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[i]);
for (int i = 0, n = PageLoader2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
String filename = generateImageFilename(index, PageLoader2.SUPPORT_IMAGE_EXTENSIONS[i]);
UniFile file = dir.subFile(filename);
if (file != null) {
result |= file.delete();
@@ -16,7 +16,6 @@
package com.hippo.ehviewer.spider;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
import android.os.AsyncTask;
@@ -31,7 +30,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
@@ -48,9 +46,7 @@ import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser;
import com.hippo.ehviewer.client.parser.GalleryPageApiParser;
import com.hippo.ehviewer.client.parser.GalleryPageParser;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.ehviewer.gallery.PageLoader2;
import com.hippo.image.Image;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
@@ -68,6 +64,7 @@ import com.hippo.yorozuya.thread.PriorityThreadFactory;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -85,6 +82,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import coil.disk.DiskCache;
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@@ -114,7 +113,7 @@ public final class SpiderQueen implements Runnable {
@NonNull
private final OkHttpClient mHttpClient;
@NonNull
private final SimpleDiskCache mSpiderInfoCache;
private final DiskCache mSpiderInfoCache;
@NonNull
private final GalleryInfo mGalleryInfo;
@NonNull
@@ -157,7 +156,7 @@ public final class SpiderQueen implements Runnable {
// For download, when it go to mPageStateArray.size(), done
private volatile int mDownloadPage = -1;
private SpiderQueen(EhApplication application, @NonNull GalleryInfo galleryInfo) {
private SpiderQueen(@NonNull GalleryInfo galleryInfo) {
mHttpClient = EhApplication.getOkHttpClient();
mSpiderInfoCache = EhApplication.getSpiderInfoCache();
mGalleryInfo = galleryInfo;
@@ -167,7 +166,7 @@ public final class SpiderQueen implements Runnable {
mPreloadNumber = MathUtils.clamp(Settings.getPreloadImage(), 0, 100);
for (int i = 0; i < DECODE_THREAD_NUM; i++) {
mDecodeIndexArray[i] = GalleryPageView.INVALID_INDEX;
mDecodeIndexArray[i] = -1;
}
mWorkerPoolExecutor = new ThreadPoolExecutor(mWorkerMaxCount, mWorkerMaxCount,
@@ -177,14 +176,12 @@ public final class SpiderQueen implements Runnable {
}
@UiThread
public static SpiderQueen obtainSpiderQueen(@NonNull Context context,
@NonNull GalleryInfo galleryInfo, @Mode int mode) {
public static SpiderQueen obtainSpiderQueen(@NonNull GalleryInfo galleryInfo, @Mode int mode) {
OSUtils.checkMainLoop();
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
if (queen == null) {
EhApplication application = (EhApplication) context.getApplicationContext();
queen = new SpiderQueen(application, galleryInfo);
queen = new SpiderQueen(galleryInfo);
sQueenMap.put(galleryInfo.gid, queen);
// Set mode
queen.setMode(mode);
@@ -358,12 +355,8 @@ public final class SpiderQueen implements Runnable {
private void setMode(@Mode int mode) {
switch (mode) {
case MODE_READ:
mReadReference++;
break;
case MODE_DOWNLOAD:
mDownloadReference++;
break;
case MODE_READ -> mReadReference++;
case MODE_DOWNLOAD -> mDownloadReference++;
}
if (mDownloadReference > 1) {
@@ -375,12 +368,8 @@ public final class SpiderQueen implements Runnable {
private void clearMode(@Mode int mode) {
switch (mode) {
case MODE_READ:
mReadReference--;
break;
case MODE_DOWNLOAD:
mDownloadReference--;
break;
case MODE_READ -> mReadReference--;
case MODE_DOWNLOAD -> mDownloadReference--;
}
if (mReadReference < 0 || mDownloadReference < 0) {
@@ -407,9 +396,9 @@ public final class SpiderQueen implements Runnable {
public int size() {
if (mQueenThread == null) {
return GalleryProvider.STATE_ERROR;
return PageLoader.STATE_ERROR;
} else if (mPageStateArray == null) {
return GalleryProvider.STATE_WAIT;
return PageLoader.STATE_WAIT;
} else {
return mPageStateArray.length;
}
@@ -427,8 +416,8 @@ public final class SpiderQueen implements Runnable {
return request(index, true, true, false);
}
public Object request(int index) {
return request(index, true, false, true);
public Object request(int index, boolean addNeighbor) {
return request(index, true, false, addNeighbor);
}
private int getPageState(int index) {
@@ -523,29 +512,25 @@ public final class SpiderQueen implements Runnable {
Object result;
switch (state) {
default:
case STATE_NONE:
result = null;
break;
case STATE_DOWNLOADING:
result = mPagePercentMap.get(index);
break;
case STATE_FAILED:
case STATE_NONE -> result = null;
case STATE_DOWNLOADING -> result = mPagePercentMap.get(index);
case STATE_FAILED -> {
String error = mPageErrorMap.get(index);
if (error == null) {
error = GetText.getString(R.string.error_unknown);
}
result = error;
break;
case STATE_FINISHED:
}
case STATE_FINISHED -> {
synchronized (mDecodeRequestQueue) {
if (!contain(mDecodeIndexArray, index) && !mDecodeRequestQueue.contains(index)) {
mDecodeRequestQueue.add(index);
mDecodeRequestQueue.notify();
mDecodeRequestQueue.notifyAll();
}
}
result = null;
break;
}
default -> throw new IllegalStateException("Unexpected value: " + state);
}
tryToEnsureWorkers();
@@ -645,7 +630,6 @@ public final class SpiderQueen implements Runnable {
return null;
}
OutputStream os = null;
try {
pipe.obtain();
@@ -661,7 +645,6 @@ public final class SpiderQueen implements Runnable {
} finally {
pipe.close();
pipe.release();
IOUtils.closeQuietly(os);
}
}
@@ -707,21 +690,16 @@ public final class SpiderQueen implements Runnable {
}
// Read from cache
InputStreamPipe pipe = mSpiderInfoCache.getInputStreamPipe(Long.toString(mGalleryInfo.gid));
if (null != pipe) {
try {
pipe.obtain();
spiderInfo = SpiderInfo.read(pipe.open());
if (spiderInfo != null && spiderInfo.gid == mGalleryInfo.gid &&
spiderInfo.token.equals(mGalleryInfo.token)) {
return spiderInfo;
}
} catch (IOException e) {
// Ignore
} finally {
pipe.close();
pipe.release();
var entry = mSpiderInfoCache.get(Long.toString(mGalleryInfo.gid));
if (entry == null) return null;
try (entry; InputStream is = new FileInputStream(entry.getData().toFile())) {
spiderInfo = SpiderInfo.read(is);
if (spiderInfo != null && spiderInfo.gid == mGalleryInfo.gid &&
spiderInfo.token.equals(mGalleryInfo.token)) {
return spiderInfo;
}
} catch (IOException e) {
// Ignore
}
return null;
@@ -751,14 +729,13 @@ public final class SpiderQueen implements Runnable {
}
private SpiderInfo readSpiderInfoFromInternet() {
try {
SpiderInfo spiderInfo = new SpiderInfo();
spiderInfo.gid = mGalleryInfo.gid;
spiderInfo.token = mGalleryInfo.token;
SpiderInfo spiderInfo = new SpiderInfo();
spiderInfo.gid = mGalleryInfo.gid;
spiderInfo.token = mGalleryInfo.token;
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
Response response = mHttpClient.newCall(request).execute();
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
try (Response response = mHttpClient.newCall(request).execute()) {
String body = response.body().string();
spiderInfo.pages = GalleryDetailParser.parsePages(body);
@@ -777,15 +754,14 @@ public final class SpiderQueen implements Runnable {
return null;
}
try {
String url = EhUrl.getGalleryMultiPageViewerUrl(
mGalleryInfo.gid, mGalleryInfo.token);
if (DEBUG_PTOKEN) {
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
}
String referer = EhUrl.getReferer();
Request request = new EhRequestBuilder(url, referer).build();
Response response = mHttpClient.newCall(request).execute();
String url = EhUrl.getGalleryMultiPageViewerUrl(
mGalleryInfo.gid, mGalleryInfo.token);
if (DEBUG_PTOKEN) {
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
}
String referer = EhUrl.getReferer();
Request request = new EhRequestBuilder(url, referer).build();
try (Response response = mHttpClient.newCall(request).execute()) {
String body = response.body().string();
ArrayList<String> list = GalleryMultiPageViewerPTokenParser.parse(body);
@@ -824,16 +800,15 @@ public final class SpiderQueen implements Runnable {
previewIndex = Math.min(previewIndex, spiderInfo.previewPages - 1);
}
try {
String url = EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
String referer = EhUrl.getReferer();
if (DEBUG_PTOKEN) {
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
}
Request request = new EhRequestBuilder(url, referer).build();
Response response = mHttpClient.newCall(request).execute();
String url = EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
String referer = EhUrl.getReferer();
if (DEBUG_PTOKEN) {
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
}
Request request = new EhRequestBuilder(url, referer).build();
try (Response response = mHttpClient.newCall(request).execute()) {
String body = response.body().string();
readPreviews(body, previewIndex, spiderInfo);
@@ -858,22 +833,20 @@ public final class SpiderQueen implements Runnable {
UniFile file = downloadDir.createFile(SPIDER_INFO_FILENAME);
try {
spiderInfo.write(file.openOutputStream());
} catch (Throwable e) {
} catch (IOException e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
}
// Read from cache
OutputStreamPipe pipe = mSpiderInfoCache.getOutputStreamPipe(Long.toString(mGalleryInfo.gid));
try {
pipe.obtain();
spiderInfo.write(pipe.open());
// Write to cache
var editor = mSpiderInfoCache.edit(Long.toString(mGalleryInfo.gid));
if (editor == null) return;
try (OutputStream os = new FileOutputStream(editor.getData().toFile())) {
spiderInfo.write(os);
editor.commit();
} catch (IOException e) {
// Ignore
} finally {
pipe.close();
pipe.release();
editor.abort();
}
}
@@ -1108,12 +1081,7 @@ public final class SpiderQueen implements Runnable {
private String getPageUrl(long gid, int index, String pToken,
String oldPageUrl, String skipHathKey) {
String pageUrl;
if (oldPageUrl != null) {
pageUrl = oldPageUrl;
} else {
pageUrl = EhUrl.getPageUrl(gid, index, pToken);
}
String pageUrl = oldPageUrl != null ? oldPageUrl : EhUrl.getPageUrl(gid, index, pToken);
// Add skipHathKey
if (skipHathKey != null) {
if (pageUrl.contains("?")) {
@@ -1150,7 +1118,7 @@ public final class SpiderQueen implements Runnable {
}
// 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;
List<String> skipHathKeys = new ArrayList<>(5);
String originImageUrl = null;
@@ -1269,10 +1237,7 @@ public final class SpiderQueen implements Runnable {
Log.d(TAG, "Start download image " + index);
}
// disable Call Timeout for image-downloading requests
Call call = mHttpClient.newBuilder()
.callTimeout(0, TimeUnit.SECONDS).build()
.newCall(new EhRequestBuilder(targetImageUrl, referer).build());
Call call = mHttpClient.newCall(new EhRequestBuilder(targetImageUrl, referer).build());
Response response = call.execute();
ResponseBody responseBody = response.body();
@@ -1284,12 +1249,6 @@ public final class SpiderQueen implements Runnable {
continue;
}
if (responseBody == null) {
error = "Empty response body";
forceHtml = true;
continue;
}
// Get extension
String extension = null;
MediaType mediaType = responseBody.contentType();
@@ -1297,8 +1256,8 @@ public final class SpiderQueen implements Runnable {
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mediaType.toString());
}
// Ensure extension
if (!Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
extension = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
if (!Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
extension = PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
}
OutputStreamPipe osPipe = null;
@@ -1575,7 +1534,7 @@ public final class SpiderQueen implements Runnable {
}
// Get image url
return downloadImage(mGid, index, pToken, previousPToken, force);
return downloadImage(mGid, index, pToken, previousPToken);
}
@Override
@@ -1596,7 +1555,7 @@ public final class SpiderQueen implements Runnable {
Log.e(TAG, "WTF, mWorkerCount < 0, not thread safe or something wrong");
mWorkerCount = 0;
}
finish = mWorkerCount <= 0;
finish = mWorkerCount == 0;
}
if (finish) {
@@ -1619,7 +1578,7 @@ public final class SpiderQueen implements Runnable {
private void resetDecodeIndex() {
synchronized (mDecodeRequestQueue) {
mDecodeIndexArray[mThreadIndex] = GalleryPageView.INVALID_INDEX;
mDecodeIndexArray[mThreadIndex] = -1;
}
}
@@ -16,15 +16,11 @@
package com.hippo.ehviewer.ui
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.annotation.StyleRes
@@ -33,53 +29,30 @@ import androidx.core.content.ContextCompat
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import rikka.core.res.resolveColor
import eu.kanade.tachiyomi.util.system.isNightMode
import eu.kanade.tachiyomi.util.view.setSecureScreen
import rikka.insets.WindowInsetsHelper
import rikka.layoutinflater.view.LayoutInflaterFactory
abstract class EhActivity : AppCompatActivity() {
@StyleRes
fun getThemeStyleRes(context: Context): Int {
return if (THEME_BLACK == getTheme(context)) {
R.style.ThemeOverlay_Black
} else R.style.ThemeOverlay
fun getThemeStyleRes(): Int {
val isBlackDarkTheme = Settings.getBoolean("black_dark_theme", false)
return if (isBlackDarkTheme && isNightMode()) R.style.ThemeOverlay_Black else R.style.ThemeOverlay
}
override fun onApplyThemeResource(theme: Theme, resid: Int, first: Boolean) {
if (parent == null) {
theme.applyStyle(resid, true)
} else {
try {
theme.setTo(parent.theme)
} catch (ignored: Exception) {
}
theme.applyStyle(resid, false)
}
theme.applyStyle(getThemeStyleRes(this), true)
super.onApplyThemeResource(theme, R.style.ThemeOverlay, first)
theme.applyStyle(resid, true)
theme.applyStyle(getThemeStyleRes(), true)
}
override fun onNightModeChanged(mode: Int) {
theme.applyStyle(getThemeStyleRes(this), true)
theme.applyStyle(getThemeStyleRes(), true)
}
override fun onCreate(savedInstanceState: Bundle?) {
layoutInflater.factory2 =
LayoutInflaterFactory(delegate).addOnViewCreatedListener(
WindowInsetsHelper.LISTENER
)
layoutInflater.factory2 = LayoutInflaterFactory(delegate).addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
super.onCreate(savedInstanceState)
window.decorView.post {
val rootWindowInsets = window.decorView.rootWindowInsets
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
window.navigationBarColor =
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
}
}
(application as EhApplication).registerActivity(this)
}
@@ -89,56 +62,24 @@ abstract class EhActivity : AppCompatActivity() {
}
override fun onResume() {
val locked_resume_time = System.currentTimeMillis() / 1000
val locked_delay_time = locked_resume_time - EhApplication.locked_last_leave_time
if (locked_delay_time < Settings.getSecurityDelay() * 60) {
val lockedResumeTime = System.currentTimeMillis() / 1000
val lockedDelayTime = lockedResumeTime - EhApplication.locked_last_leave_time
if (lockedDelayTime < Settings.getSecurityDelay() * 60) {
EhApplication.locked = false
} else if (Settings.getSecurity() && isAuthenticationSupported() && EhApplication.locked) {
startActivity(Intent(this, SecurityActivity::class.java))
}
super.onResume()
if (Settings.getEnabledSecurity()) {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
window.setSecureScreen(Settings.getEnabledSecurity())
}
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) {
Settings.putNotificationRequired()
}
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { Settings.putNotificationRequired() }
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun checkAndRequestNotificationPermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
return
}
if (Settings.getNotificationRequired())
return
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) return
if (Settings.getNotificationRequired()) return
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
companion object {
private const val THEME_DEFAULT = "DEFAULT"
private const val THEME_BLACK = "BLACK"
private val isBlackNightTheme: Boolean
get() = Settings.getBoolean("black_dark_theme", false)
fun getTheme(context: Context): String {
return if (isBlackNightTheme && isNightMode(context.resources.configuration)) THEME_BLACK else THEME_DEFAULT
}
private fun isNightMode(configuration: Configuration): Boolean {
return configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0
}
}
}
}
File diff suppressed because it is too large. Load diff
@@ -30,6 +30,7 @@ import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.util.addTextToClipboard
import com.hippo.util.tellClipboard
class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment() {
private var mKeys: ArrayList<String> = arrayListOf()
@@ -117,10 +118,7 @@ class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment(
if (position == INDEX_PARENT) {
UrlOpener.openUrl(context, mValues[position], true)
} else {
activity?.addTextToClipboard(
mValues[position],
false,
)
requireActivity() tellClipboard mValues[position]
if (position == INDEX_URL) {
// Save it to avoid detect the gallery
Settings.putClipboardTextHashCode(mValues[position].hashCode())
@@ -28,11 +28,11 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.IdRes;
@@ -40,6 +40,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.navigation.NavigationView;
@@ -49,7 +50,6 @@ import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhTagDatabase;
import com.hippo.ehviewer.client.EhUrlOpener;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
@@ -136,7 +136,7 @@ public final class MainActivity extends StageActivity
@NonNull
@Override
protected Announcer getLaunchAnnouncer() {
if (EhUtils.needSignedIn(this)) {
if (EhUtils.needSignedIn()) {
return new Announcer(SignInScene.class);
} else if (Settings.getSelectSite()) {
return new Announcer(SelectSiteScene.class);
@@ -150,7 +150,7 @@ public final class MainActivity extends StageActivity
// Sometimes scene can't show directly
private Announcer processAnnouncer(Announcer announcer) {
if (0 == getSceneCount()) {
if (EhUtils.needSignedIn(this)) {
if (EhUtils.needSignedIn()) {
Bundle newArgs = new Bundle();
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.getArgs());
@@ -287,6 +287,9 @@ public final class MainActivity extends StageActivity
mDrawerLayout = (DrawerLayout) ViewUtils.$$(this, R.id.draw_view);
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
var callback = new DrawerOnBackPressedCallback(false, mDrawerLayout);
mDrawerLayout.addDrawerListener(callback);
getOnBackPressedDispatcher().addCallback(callback);
if (mNavView != null) {
mNavView.setNavigationItemSelectedListener(this);
@@ -309,8 +312,6 @@ public final class MainActivity extends StageActivity
} else {
onRestore(savedInstanceState);
}
EhTagDatabase.update(this);
}
@@ -483,9 +484,9 @@ public final class MainActivity extends StageActivity
baseScene.getLayoutInflater(), mRightDrawer, null);
if (drawerView != null) {
mRightDrawer.addView(drawerView);
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
} else {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
}
}
@@ -517,6 +518,13 @@ public final class MainActivity extends StageActivity
}
}
public Integer getDrawerLockMode(int edgeGravity) {
if (mDrawerLayout != null) {
return mDrawerLayout.getDrawerLockMode(edgeGravity);
}
return null;
}
public void openDrawer(int drawerGravity) {
if (mDrawerLayout != null) {
mDrawerLayout.openDrawer(drawerGravity);
@@ -568,17 +576,6 @@ public final class MainActivity extends StageActivity
}
}
@SuppressLint("RtlHardcoded")
@Override
public void onBackPressed() {
if (mDrawerLayout != null && (mDrawerLayout.isDrawerOpen(Gravity.LEFT) ||
mDrawerLayout.isDrawerOpen(Gravity.RIGHT))) {
mDrawerLayout.closeDrawers();
} else {
super.onBackPressed();
}
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Don't select twice
@@ -625,4 +622,40 @@ public final class MainActivity extends StageActivity
return true;
}
static class DrawerOnBackPressedCallback extends OnBackPressedCallback implements DrawerLayout.DrawerListener {
private final DrawerLayout mLayout;
public DrawerOnBackPressedCallback(boolean enabled, DrawerLayout drawerLayout) {
super(enabled);
mLayout = drawerLayout;
}
@Override
public void handleOnBackPressed() {
mLayout.closeDrawers();
}
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
setEnabled(true);
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {}
@Override
public void onDrawerStateChanged(int newState) {
switch (newState) {
case DrawerLayout.STATE_DRAGGING, DrawerLayout.STATE_SETTLING -> setEnabled(true);
case DrawerLayout.STATE_IDLE -> {
if (!mLayout.isDrawerVisible(GravityCompat.START) && !mLayout.isDrawerVisible(GravityCompat.END))
setEnabled(false);
}
}
}
}
}
@@ -41,8 +41,6 @@ class SettingsActivity : EhActivity() {
mFab = findViewById(R.id.fab)
mAppbarLayout = findViewById(R.id.appbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
@@ -16,9 +16,9 @@
package com.hippo.ehviewer.ui.fragment;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -70,7 +70,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
private static final String KEY_OPEN_BY_DEFAULT = "open_by_default";
private static final String KEY_BACKUP_FAVORITE = "backup_favorite";
ActivityResultLauncher<String> exportLauncher = registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
new ActivityResultContracts.CreateDocument("application/vnd.sqlite3"),
uri -> {
if (uri != null) {
try {
@@ -107,7 +107,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
}
});
ActivityResultLauncher<String> dumpLogcatLauncher = registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
new ActivityResultContracts.CreateDocument("application/zip"),
uri -> {
if (uri != null) {
try {
@@ -291,7 +291,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
return true;
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
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()));
startActivity(intent);
} catch (Throwable t) {
@@ -312,36 +312,35 @@ public class AdvancedFragment extends BasePreferenceFragment {
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
request.setCallback(new EhClient.Callback() {
request.setCallback(new EhClient.Callback<FavoritesParser.Result>() {
@Override
public void onSuccess(Object result) {
public void onSuccess(FavoritesParser.Result result) {
try {
FavoritesParser.Result favResult = (FavoritesParser.Result) result;
if (favResult.galleryInfoList.isEmpty()) {
if (result.galleryInfoList.isEmpty()) {
showTip(R.string.settings_advanced_backup_favorite_nothing, BaseScene.LENGTH_SHORT);
} else {
if (favTotal == 0 && favResult.countArray != null) {
if (favTotal == 0 && result.countArray != null) {
int totalFav = 0;
for (int i = 0; i < 10; i++) {
totalFav = totalFav + favResult.countArray[i];
totalFav = totalFav + result.countArray[i];
}
favTotal = (int) Math.ceil((double) totalFav / favResult.galleryInfoList.size());
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(favResult.galleryInfoList);
EhDB.putLocalFavorites(result.galleryInfoList);
if (favResult.nextPage != null) {
if (result.next != null) {
try {
Thread.sleep(Settings.getDownloadDelay());
} catch (InterruptedException e) {
e.printStackTrace();
}
favIndex++;
favListUrlBuilder.setNext(favResult.nextPage);
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
favListUrlBuilder.setIndex(result.next, true);
request.setArgs(favListUrlBuilder.build());
mClient.execute(request);
} else {
showTip(R.string.settings_advanced_backup_favorite_success, BaseScene.LENGTH_SHORT);
@@ -363,7 +362,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
}
});
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
request.setArgs(favListUrlBuilder.build());
mClient.execute(request);
}
@@ -372,7 +371,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
String key = preference.getKey();
if (KEY_APP_LANGUAGE.equals(key) && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
if ("system".equals(newValue)) {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.wrap(Resources.getSystem().getConfiguration().getLocales()));
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList());
} else {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags((String) newValue));
}
@@ -1,90 +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 android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.SettingsActivity;
public class BasePreferenceFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
@Override
public void onStart() {
super.onStart();
setTitle(getFragmentTitle());
}
@StringRes
public int getFragmentTitle() {
return -1;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return false;
}
@Override
public boolean onPreferenceClick(Preference preference) {
return false;
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
String key = preference.getKey();
try {
Fragment classObj = (Fragment) Class.forName(key).newInstance();
requireActivity().getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.fragment, classObj)
.addToBackStack(null)
.commitAllowingStateLoss();
} catch (ClassNotFoundException | IllegalAccessException |
java.lang.InstantiationException ignored) {
}
return true;
}
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,84 @@
/*
* 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.os.Bundle
import androidx.annotation.StringRes
import androidx.fragment.app.FragmentTransaction
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.SettingsActivity
open class BasePreferenceFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
override fun onStart() {
super.onStart()
setTitle(fragmentTitle)
}
@get:StringRes
open val fragmentTitle: Int
get() = -1
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
return false
}
override fun onPreferenceClick(preference: Preference): Boolean {
return false
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
val fragment = when (preference.key) {
"eh" -> EhFragment()
"download" -> DownloadFragment()
"privacy" -> PrivacyFragment()
"advance" -> AdvancedFragment()
"about" -> AboutFragment()
"filter" -> FilterFragment()
"mytags" -> MyTagsFragment()
"uconfig" -> UConfigFragment()
"hosts" -> HostsFragment()
else -> null
}
fragment?.let {
requireActivity().supportFragmentManager
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.fragment, it)
.addToBackStack(null)
.commitAllowingStateLoss()
}
return true
}
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)
}
}
@@ -18,7 +18,9 @@ package com.hippo.ehviewer.ui.fragment
import android.app.Activity
import android.content.res.Configuration
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.ListPreference
import androidx.preference.Preference
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
@@ -36,6 +38,7 @@ class EhFragment : BasePreferenceFragment() {
val listThumbSize = findPreference<Preference>(Settings.KEY_LIST_THUMB_SIZE)
val detailSize = findPreference<Preference>(Settings.KEY_DETAIL_SIZE)
val thumbSize = findPreference<Preference>(Settings.KEY_THUMB_SIZE)
val thumbResolution = findPreference<Preference>(Settings.KEY_THUMB_RESOLUTION)
val showTagTranslations = findPreference<Preference>(Settings.KEY_SHOW_TAG_TRANSLATIONS)
val tagTranslationsSource = findPreference<Preference>("tag_translations_source")
Settings.getDisplayName()?.let { account?.summary = it }
@@ -47,7 +50,10 @@ class EhFragment : BasePreferenceFragment() {
thumbSize!!.onPreferenceChangeListener = this
showTagTranslations!!.onPreferenceChangeListener = this
blackDarkTheme!!.onPreferenceChangeListener = this
if (!EhTagDatabase.isPossible(requireActivity())) {
thumbResolution!!.setSummaryProvider {
getString(R.string.settings_eh_thumb_resolution_summary, (it as ListPreference).entry)
}
if (!EhTagDatabase.isTranslatable(requireActivity())) {
preferenceScreen.removePreference(showTagTranslations)
preferenceScreen.removePreference(tagTranslationsSource!!)
}
@@ -75,7 +81,7 @@ class EhFragment : BasePreferenceFragment() {
requireActivity().setResult(Activity.RESULT_OK)
} else if (Settings.KEY_SHOW_TAG_TRANSLATIONS == key) {
if (java.lang.Boolean.TRUE == newValue) {
EhTagDatabase.update(requireActivity())
EhTagDatabase.update()
}
} else if (Settings.KEY_BLACK_DARK_THEME == key) {
if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) {
@@ -86,7 +92,7 @@ class EhFragment : BasePreferenceFragment() {
return true
}
override fun getFragmentTitle(): Int {
return R.string.settings_eh
}
@get:StringRes
override val fragmentTitle: Int
get() = R.string.settings_eh
}
@@ -264,10 +264,14 @@ class FilterFragment : BaseFragment() {
private val mUploaderFilterList: List<Filter> = mEhFilter.uploaderFilterList
private val mTagFilterList: List<Filter> = mEhFilter.tagFilterList
private val mTagNamespaceFilterList: List<Filter> = mEhFilter.tagNamespaceFilterList
private val mCommenterFilterList: List<Filter> = mEhFilter.commenterFilterList
private val mCommentFilterList: List<Filter> = mEhFilter.commentFilterList
private var mTitleHeader: Filter? = null
private var mUploaderHeader: Filter? = null
private var mTagHeader: Filter? = null
private var mTagNamespaceHeader: Filter? = null
private var mCommenterHeader: Filter? = null
private var mCommentHeader: Filter? = null
fun size(): Int {
var count = 0
var size = mTitleFilterList.size
@@ -278,6 +282,10 @@ class FilterFragment : BaseFragment() {
count += if (0 == size) 0 else size + 1
size = mTagNamespaceFilterList.size
count += if (0 == size) 0 else size + 1
size = mCommenterFilterList.size;
count += if (0 == size) 0 else size + 1
size = mCommentFilterList.size;
count += if (0 == size) 0 else size + 1
return count
}
@@ -317,6 +325,24 @@ class FilterFragment : BaseFragment() {
}
return mTagNamespaceHeader!!
}
private val commenterHeader: Filter
get() {
if (null == mCommenterHeader) {
mCommenterHeader = Filter()
mCommenterHeader!!.mode = MODE_HEADER
mCommenterHeader!!.text = getString(R.string.filter_commenter)
}
return mCommenterHeader!!
}
private val commentHeader: Filter
get() {
if (null == mCommentHeader) {
mCommentHeader = Filter()
mCommentHeader!!.mode = MODE_HEADER
mCommentHeader!!.text = getString(R.string.filter_comment)
}
return mCommentHeader!!
}
operator fun get(index: Int): Filter {
var index1 = index
@@ -356,6 +382,26 @@ class FilterFragment : BaseFragment() {
return tagNamespaceHeader
} else if (index1 <= size) {
return mTagNamespaceFilterList[index1 - 1]
} else {
size + 1
}
}
size = mCommenterFilterList.size
if (0 != size) {
index1 -= if (index1 == 0) {
return commenterHeader
} else if (index1 <= size) {
return mCommenterFilterList[index1 - 1]
} else {
size + 1
}
}
size = mCommentFilterList.size
if (0 != size) {
if (index1 == 0) {
return commentHeader
} else if (index1 <= size) {
return mCommentFilterList[index1 - 1]
}
}
throw IndexOutOfBoundsException()
@@ -19,6 +19,7 @@
package com.hippo.ehviewer.ui.fragment
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
import com.hippo.ehviewer.R
@@ -59,9 +60,9 @@ class PrivacyFragment : BasePreferenceFragment() {
}
}
override fun getFragmentTitle(): Int {
return R.string.settings_privacy
}
@get:StringRes
override val fragmentTitle: Int
get() = R.string.settings_privacy
private fun setUnlockDelaySummary(value: Int) {
unlockDelay.summary = if (value == 0) {
@@ -1,36 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.fragment;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.R;
public class ReadFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.read_settings);
}
@Override
public int getFragmentTitle() {
return R.string.settings_read;
}
}
@@ -33,6 +33,7 @@ import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
@@ -78,6 +79,14 @@ public abstract class BaseScene extends SceneFragment {
}
}
public Integer getDrawerLockMode(int edgeGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
return ((MainActivity) activity).getDrawerLockMode(edgeGravity);
}
return null;
}
public void openDrawer(int drawerGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
@@ -190,9 +199,9 @@ public abstract class BaseScene extends SceneFragment {
// Update left drawer locked state
if (needShowLeftDrawer()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
} else {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
}
// Update nav checked item
@@ -200,11 +209,6 @@ public abstract class BaseScene extends SceneFragment {
// Hide soft ime
hideSoftInput();
insetsController = getInsetsController();
if (insetsController != null) {
insetsController.setAppearanceLightStatusBars((requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
}
}
@Nullable
@@ -35,7 +35,9 @@ import com.google.android.material.textfield.TextInputLayout;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.util.ClipboardUtilKt;
@@ -68,31 +70,6 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
@Nullable
private TextView mFromClipboard;
// false for error
private static boolean checkIpbMemberId(String id) {
for (int i = 0, n = id.length(); i < n; i++) {
char ch = id.charAt(i);
if (!(ch >= '0' && ch <= '9')) {
return false;
}
}
return true;
}
private static boolean checkIpbPassHash(String hash) {
if (32 != hash.length()) {
return false;
}
for (int i = 0, n = hash.length(); i < n; i++) {
char ch = hash.charAt(i);
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z')) {
return false;
}
}
return true;
}
private static Cookie newCookie(String name, String value, String domain) {
return new Cookie.Builder().name(name).value(value)
.domain(domain).expiresAt(Long.MAX_VALUE).build();
@@ -216,20 +193,12 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
hideSoftInput();
if (!checkIpbMemberId(ipbMemberId) || !(checkIpbPassHash(ipbPassHash))) {
new BaseDialogBuilder(context).setTitle(R.string.waring)
.setMessage(R.string.wrong_cookie_warning)
.setPositiveButton(R.string.i_will_check_it, null)
.setNegativeButton(R.string.i_dont_think_so, (dialog, which) -> {
storeCookie(ipbMemberId, ipbPassHash, igneous);
setResult(RESULT_OK, null);
finish();
}).show();
} else {
storeCookie(ipbMemberId, ipbPassHash, igneous);
setResult(RESULT_OK, null);
finish();
}
storeCookie(ipbMemberId, ipbPassHash, igneous);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_PROFILE)
.setCallback(new CookieSignInListener());
EhApplication.getEhClient().execute(request);
}
private void storeCookie(String id, String hash, String igneous) {
@@ -238,7 +207,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
return;
}
EhUtils.signOut(context);
EhUtils.signOut();
EhCookieStore store = EhApplication.getEhCookieStore();
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E));
@@ -252,8 +221,11 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
}
private void fillCookiesFromClipboard() {
Context context = requireContext();
hideSoftInput();
String text = ClipboardUtilKt.getTextFromClipboard(ClipboardUtilKt.getClipboardManager(getContext()), getContext());
String text = ClipboardUtilKt.getTextFromClipboard(
ClipboardUtilKt.getClipboardManager(context), context);
if (text == null) {
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
return;
@@ -309,4 +281,25 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
}
}
private class CookieSignInListener implements EhClient.Callback<Object> {
@Override
public void onSuccess(Object result) {
setResult(RESULT_OK, null);
finish();
}
@Override
public void onFailure(Exception e) {
EhApplication.getEhCookieStore().signOut();
new BaseDialogBuilder(requireContext()).setTitle(R.string.sign_in_failed)
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.wrong_cookie_warning))
.setPositiveButton(R.string.get_it, null).show();
}
@Override
public void onCancel() {
EhApplication.getEhCookieStore().signOut();
}
}
}
@@ -43,6 +43,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.ItemTouchHelper;
@@ -70,7 +71,6 @@ import com.hippo.ehviewer.dao.DownloadLabel;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.download.DownloadService;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.scene.Announcer;
import com.hippo.unifile.UniFile;
@@ -89,6 +89,7 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import rikka.core.res.ResourcesKt;
@SuppressLint("RtlHardcoded")
@@ -406,7 +407,7 @@ public class DownloadsScene extends ToolbarScene
@Override
public void onNavigationClick() {
toggleDrawer(Gravity.LEFT);
toggleDrawer(GravityCompat.START);
}
@Override
@@ -444,10 +445,10 @@ public class DownloadsScene extends ToolbarScene
}
return true;
} else if (id == R.id.action_open_download_labels) {
openDrawer(Gravity.RIGHT);
openDrawer(GravityCompat.END);
return true;
} else if (id == R.id.action_reset_reading_progress) {
new BaseDialogBuilder(getContext())
new BaseDialogBuilder(requireContext())
.setMessage(R.string.reset_reading_progress_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
@@ -566,14 +567,14 @@ public class DownloadsScene extends ToolbarScene
@Override
public void onStartDragHandler() {
// Lock right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
public void onEndDragHandler() {
// Restore right drawer
if (null != mRecyclerView && !mRecyclerView.isInCustomChoice()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
}
}
@@ -596,9 +597,9 @@ public class DownloadsScene extends ToolbarScene
return false;
}
Intent intent = new Intent(activity, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, list.get(position));
Intent intent = new Intent(activity, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, list.get(position));
startActivity(intent);
return true;
}
@@ -875,11 +876,13 @@ public class DownloadsScene extends ToolbarScene
private static class DownloadLabelHolder extends RecyclerView.ViewHolder {
private final TextView label;
private final ImageView edit;
private final ImageView option;
private DownloadLabelHolder(View itemView) {
super(itemView);
label = (TextView) ViewUtils.$$(itemView, R.id.tv_key);
edit = (ImageView) ViewUtils.$$(itemView, R.id.iv_edit);
option = (ImageView) ViewUtils.$$(itemView, R.id.iv_option);
}
}
@@ -933,12 +936,12 @@ public class DownloadsScene extends ToolbarScene
mLabel = label1;
updateForLabel();
updateView();
closeDrawer(Gravity.RIGHT);
closeDrawer(GravityCompat.END);
}
});
if (position > 0) {
holder.option.setVisibility(View.VISIBLE);
holder.option.setOnClickListener(v -> {
holder.edit.setVisibility(View.VISIBLE);
holder.edit.setOnClickListener(v -> {
if (context != null) {
EditTextDialogBuilder builder = new EditTextDialogBuilder(
context, label, getString(R.string.download_labels));
@@ -1195,8 +1198,8 @@ public class DownloadsScene extends ToolbarScene
mFabLayout.setExpanded(true);
}
// Lock drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
@@ -1205,8 +1208,8 @@ public class DownloadsScene extends ToolbarScene
mFabLayout.setExpanded(false);
}
// Unlock drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
}
@Override
@@ -22,7 +22,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
import android.view.Gravity;
@@ -34,6 +33,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -86,7 +86,7 @@ import java.util.List;
import rikka.core.res.ResourcesKt;
// TODO Get favorite, modify favorite, add favorite, what a mess!
@SuppressLint("RtlHardcoded")
@SuppressLint({"NotifyDataSetChanged", "RtlHardcoded"})
public class FavoritesScene extends SearchBarScene implements
FastScroller.OnDragHandlerListener,
FabLayout.OnClickFabListener, FabLayout.OnExpandListener,
@@ -103,8 +103,6 @@ public class FavoritesScene extends SearchBarScene implements
public int current; // -1 for error
public int limit; // -1 for error
@Nullable
private ContentLayout mContentLayout;
@Nullable
@ViewLifeCircle
private EasyRecyclerView mRecyclerView;
@Nullable
@@ -138,15 +136,13 @@ public class FavoritesScene extends SearchBarScene implements
@Override
public void run() {
if (mFabLayout != null) {
mFabLayout.setSecondaryFabVisibilityAt(0, true);
updateJumpFab(); // index: 0, 2
mFabLayout.setSecondaryFabVisibilityAt(1, true);
mFabLayout.setSecondaryFabVisibilityAt(2, true);
mFabLayout.setSecondaryFabVisibilityAt(3, false);
mFabLayout.setSecondaryFabVisibilityAt(4, false);
mFabLayout.setSecondaryFabVisibilityAt(5, false);
mFabLayout.setSecondaryFabVisibilityAt(6, false);
}
updateJumpFab();
}
};
private int mFavLocalCount = 0;
@@ -231,12 +227,12 @@ public class FavoritesScene extends SearchBarScene implements
mUrlBuilder = null;
}
@Nullable
@NonNull
@Override
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_favorites, container, false);
mContentLayout = view.findViewById(R.id.content_layout);
ContentLayout mContentLayout = view.findViewById(R.id.content_layout);
MainActivity activity = getMainActivity();
AssertUtils.assertNotNull(activity);
setOnApplySearch((query) -> {
@@ -247,7 +243,8 @@ public class FavoritesScene extends SearchBarScene implements
mRecyclerView = mContentLayout.getRecyclerView();
FastScroller fastScroller = mContentLayout.getFastScroller();
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
AssertUtils.assertNotNull(container);
container.addView(mFabLayout);
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
@@ -291,7 +288,7 @@ public class FavoritesScene extends SearchBarScene implements
// Restore search mode
if (mSearchMode) {
mSearchMode = false;
enterSearchMode(false);
enterSearchMode();
}
// Only refresh for the first time
@@ -347,7 +344,8 @@ public class FavoritesScene extends SearchBarScene implements
// Hide jump fab on local fav cat
private void updateJumpFab() {
if (mFabLayout != null && mUrlBuilder != null) {
mFabLayout.setSecondaryFabVisibilityAt(1, mUrlBuilder.getFavCat() != FavListUrlBuilder.FAV_CAT_LOCAL);
mFabLayout.setSecondaryFabVisibilityAt(0, mUrlBuilder.getFavCat() != FavListUrlBuilder.FAV_CAT_LOCAL);
mFabLayout.setSecondaryFabVisibilityAt(2, mUrlBuilder.getFavCat() != FavListUrlBuilder.FAV_CAT_LOCAL);
}
}
@@ -366,7 +364,7 @@ public class FavoritesScene extends SearchBarScene implements
mRecyclerView = null;
}
if (null != mFabLayout) {
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
removeAboveSnackView(mFabLayout);
mFabLayout = null;
}
@@ -427,9 +425,7 @@ public class FavoritesScene extends SearchBarScene implements
} else if (mFabLayout != null && mFabLayout.isExpanded()) {
mFabLayout.toggle();
} else if (mSearchMode) {
exitSearchMode(true);
} else if (isSearchViewShown()) {
hideSearchView();
exitSearchMode();
} else {
finish();
}
@@ -438,21 +434,21 @@ public class FavoritesScene extends SearchBarScene implements
@Override
public void onStartDragHandler() {
// Lock right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
public void onEndDragHandler() {
// Restore right drawer
if (null != mRecyclerView && !mRecyclerView.isInCustomChoice()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
}
showSearchBar();
}
public boolean onItemClick(View view, int position) {
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) {
public boolean onItemClick(int position) {
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.END)) {
// Skip if in search mode
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
return true;
@@ -470,12 +466,7 @@ public class FavoritesScene extends SearchBarScene implements
return true;
}
// Ensure outOfCustomChoiceMode to avoid error
//if (mRecyclerView != null) {
// mRecyclerView.isInCustomChoice();
//}
exitSearchMode(true);
exitSearchMode();
mUrlBuilder.setKeyword(null);
mUrlBuilder.setFavCat(newFavCat);
@@ -483,7 +474,7 @@ public class FavoritesScene extends SearchBarScene implements
updateJumpFab();
mHelper.refresh();
closeDrawer(Gravity.RIGHT);
closeDrawer(GravityCompat.END);
} else {
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
@@ -524,12 +515,7 @@ public class FavoritesScene extends SearchBarScene implements
return;
}
// Ensure outOfCustomChoiceMode to avoid error
//if (mRecyclerView != null) {
// mRecyclerView.isInCustomChoice();
//}
exitSearchMode(true);
exitSearchMode();
mUrlBuilder.setKeyword(query);
updateSearchBar();
@@ -566,7 +552,7 @@ public class FavoritesScene extends SearchBarScene implements
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
ArrayList listValidators = new ArrayList<>();
ArrayList<CalendarConstraints.DateValidator> listValidators = new ArrayList<>();
listValidators.add(DateValidatorPointForward.from(fromDate));
listValidators.add(DateValidatorPointBackward.before(toDate));
@@ -581,9 +567,7 @@ public class FavoritesScene extends SearchBarScene implements
.setSelection(toDate)
.build();
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
datePicker.addOnPositiveButtonClickListener(v -> {
mHelper.goTo(v);
});
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v, true));
}
@Override
@@ -595,17 +579,14 @@ public class FavoritesScene extends SearchBarScene implements
if (!mRecyclerView.isInCustomChoice()) {
switch (position) {
case 0: // Open right
openDrawer(Gravity.RIGHT);
break;
case 1: // Go to
if (mHelper.canGoTo()) {
showGoToDialog();
}
break;
case 2: // Refresh
mHelper.refresh();
break;
// Go to
case 0 -> {
if (mHelper.canGoTo()) showGoToDialog();
}
// Refresh
case 1 -> mHelper.refresh();
// Last page
case 2 -> mHelper.goTo("1-0", false);
}
view.setExpanded(false);
return;
@@ -623,11 +604,9 @@ public class FavoritesScene extends SearchBarScene implements
}
switch (position) {
case 3: { // Check all
mRecyclerView.checkAll();
break;
}
case 4: { // Download
case 3 -> // Check all
mRecyclerView.checkAll();
case 4 -> { // Download
Activity activity = getMainActivity();
if (activity != null) {
CommonOperations.startDownload(getMainActivity(), mModifyGiList, false);
@@ -636,9 +615,8 @@ public class FavoritesScene extends SearchBarScene implements
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
mRecyclerView.outOfCustomChoiceMode();
}
break;
}
case 5: { // Delete
case 5 -> { // Delete
DeleteDialogHelper helper = new DeleteDialogHelper();
new BaseDialogBuilder(context)
.setTitle(R.string.delete_favorites_dialog_title)
@@ -646,9 +624,8 @@ public class FavoritesScene extends SearchBarScene implements
.setPositiveButton(android.R.string.ok, helper)
.setOnCancelListener(helper)
.show();
break;
}
case 6: { // Move
case 6 -> { // Move
MoveDialogHelper helper = new MoveDialogHelper();
// First is local favorite, the other 10 is cloud favorite
String[] array = new String[11];
@@ -659,7 +636,6 @@ public class FavoritesScene extends SearchBarScene implements
.setItems(array, helper)
.setOnCancelListener(helper)
.show();
break;
}
}
}
@@ -696,8 +672,8 @@ public class FavoritesScene extends SearchBarScene implements
mHelper.setRefreshLayoutEnable(false);
}
// Lock drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
@@ -711,8 +687,8 @@ public class FavoritesScene extends SearchBarScene implements
mHelper.setRefreshLayoutEnable(true);
}
// Unlock drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
}
@Override
@@ -723,7 +699,7 @@ public class FavoritesScene extends SearchBarScene implements
}
}
private void enterSearchMode(boolean animation) {
private void enterSearchMode() {
if (mSearchMode) {
return;
}
@@ -731,7 +707,7 @@ public class FavoritesScene extends SearchBarScene implements
showSearchBar();
}
private void exitSearchMode(boolean animation) {
private void exitSearchMode() {
if (!mSearchMode) {
return;
}
@@ -756,29 +732,7 @@ public class FavoritesScene extends SearchBarScene implements
}
updateSearchBar();
assert mUrlBuilder != null;
int pages = 0;
if (result.nextPage == null)
pages = mHelper.pgCounter + 1;
else
pages = Integer.MAX_VALUE;
String prev = result.prevPage, next = result.nextPage;
switch (mHelper.operation) {
case -1:
if (prev != null) mHelper.upperPage = prev;
break;
case 1:
if (next != null) mHelper.lowerPage = next;
break;
default:
mHelper.upperPage = prev;
mHelper.lowerPage = next;
break;
}
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
mHelper.onGetPageData(taskId, 0, 0, result.prev, result.next, result.galleryInfoList);
if (mDrawerAdapter != null) {
mDrawerAdapter.notifyDataSetChanged();
@@ -803,9 +757,9 @@ public class FavoritesScene extends SearchBarScene implements
}
if (list.size() == 0) {
mHelper.onGetPageData(taskId, 0, 0, Collections.EMPTY_LIST);
mHelper.onGetPageData(taskId, 0, 0, null, null, Collections.EMPTY_LIST);
} else {
mHelper.onGetPageData(taskId, 1, 0, list);
mHelper.onGetPageData(taskId, 1, 0, null, null, list);
}
if (TextUtils.isEmpty(keyword)) {
@@ -967,7 +921,7 @@ public class FavoritesScene extends SearchBarScene implements
holder.value.setText(Integer.toString(mFavCountArray[position - 2]));
holder.itemView.setEnabled(true);
}
holder.itemView.setOnClickListener(v -> onItemClick(holder.itemView, position));
holder.itemView.setOnClickListener(v -> onItemClick(position));
}
@Override
@@ -1080,7 +1034,7 @@ public class FavoritesScene extends SearchBarScene implements
@Override
void onItemClick(View view, int position) {
FavoritesScene.this.onItemClick(view, position);
FavoritesScene.this.onItemClick(position);
}
@Override
@@ -1096,26 +1050,21 @@ public class FavoritesScene extends SearchBarScene implements
}
private class FavoritesHelper extends GalleryInfoContentHelper {
public String upperPage = null;
public String lowerPage = null;
public int operation = 0;
public int pgCounter = 0;
@Override
protected void getPageData(final int taskId, int type, int page) {
protected void getPageData(final int taskId, int type, int page, String index, boolean isNext) {
MainActivity activity = getMainActivity();
if (null == activity || null == mUrlBuilder || null == mClient) {
return;
}
pgCounter = page;
if (mEnableModify) {
mEnableModify = false;
boolean local = mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_LOCAL;
long[] gidArray = new long[mModifyGiList.size()];
if (mModifyAdd) {
long[] gidArray = new long[mModifyGiList.size()];
String[] tokenArray = new String[mModifyGiList.size()];
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
GalleryInfo gi = mModifyGiList.get(i);
@@ -1133,7 +1082,6 @@ public class FavoritesScene extends SearchBarScene implements
request.setArgs(gidArray, tokenArray, mModifyFavCat);
mClient.execute(request);
} else {
long[] gidArray = new long[mModifyGiList.size()];
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
gidArray[i] = mModifyGiList.get(i).gid;
}
@@ -1147,45 +1095,28 @@ public class FavoritesScene extends SearchBarScene implements
url = mUrlBuilder.build();
}
mUrlBuilder.setNext(lowerPage);
mUrlBuilder.setIndex(index, true);
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_MODIFY_FAVORITES);
request.setCallback(new GetFavoritesListener(getContext(),
activity.getStageId(), getTag(),
taskId, local, mUrlBuilder.getKeyword()));
request.setArgs(url, gidArray, mModifyFavCat, Settings.getShowJpnTitle());
request.setArgs(url, gidArray, mModifyFavCat);
mClient.execute(request);
}
} else if (mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_LOCAL) {
final String keyword = mUrlBuilder.getKeyword();
SimpleHandler.getInstance().post(() -> onGetFavoritesLocal(keyword, taskId));
} else {
String prev = null, next = null;
if (jumpTo != null) {
next = Integer.toString(minGid);
operation = 0;
} else if (page != 0) {
if (page >= mHelper.getPageForTop()) {
next = lowerPage;
operation = 1;
} else {
prev = upperPage;
operation = -1;
}
}
mUrlBuilder.setPrev(prev);
mUrlBuilder.setNext(next);
mUrlBuilder.setIndex(index, isNext);
mUrlBuilder.setJumpTo(jumpTo);
jumpTo = null;
String url = mUrlBuilder.build();
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
request.setCallback(new GetFavoritesListener(getContext(),
activity.getStageId(), getTag(),
taskId, false, mUrlBuilder.getKeyword()));
request.setArgs(url, Settings.getShowJpnTitle());
request.setArgs(url);
mClient.execute(request);
}
}
@@ -1207,13 +1138,6 @@ public class FavoritesScene extends SearchBarScene implements
}
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
if (mAdapter != null) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
}
@Override
protected void notifyItemRangeInserted(int positionStart, int itemCount) {
if (mAdapter != null) {
@@ -1237,34 +1161,5 @@ public class FavoritesScene extends SearchBarScene implements
showSearchBar();
}
}
@Override
protected void onClearData() {
super.onClearData();
}
@Override
protected void beforeRefresh() {
super.beforeRefresh();
upperPage = null;
lowerPage = null;
operation = 0;
}
@Override
protected Parcelable saveInstanceState(Parcelable superState) {
Bundle bundle = (Bundle) super.saveInstanceState(superState);
bundle.putString(KEY_UPPER_PAGE, upperPage);
bundle.putString(KEY_LOWER_PAGE, lowerPage);
return bundle;
}
@Override
protected Parcelable restoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
upperPage = bundle.getString(KEY_UPPER_PAGE);
lowerPage = bundle.getString(KEY_LOWER_PAGE);
return super.restoreInstanceState(state);
}
}
}
@@ -106,8 +106,7 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
RecyclerView recyclerView = mRecyclerView;
switch (type) {
default:
case GalleryAdapter.TYPE_LIST: {
case GalleryAdapter.TYPE_LIST -> {
int columnWidth = mResources.getDimensionPixelOffset(Settings.getDetailSizeResId());
mLayoutManager.setColumnSize(columnWidth);
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE);
@@ -123,9 +122,8 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
recyclerView.addItemDecoration(mListDecoration);
adjustPaddings();
notifyDataSetChanged();
break;
}
case GalleryAdapter.TYPE_GRID: {
case GalleryAdapter.TYPE_GRID -> {
int columnWidth = Settings.getThumbSize();
mLayoutManager.setColumnSize(columnWidth);
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_SUITABLE_SIZE);
@@ -141,7 +139,6 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
recyclerView.addItemDecoration(mGirdDecoration);
adjustPaddings();
notifyDataSetChanged();
break;
}
}
}
@@ -149,16 +146,11 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
@NonNull
@Override
public GalleryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
int layoutId;
switch (viewType) {
default:
case TYPE_LIST:
layoutId = R.layout.item_gallery_list;
break;
case TYPE_GRID:
layoutId = R.layout.item_gallery_grid;
break;
}
int layoutId = switch (viewType) {
case TYPE_LIST -> R.layout.item_gallery_list;
case TYPE_GRID -> R.layout.item_gallery_grid;
default -> throw new IllegalStateException("Unexpected value: " + viewType);
};
GalleryHolder holder = new GalleryHolder(mInflater.inflate(layoutId, parent, false));
@@ -169,6 +161,9 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
holder.thumb.setLayoutParams(lp);
}
holder.card.setOnClickListener(v -> onItemClick(holder.itemView, holder.getBindingAdapterPosition()));
holder.card.setOnLongClickListener(v -> onItemLongClick(holder.itemView, holder.getBindingAdapterPosition()));
return holder;
}
@@ -193,8 +188,7 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
}
switch (mType) {
default:
case TYPE_LIST: {
case TYPE_LIST -> {
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
holder.title.setText(EhUtils.getSuitableTitle(gi));
holder.uploader.setText(gi.uploader);
@@ -223,9 +217,8 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
}
holder.favourited.setVisibility((mShowFavourited && gi.favoriteSlot >= -1 && gi.favoriteSlot <= 10) ? View.VISIBLE : View.GONE);
holder.downloaded.setVisibility(mDownloadManager.containDownloadInfo(gi.gid) ? View.VISIBLE : View.GONE);
break;
}
case TYPE_GRID: {
case TYPE_GRID -> {
((TileThumb) holder.thumb).setThumbSize(gi.thumbWidth, gi.thumbHeight);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
View category = holder.category;
@@ -238,15 +231,11 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
((TriangleDrawable) drawable).setColor(color);
}
holder.simpleLanguage.setText(gi.simpleLanguage);
break;
}
}
// Update transition name
ViewCompat.setTransitionName(holder.thumb, TransitionNameFactory.getThumbTransitionName(gi.gid));
holder.card.setOnClickListener(v -> onItemClick(holder.itemView, position));
holder.card.setOnLongClickListener(v -> onItemLongClick(holder.itemView, position));
}
@IntDef({TYPE_LIST, TYPE_GRID})
@@ -62,6 +62,7 @@ import com.hippo.ehviewer.R;
import com.hippo.ehviewer.UrlOpener;
import com.hippo.ehviewer.WindowInsetsAnimationHelper;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.data.GalleryComment;
@@ -69,6 +70,7 @@ import com.hippo.ehviewer.client.data.GalleryCommentList;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.VoteCommentParser;
import com.hippo.ehviewer.dao.Filter;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.text.URLImageGetter;
@@ -194,7 +196,6 @@ public final class GalleryCommentsScene extends ToolbarScene
mEditPanel,
mFabLayout
));
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(getTheme(), androidx.appcompat.R.attr.colorPrimary));
mRefreshLayout.setOnRefreshListener(this);
Context context = requireContext();
@@ -266,6 +267,45 @@ public final class GalleryCommentsScene extends ToolbarScene
onBackPressed();
}
private void showFilterCommenterDialog(String commenter, int position) {
Context context = getContext();
if (context == null || commenter == null) {
return;
}
new BaseDialogBuilder(context)
.setMessage(getString(R.string.filter_the_commenter, commenter))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Filter filter = new Filter();
filter.mode = EhFilter.MODE_COMMENTER;
filter.text = commenter;
EhFilter.getInstance().addFilter(filter);
hideComment(position);
showTip(R.string.filter_added, LENGTH_SHORT);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void hideComment(int position) {
if (mGalleryDetail == null || mGalleryDetail.comments == null || mGalleryDetail.comments.comments == null) {
return;
}
GalleryComment[] oldCommentsList = mGalleryDetail.comments.comments;
GalleryComment[] newCommentsList = new GalleryComment[oldCommentsList.length - 1];
for (int i = 0, j = 0; i < oldCommentsList.length; i++) {
if (i != position) {
newCommentsList[j] = oldCommentsList[i];
j++;
}
}
mGalleryDetail.comments.comments = newCommentsList;
mAdapter.notifyDataSetChanged();
updateView(true);
}
private void voteComment(long id, int vote) {
Context context = getContext();
MainActivity activity = getMainActivity();
@@ -340,6 +380,10 @@ public final class GalleryCommentsScene extends ToolbarScene
menu.add(resources.getString(R.string.copy_comment_text));
menuId.add(R.id.copy);
if (!comment.uploader && !comment.editable) {
menu.add(resources.getString(R.string.block_commenter));
menuId.add(R.id.block_commenter);
}
if (comment.editable) {
menu.add(resources.getString(R.string.edit_comment));
menuId.add(R.id.edit_comment);
@@ -365,6 +409,8 @@ public final class GalleryCommentsScene extends ToolbarScene
int id = menuId.get(which);
if (id == R.id.copy) {
ClipboardUtilKt.addTextToClipboard(requireActivity(), comment.comment, false);
} else if (id == R.id.block_commenter) {
showFilterCommenterDialog(comment.user, position);
} else if (id == R.id.vote_up) {
voteComment(comment.id, 1);
} else if (id == R.id.vote_down) {
@@ -57,7 +57,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.widget.NestedScrollView;
import com.google.android.material.progressindicator.CircularProgressIndicator;
@@ -65,7 +64,6 @@ import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.app.CheckBoxDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.R;
@@ -90,11 +88,10 @@ import com.hippo.ehviewer.client.parser.RateGalleryParser;
import com.hippo.ehviewer.client.parser.VoteTagParser;
import com.hippo.ehviewer.dao.DownloadInfo;
import com.hippo.ehviewer.dao.Filter;
import com.hippo.ehviewer.gallery.EhGalleryProvider;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.ehviewer.gallery.EhPageLoader;
import com.hippo.ehviewer.gallery.PageLoader2;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.GalleryInfoBottomSheet;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
@@ -119,13 +116,13 @@ import com.hippo.yorozuya.IntIdGenerator;
import com.hippo.yorozuya.ViewUtils;
import com.hippo.yorozuya.collect.IntList;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import coil.Coil;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import okhttp3.HttpUrl;
import rikka.core.res.ResourcesKt;
@@ -263,7 +260,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private Pair<String, String>[] mArchiveList;
@State
private int mState = STATE_INIT;
private boolean mModifingFavorites;
private boolean mModifyingFavorites;
@Nullable
private static String getArtist(GalleryTagGroup[] tagGroups) {
@@ -295,47 +292,21 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
@StringRes
private int getRatingText(float rating) {
int resId;
switch (Math.round(rating * 2)) {
case 0:
resId = R.string.rating0;
break;
case 1:
resId = R.string.rating1;
break;
case 2:
resId = R.string.rating2;
break;
case 3:
resId = R.string.rating3;
break;
case 4:
resId = R.string.rating4;
break;
case 5:
resId = R.string.rating5;
break;
case 6:
resId = R.string.rating6;
break;
case 7:
resId = R.string.rating7;
break;
case 8:
resId = R.string.rating8;
break;
case 9:
resId = R.string.rating9;
break;
case 10:
resId = R.string.rating10;
break;
default:
resId = R.string.rating_none;
break;
}
return resId;
return switch (Math.round(rating * 2)) {
case 0 -> R.string.rating0;
case 1 -> R.string.rating1;
case 2 -> R.string.rating2;
case 3 -> R.string.rating3;
case 4 -> R.string.rating4;
case 5 -> R.string.rating5;
case 6 -> R.string.rating6;
case 7 -> R.string.rating7;
case 8 -> R.string.rating8;
case 9 -> R.string.rating9;
case 10 -> R.string.rating10;
default -> R.string.rating_none;
};
}
private void handleArgs(Bundle args) {
@@ -359,7 +330,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
@Nullable
private String getGalleryDetailUrl(boolean allComment) {
private String getGalleryDetailUrl() {
long gid;
String token;
if (mGalleryDetail != null) {
@@ -374,7 +345,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
} else {
return null;
}
return EhUrl.getGalleryDetailUrl(gid, token, 0, allComment);
return EhUrl.getGalleryDetailUrl(gid, token, 0, false);
}
// -1 for error
@@ -390,18 +361,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
}
private String getToken() {
if (mGalleryDetail != null) {
return mGalleryDetail.token;
} else if (mGalleryInfo != null) {
return mGalleryInfo.token;
} else if (ACTION_GID_TOKEN.equals(mAction)) {
return mToken;
} else {
return null;
}
}
private String getUploader() {
if (mGalleryDetail != null) {
return mGalleryDetail.uploader;
@@ -454,7 +413,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
super.onResume();
if (mRead != null) {
try {
GalleryProvider2 galleryProvider = new EhGalleryProvider(requireContext(), mGalleryInfo);
PageLoader2 galleryProvider = new EhPageLoader(mGalleryInfo);
galleryProvider.start();
int startPage = galleryProvider.getStartPage();
if (startPage != 0) {
@@ -521,14 +480,14 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
public boolean onMenuItemClick(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_open_in_other_app) {
String url = getGalleryDetailUrl(false);
String url = getGalleryDetailUrl();
Activity activity = getMainActivity();
if (null != url && null != activity) {
UrlOpener.openUrl(activity, url, false);
}
} else if (itemId == R.id.action_refresh) {
if (mState != STATE_REFRESH && mState != STATE_REFRESH_HEADER) {
adjustViewVisibility(STATE_REFRESH, true);
adjustViewVisibility(STATE_REFRESH);
request();
}
} else if (itemId == R.id.action_add_tag) {
@@ -552,7 +511,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return true;
}
@Nullable
@NonNull
@Override
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
@@ -657,16 +616,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (prepareData()) {
if (mGalleryDetail != null) {
bindViewSecond();
adjustViewVisibility(STATE_NORMAL, false);
adjustViewVisibility(STATE_NORMAL);
} else if (mGalleryInfo != null) {
bindViewFirst();
adjustViewVisibility(STATE_REFRESH_HEADER, false);
adjustViewVisibility(STATE_REFRESH_HEADER);
} else {
adjustViewVisibility(STATE_REFRESH, false);
adjustViewVisibility(STATE_REFRESH);
}
} else {
mTip.setText(R.string.error_cannot_find_gallery);
adjustViewVisibility(STATE_FAILED, false);
adjustViewVisibility(STATE_FAILED);
}
EhApplication.getDownloadManager().addDownloadInfoListener(this);
@@ -761,12 +720,12 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private boolean request() {
Context context = getContext();
MainActivity activity = getMainActivity();
String url = getGalleryDetailUrl(false);
String url = getGalleryDetailUrl();
if (null == context || null == activity || null == url) {
return false;
}
EhClient.Callback callback = new GetGalleryDetailListener(context,
EhClient.Callback<?> callback = new GetGalleryDetailListener(context,
activity.getStageId(), getTag());
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
@@ -778,7 +737,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return true;
}
private void adjustViewVisibility(int state, boolean animation) {
private void adjustViewVisibility(int state) {
if (state == mState) {
return;
}
@@ -786,34 +745,29 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return;
}
int oldState = mState;
mState = state;
animation = !TRANSITION_ANIMATION_DISABLED && animation;
boolean animation = !TRANSITION_ANIMATION_DISABLED && animation;
switch (state) {
case STATE_NORMAL:
case STATE_NORMAL -> {
// Show mMainView
mViewTransition.showView(0, animation);
// Show mBelowHeader
mViewTransition2.showView(0, animation);
break;
case STATE_REFRESH:
}
case STATE_REFRESH ->
// Show mProgressView
mViewTransition.showView(1, animation);
break;
case STATE_REFRESH_HEADER:
mViewTransition.showView(1, animation);
case STATE_REFRESH_HEADER -> {
// Show mMainView
mViewTransition.showView(0, animation);
// Show mProgress
mViewTransition2.showView(1, animation);
break;
default:
case STATE_INIT:
case STATE_FAILED:
}
case STATE_INIT, STATE_FAILED ->
// Show mFailedView
mViewTransition.showView(2, animation);
break;
mViewTransition.showView(2, animation);
}
}
@@ -868,10 +822,10 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (mPage != 0) {
Snackbar.make(requireActivity().findViewById(R.id.snackbar), getString(R.string.read_from, mPage), Snackbar.LENGTH_LONG)
.setAction(R.string.read, v -> {
Intent intent = new Intent(requireContext(), GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryDetail);
intent.putExtra(GalleryActivity.KEY_PAGE, mPage);
Intent intent = new Intent(requireContext(), ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryDetail);
intent.putExtra(ReaderActivity.KEY_PAGE, mPage);
startActivity(intent);
})
.show();
@@ -917,7 +871,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private void bindTags(GalleryTagGroup[] tagGroups) {
Context context = getContext();
LayoutInflater inflater = getLayoutInflater();
if (null == context || null == inflater || null == mTags || null == mNoTags) {
if (null == context || null == mTags || null == mNoTags) {
return;
}
@@ -929,7 +883,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mNoTags.setVisibility(View.GONE);
}
EhTagDatabase ehTags = Settings.getShowTagTranslations() ? EhTagDatabase.getInstance(context) : null;
EhTagDatabase ehTags = Settings.getShowTagTranslations() && EhTagDatabase.INSTANCE.isTranslatable(context) ? EhTagDatabase.INSTANCE : null;
int colorTag = ResourcesKt.resolveColor(getTheme(), R.attr.tagBackgroundColor);
int colorName = ResourcesKt.resolveColor(getTheme(), R.attr.tagGroupBackgroundColor);
for (GalleryTagGroup tg : tagGroups) {
@@ -938,8 +892,8 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mTags.addView(ll);
String readableTagName = null;
if (ehTags != null) {
readableTagName = ehTags.getTranslation("n:" + tg.groupName);
if (ehTags != null && ehTags.isInitialized()) {
readableTagName = ehTags.getTranslation("n", tg.groupName);
}
TextView tgName = (TextView) inflater.inflate(R.layout.item_gallery_tag, ll, false);
@@ -948,9 +902,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
tgName.setBackgroundTintList(ColorStateList.valueOf(colorName));
String prefix = EhTagDatabase.namespaceToPrefix(tg.groupName);
if (prefix == null) {
prefix = "";
}
AutoWrapLayout awl = new AutoWrapLayout(context);
ll.addView(awl, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -961,7 +912,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
String readableTag = null;
if (ehTags != null) {
readableTag = ehTags.getTranslation(prefix + tagStr);
readableTag = ehTags.getTranslation(prefix, tagStr);
}
tag.setText(readableTag != null ? readableTag : tagStr);
@@ -976,7 +927,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private void bindComments(GalleryComment[] comments) {
Context context = getContext();
LayoutInflater inflater = getLayoutInflater();
if (null == context || null == inflater || null == mComments || null == mCommentsText) {
if (null == context || null == mComments || null == mCommentsText) {
return;
}
@@ -1015,7 +966,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
LayoutInflater inflater = getLayoutInflater();
Resources resources = getResourcesOrNull();
int previewNum = Settings.getPreviewNum();
if (null == inflater || null == resources || null == mGridLayout || null == mPreviewText) {
if (null == resources || null == mGridLayout || null == mPreviewText) {
return;
}
@@ -1051,16 +1002,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return getString(R.string.rating_text, getString(getRatingText(rating)), rating, ratingCount);
}
private void setTransitionName() {
long gid = getGid();
if (gid != -1 && mThumb != null && mUploader != null && mCategory != null) {
ViewCompat.setTransitionName(mThumb, TransitionNameFactory.getThumbTransitionName(gid));
ViewCompat.setTransitionName(mUploader, TransitionNameFactory.getUploaderTransitionName(gid));
ViewCompat.setTransitionName(mCategory, TransitionNameFactory.getCategoryTransitionName(gid));
}
}
private void showSimilarGalleryList() {
GalleryDetail gd = mGalleryDetail;
if (null == gd) {
@@ -1122,7 +1063,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (mTip == v) {
if (request()) {
adjustViewVisibility(STATE_REFRESH, true);
adjustViewVisibility(STATE_REFRESH);
}
} else if (mUploader == v) {
String uploader = getUploader();
@@ -1166,9 +1107,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
galleryInfo = mGalleryDetail;
}
if (galleryInfo != null) {
Intent intent = new Intent(activity, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryInfo);
Intent intent = new Intent(activity, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryInfo);
startActivity(intent);
}
} else if (mNewerVersion == v) {
@@ -1193,17 +1134,17 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
var galleryInfoBottomSheet = new GalleryInfoBottomSheet(mGalleryDetail);
galleryInfoBottomSheet.show(requireActivity().getSupportFragmentManager(), GalleryInfoBottomSheet.TAG);
} else if (mHeart == v || mHeartOutline == v) {
if (mGalleryDetail != null && !mModifingFavorites) {
if (mGalleryDetail != null && !mModifyingFavorites) {
boolean remove = false;
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
mModifingFavorites = true;
mModifyingFavorites = true;
CommonOperations.removeFromFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(context,
activity.getStageId(), getTag(), true));
remove = true;
}
if (!remove) {
mModifingFavorites = true;
mModifyingFavorites = true;
CommonOperations.addToFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(context,
activity.getStageId(), getTag(), false));
@@ -1212,7 +1153,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
updateFavoriteDrawable();
}
} else if (mShare == v) {
String url = getGalleryDetailUrl(false);
String url = getGalleryDetailUrl();
if (url != null) {
AppHelper.share(activity, url);
}
@@ -1300,10 +1241,10 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
o = v.getTag(R.id.index);
if (null != galleryInfo && o instanceof Integer) {
int index = (Integer) o;
Intent intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryInfo);
intent.putExtra(GalleryActivity.KEY_PAGE, index);
Intent intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryInfo);
intent.putExtra(ReaderActivity.KEY_PAGE, index);
startActivity(intent);
}
}
@@ -1370,7 +1311,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
menu.add(resources.getString(android.R.string.copy));
menuId.add(R.id.copy);
if (!tag.equals(tv.getText().toString())) {
if (!tag2.equals(tv.getText().toString())) {
menu.add(resources.getString(R.string.copy_trans));
menuId.add(R.id.copy_trans);
}
@@ -1442,17 +1383,17 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
return true;
} else if (mHeart == v || mHeartOutline == v) {
if (mGalleryDetail != null && !mModifingFavorites) {
if (mGalleryDetail != null && !mModifyingFavorites) {
boolean remove = false;
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
mModifingFavorites = true;
mModifyingFavorites = true;
CommonOperations.removeFromFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(activity,
activity.getStageId(), getTag(), true));
remove = true;
}
if (!remove) {
mModifingFavorites = true;
mModifyingFavorites = true;
CommonOperations.addToFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(activity,
activity.getStageId(), getTag(), false), true);
@@ -1497,25 +1438,13 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return;
}
switch (mDownloadState) {
default:
case DownloadInfo.STATE_INVALID:
mDownload.setText(R.string.download);
break;
case DownloadInfo.STATE_NONE:
mDownload.setText(R.string.download_state_none);
break;
case DownloadInfo.STATE_WAIT:
mDownload.setText(R.string.download_state_wait);
break;
case DownloadInfo.STATE_DOWNLOAD:
mDownload.setText(R.string.download_state_downloading);
break;
case DownloadInfo.STATE_FINISH:
mDownload.setText(R.string.download_state_downloaded);
break;
case DownloadInfo.STATE_FAILED:
mDownload.setText(R.string.download_state_failed);
break;
case DownloadInfo.STATE_INVALID -> mDownload.setText(R.string.download);
case DownloadInfo.STATE_NONE -> mDownload.setText(R.string.download_state_none);
case DownloadInfo.STATE_WAIT -> mDownload.setText(R.string.download_state_wait);
case DownloadInfo.STATE_DOWNLOAD ->
mDownload.setText(R.string.download_state_downloading);
case DownloadInfo.STATE_FINISH -> mDownload.setText(R.string.download_state_downloaded);
case DownloadInfo.STATE_FAILED -> mDownload.setText(R.string.download_state_failed);
}
}
@@ -1575,7 +1504,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private void onGetGalleryDetailSuccess(GalleryDetail result) {
mGalleryDetail = result;
updateDownloadState();
adjustViewVisibility(STATE_NORMAL, true);
adjustViewVisibility(STATE_NORMAL);
bindViewSecond();
}
@@ -1585,7 +1514,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (null != context && null != mTip) {
String error = ExceptionUtils.getReadableString(e);
mTip.setText(error);
adjustViewVisibility(STATE_FAILED, true);
adjustViewVisibility(STATE_FAILED);
}
}
@@ -1603,26 +1532,26 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
private void onModifyFavoritesSuccess(boolean addOrRemove) {
mModifingFavorites = false;
mModifyingFavorites = false;
if (mGalleryDetail != null) {
mGalleryDetail.isFavorited = !addOrRemove && mGalleryDetail.favoriteName != null;
updateFavoriteDrawable();
}
}
private void onModifyFavoritesFailure(boolean addOrRemove) {
mModifingFavorites = false;
private void onModifyFavoritesFailure() {
mModifyingFavorites = false;
}
private void onModifyFavoritesCancel(boolean addOrRemove) {
mModifingFavorites = false;
private void onModifyFavoritesCancel() {
mModifyingFavorites = false;
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
String url = getGalleryDetailUrl(false);
String url = getGalleryDetailUrl();
if (url != null) {
outContent.setWebUri(Uri.parse(url));
}
@@ -1776,7 +1705,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
R.string.add_to_favorite_failure, LENGTH_LONG);
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onModifyFavoritesFailure(mAddOrRemove);
scene.onModifyFavoritesFailure();
}
}
@@ -1784,7 +1713,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
public void onCancel() {
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onModifyFavoritesCancel(mAddOrRemove);
scene.onModifyFavoritesCancel();
}
}
@@ -2110,7 +2039,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
}
private class DeleteDialogHelper implements DialogInterface.OnClickListener {
private static class DeleteDialogHelper implements DialogInterface.OnClickListener {
private final com.hippo.ehviewer.download.DownloadManager mDownloadManager;
private final GalleryInfo mGalleryInfo;
private final CheckBoxDialogBuilder mBuilder;
@@ -23,7 +23,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
import android.text.Spannable;
@@ -49,6 +48,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -88,7 +88,6 @@ import com.hippo.ehviewer.dao.DownloadLabel;
import com.hippo.ehviewer.dao.QuickSearch;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
import com.hippo.ehviewer.widget.GalleryInfoContentHelper;
@@ -118,6 +117,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import rikka.core.res.ResourcesKt;
@SuppressLint("RtlHardcoded")
@@ -140,8 +140,6 @@ public final class GalleryListScene extends SearchBarScene
private final static int STATE_SEARCH = 2;
private final static int STATE_SEARCH_SHOW_LIST = 3;
private static final long ANIMATE_TIME = 300L;
// Double click back exit
private final long mPressBackTime = 0;
/*---------------
Whole life cycle
---------------*/
@@ -149,16 +147,11 @@ public final class GalleryListScene extends SearchBarScene
private EhClient mClient;
@Nullable
private ListUrlBuilder mUrlBuilder;
/*---------------
View life cycle
---------------*/
@Nullable
private ContentLayout mContentLayout;
@Nullable
private EasyRecyclerView mRecyclerView;
@Nullable
private SearchLayout mSearchLayout;
ActivityResultLauncher<PickVisualMediaRequest> selectImageLauncher = registerForActivityResult(
final ActivityResultLauncher<PickVisualMediaRequest> selectImageLauncher = registerForActivityResult(
new ActivityResultContracts.PickVisualMedia(),
result -> mSearchLayout.setImageUri(result));
@Nullable
@@ -247,17 +240,13 @@ public final class GalleryListScene extends SearchBarScene
} else if (ListUrlBuilder.MODE_WHATS_HOT == urlBuilder.getMode()) {
return resources.getString(R.string.whats_hot);
} else if (ListUrlBuilder.MODE_TOPLIST == urlBuilder.getMode()) {
switch (urlBuilder.getKeyword()) {
case "11":
return resources.getString(R.string.toplist_alltime);
case "12":
return resources.getString(R.string.toplist_pastyear);
case "13":
return resources.getString(R.string.toplist_pastmonth);
case "15":
return resources.getString(R.string.toplist_yesterday);
}
return null;
return switch (urlBuilder.getKeyword()) {
case "11" -> resources.getString(R.string.toplist_alltime);
case "12" -> resources.getString(R.string.toplist_pastyear);
case "13" -> resources.getString(R.string.toplist_pastmonth);
case "15" -> resources.getString(R.string.toplist_yesterday);
default -> null;
};
} else if (!TextUtils.isEmpty(keyword)) {
return keyword;
} else if (MathUtils.hammingWeight(category) == 1) {
@@ -267,11 +256,6 @@ public final class GalleryListScene extends SearchBarScene
}
}
@Override
public int getMenuResId() {
return R.menu.scene_gallery_list_searchbar_menu;
}
public static void startScene(SceneFragment scene, ListUrlBuilder lub) {
scene.startScene(getStartAnnouncer(lub));
}
@@ -283,6 +267,11 @@ public final class GalleryListScene extends SearchBarScene
return new Announcer(GalleryListScene.class).setArgs(args);
}
@Override
public int getMenuResId() {
return R.menu.scene_gallery_list_searchbar_menu;
}
@Override
public int getNavCheckedItem() {
return mNavCheckedId;
@@ -325,6 +314,7 @@ public final class GalleryListScene extends SearchBarScene
showSearchBar();
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -478,30 +468,33 @@ public final class GalleryListScene extends SearchBarScene
private void onUpdateUrlBuilder() {
ListUrlBuilder builder = mUrlBuilder;
Resources resources = getResourcesOrNull();
if (resources == null || builder == null || mSearchLayout == null) {
if (resources == null || builder == null || mSearchLayout == null || mFabLayout == null) {
return;
}
String keyword = builder.getKeyword();
int category = builder.getCategory();
boolean isTopList = builder.getMode() == ListUrlBuilder.MODE_TOPLIST;
var mode = builder.getMode();
boolean isPopular = mode == ListUrlBuilder.MODE_WHATS_HOT;
boolean isTopList = mode == ListUrlBuilder.MODE_TOPLIST;
if (isTopList != mIsTopList) {
mIsTopList = isTopList;
recreateDrawerView();
mFabLayout.getSecondaryFabAt(0).setImageResource(isTopList ? R.drawable.ic_baseline_format_list_numbered_24 : R.drawable.v_magnify_x24);
}
mFabLayout.setSecondaryFabVisibilityAt(1, ListUrlBuilder.MODE_WHATS_HOT != builder.getMode());
// Update fab visibility
mFabLayout.setSecondaryFabVisibilityAt(0, !isPopular);
mFabLayout.setSecondaryFabVisibilityAt(2, !isPopular);
// Update normal search mode
mSearchLayout.setNormalSearchMode(builder.getMode() == ListUrlBuilder.MODE_SUBSCRIPTION
mSearchLayout.setNormalSearchMode(mode == ListUrlBuilder.MODE_SUBSCRIPTION
? R.id.search_subscription_search
: R.id.search_normal_search);
// Update search edit text
if (!TextUtils.isEmpty(keyword) && !mIsTopList) {
if (builder.getMode() == ListUrlBuilder.MODE_TAG) {
if (mode == ListUrlBuilder.MODE_TAG) {
keyword = wrapTagKeyword(keyword);
}
setSearchBarText(keyword);
@@ -516,19 +509,16 @@ public final class GalleryListScene extends SearchBarScene
// Update nav checked item
int checkedItemId;
if (ListUrlBuilder.MODE_NORMAL == builder.getMode() &&
EhUtils.NONE == category &&
TextUtils.isEmpty(keyword)) {
checkedItemId = R.id.nav_homepage;
} else if (ListUrlBuilder.MODE_SUBSCRIPTION == builder.getMode()) {
checkedItemId = R.id.nav_subscription;
} else if (ListUrlBuilder.MODE_WHATS_HOT == builder.getMode()) {
checkedItemId = R.id.nav_whats_hot;
} else if (ListUrlBuilder.MODE_TOPLIST == builder.getMode()) {
checkedItemId = R.id.nav_toplist;
} else {
checkedItemId = 0;
}
checkedItemId = switch (mode) {
case ListUrlBuilder.MODE_NORMAL ->
EhUtils.NONE == category && TextUtils.isEmpty(keyword) ? R.id.nav_homepage : 0;
case ListUrlBuilder.MODE_SUBSCRIPTION -> R.id.nav_subscription;
case ListUrlBuilder.MODE_WHATS_HOT -> R.id.nav_whats_hot;
case ListUrlBuilder.MODE_TOPLIST -> R.id.nav_toplist;
case ListUrlBuilder.MODE_TAG, ListUrlBuilder.MODE_UPLOADER, ListUrlBuilder.MODE_IMAGE_SEARCH ->
0;
default -> throw new IllegalStateException("Unexpected value: " + mode);
};
setNavCheckedItem(checkedItemId);
mNavCheckedId = checkedItemId;
}
@@ -547,18 +537,19 @@ public final class GalleryListScene extends SearchBarScene
mShowActionFab = true;
View mainLayout = ViewUtils.$$(view, R.id.main_layout);
mContentLayout = (ContentLayout) ViewUtils.$$(mainLayout, R.id.content_layout);
ContentLayout mContentLayout = (ContentLayout) ViewUtils.$$(mainLayout, R.id.content_layout);
mRecyclerView = mContentLayout.getRecyclerView();
FastScroller fastScroller = mContentLayout.getFastScroller();
mSearchLayout = (SearchLayout) ViewUtils.$$(mainLayout, R.id.search_layout);
mFabLayout = (FabLayout) ViewUtils.$$(mainLayout, R.id.fab_layout);
AssertUtils.assertNotNull(container);
mSearchFab = ViewUtils.$$(container, R.id.search_fab);
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
mFabLayout,
(View) mSearchFab.getParent()
));
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
container.addView(mFabLayout);
int paddingTopSB = resources.getDimensionPixelOffset(R.dimen.gallery_padding_top_search_bar);
@@ -601,7 +592,7 @@ public final class GalleryListScene extends SearchBarScene
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
mActionFabDrawable = new AddDeleteDrawable(context, colorID);
mFabLayout.getPrimaryFab().setImageDrawable(mActionFabDrawable);
mSearchFab.setOnClickListener( (v) -> {
mSearchFab.setOnClickListener((v) -> {
if (STATE_NORMAL != mState) {
onApplySearch();
hideSoftInput();
@@ -640,7 +631,7 @@ public final class GalleryListScene extends SearchBarScene
mRecyclerView = null;
}
if (null != mFabLayout) {
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
removeAboveSnackView(mFabLayout);
mFabLayout = null;
}
@@ -651,8 +642,7 @@ public final class GalleryListScene extends SearchBarScene
mActionFabDrawable = null;
}
private void showQuickSearchTipDialog(final QsDrawerAdapter adapter,
final EasyRecyclerView recyclerView, final TextView tip) {
private void showQuickSearchTipDialog() {
Context context = getContext();
if (null == context) {
return;
@@ -668,7 +658,7 @@ public final class GalleryListScene extends SearchBarScene
final EasyRecyclerView recyclerView, final TextView tip) {
Context context = getContext();
final ListUrlBuilder urlBuilder = mUrlBuilder;
if (null == context || null == urlBuilder) {
if (null == context || null == urlBuilder || null == mHelper) {
return;
}
@@ -678,11 +668,17 @@ public final class GalleryListScene extends SearchBarScene
return;
}
var gi = mHelper.getFirstVisibleItem();
var next = gi != null ? "@" + (gi.gid + 1) : null;
// Check duplicate
for (QuickSearch q : mQuickSearchList) {
if (urlBuilder.equalsQuickSearch(q)) {
showTip(getString(R.string.duplicate_quick_search, q.name), LENGTH_LONG);
return;
var i = q.name.lastIndexOf("@");
if (i != -1 && q.name.substring(i).equals(next)) {
showTip(getString(R.string.duplicate_quick_search, q.name), LENGTH_LONG);
return;
}
}
}
@@ -690,6 +686,10 @@ public final class GalleryListScene extends SearchBarScene
getSuitableTitleForUrlBuilder(context.getResources(), urlBuilder, false), getString(R.string.quick_search));
builder.setTitle(R.string.add_quick_search_dialog_title);
builder.setPositiveButton(android.R.string.ok, null);
// TODO: It's ugly
final boolean[] checked = {Settings.getQSSaveProgress()};
final String[] hint = {getString(R.string.save_progress)};
builder.setMultiChoiceItems(hint, checked, (dialog, which, isChecked) -> checked[which] = isChecked);
final AlertDialog dialog = builder.show();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
String text = builder.getText().trim();
@@ -700,6 +700,13 @@ public final class GalleryListScene extends SearchBarScene
return;
}
if (checked[0] && next != null) {
text += next;
Settings.putQSSaveProgress(true);
} else {
Settings.putQSSaveProgress(false);
}
// Check name duplicate
for (QuickSearch q : mQuickSearchList) {
if (text.equals(q.name)) {
@@ -756,7 +763,7 @@ public final class GalleryListScene extends SearchBarScene
if (id == R.id.action_add) {
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
} else if (id == R.id.action_help) {
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
showQuickSearchTipDialog();
}
return true;
});
@@ -776,25 +783,11 @@ public final class GalleryListScene extends SearchBarScene
if (null != mFabLayout && mFabLayout.isExpanded()) {
return false;
}
if (isSearchViewShown())
return false;
switch (mState) {
case STATE_NORMAL:
return getStackIndex() == 0;
case STATE_SIMPLE_SEARCH:
case STATE_SEARCH:
case STATE_SEARCH_SHOW_LIST:
return false;
}
return false;
return mState == STATE_NORMAL && getStackIndex() == 0;
}
@Override
public void onBackPressed() {
if (isSearchViewShown()) {
hideSearchView();
return;
}
if (null != mFabLayout && mFabLayout.isExpanded()) {
mFabLayout.setExpanded(false);
return;
@@ -817,14 +810,14 @@ public final class GalleryListScene extends SearchBarScene
}
}
public boolean onItemClick(View view, int position) {
public void onItemClick(int position) {
if (null == mHelper || null == mRecyclerView) {
return false;
return;
}
GalleryInfo gi = mHelper.getDataAtEx(position);
if (gi == null) {
return true;
return;
}
Bundle args = new Bundle();
@@ -832,7 +825,6 @@ public final class GalleryListScene extends SearchBarScene
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, gi);
Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args);
startScene(announcer);
return true;
}
@Override
@@ -844,13 +836,13 @@ public final class GalleryListScene extends SearchBarScene
private void showGoToDialog() {
Context context = getContext();
if (null == context || null == mHelper) {
if (null == context || null == mHelper || null == mUrlBuilder) {
return;
}
if (ListUrlBuilder.MODE_TOPLIST == mUrlBuilder.getMode()) {
if (mIsTopList) {
final int page = mHelper.getPageForTop() + 1;
final int pages = 200;
final int pages = mHelper.getPages();
String hint = getString(R.string.go_to_hint, page, pages);
final EditTextDialogBuilder builder = new EditTextDialogBuilder(context, null, hint);
builder.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
@@ -876,7 +868,7 @@ public final class GalleryListScene extends SearchBarScene
return;
}
builder.setError(null);
mHelper.goToPage(goTo);
mHelper.goTo(goTo);
dialog.dismiss();
});
} else {
@@ -884,7 +876,7 @@ public final class GalleryListScene extends SearchBarScene
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
ArrayList listValidators = new ArrayList<>();
ArrayList<CalendarConstraints.DateValidator> listValidators = new ArrayList<>();
listValidators.add(DateValidatorPointForward.from(fromDate));
listValidators.add(DateValidatorPointBackward.before(toDate));
@@ -899,9 +891,7 @@ public final class GalleryListScene extends SearchBarScene
.setSelection(toDate)
.build();
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
datePicker.addOnPositiveButtonClickListener(v -> {
mHelper.goTo(v);
});
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v, true));
}
}
@@ -912,17 +902,20 @@ public final class GalleryListScene extends SearchBarScene
}
switch (position) {
case 0: // Open right
openDrawer(Gravity.RIGHT);
break;
case 1: // Go to
if (mHelper.canGoTo()) {
showGoToDialog();
// Go to
case 0 -> {
if (mHelper.canGoTo()) showGoToDialog();
}
// Refresh
case 1 -> mHelper.refresh();
// Last page
case 2 -> {
if (mIsTopList) {
mHelper.goTo(mHelper.getPages() - 1);
} else {
mHelper.goTo("1", false);
}
break;
case 2: // Refresh
mHelper.refresh();
break;
}
}
view.setExpanded(false);
@@ -935,12 +928,12 @@ public final class GalleryListScene extends SearchBarScene
}
if (expanded) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
mActionFabDrawable.setDelete(ANIMATE_TIME);
} else {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
mActionFabDrawable.setAdd(ANIMATE_TIME);
}
}
@@ -999,9 +992,9 @@ public final class GalleryListScene extends SearchBarScene
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
switch (which) {
case 0: // Read
Intent intent = new Intent(activity, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi);
Intent intent = new Intent(activity, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, gi);
startActivity(intent);
break;
case 1: // Download
@@ -1186,21 +1179,12 @@ public final class GalleryListScene extends SearchBarScene
}
break;
case STATE_SEARCH:
if (state == STATE_NORMAL) {
mViewTransition.showView(0, animation);
selectActionFab(animation);
} else if (state == STATE_SIMPLE_SEARCH) {
mViewTransition.showView(0, animation);
} else if (state == STATE_SEARCH_SHOW_LIST) {
}
break;
case STATE_SEARCH_SHOW_LIST:
if (state == STATE_NORMAL) {
mViewTransition.showView(0, animation);
selectActionFab(animation);
} else if (state == STATE_SIMPLE_SEARCH) {
mViewTransition.showView(0, animation);
} else if (state == STATE_SEARCH) {
}
break;
}
@@ -1208,12 +1192,6 @@ public final class GalleryListScene extends SearchBarScene
((StageActivity) requireActivity()).updateBackPressCallBackStatus();
}
public void onClickTitle() {
if (mState == STATE_NORMAL) {
setState(STATE_SIMPLE_SEARCH);
}
}
@Override
public boolean onMenuItemClick(@NonNull MenuItem item) {
if (mState == STATE_NORMAL) {
@@ -1251,35 +1229,26 @@ public final class GalleryListScene extends SearchBarScene
setState(STATE_NORMAL);
}
public void onReceiveContent(Uri uri) {
if (mSearchLayout == null || uri == null) {
return;
}
mSearchLayout.setSearchMode(SearchLayout.SEARCH_MODE_IMAGE);
mSearchLayout.setImageUri(uri);
setState(STATE_SEARCH);
}
@Override
public void onStartDragHandler() {
// Lock right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
public void onEndDragHandler() {
// Restore right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
showSearchBar();
}
public void onStateChange(int newState) {
if (newState == STATE_NORMAL || newState == STATE_SIMPLE_SEARCH) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
} else {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
}
@@ -1302,21 +1271,16 @@ public final class GalleryListScene extends SearchBarScene
private void onGetGalleryListSuccess(GalleryListParser.Result result, int taskId) {
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
if (mHelper != null && mHelper.isCurrentTask(taskId) && mUrlBuilder != null) {
String emptyString = getString(mUrlBuilder.getMode() == ListUrlBuilder.MODE_SUBSCRIPTION && result.noWatchedTags
? R.string.gallery_list_empty_hit_subscription
: R.string.gallery_list_empty_hit);
mHelper.setEmptyString(emptyString);
int pages = 0;
if (mIsTopList)
pages = 200;
else if (result.nextGid == 0)
pages = mHelper.pgCounter + 1;
else
pages = result.founds;
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
if (mIsTopList) {
mHelper.onGetPageData(taskId, result.pages, result.nextPage, null, null, result.galleryInfoList);
} else {
mHelper.onGetPageData(taskId, 0, 0, result.prev, result.next, result.galleryInfoList);
}
}
}
@@ -1487,12 +1451,13 @@ public final class GalleryListScene extends SearchBarScene
return;
}
mUrlBuilder.set(mQuickSearchList.get(position));
mUrlBuilder.setNextGid(0);
var q = mQuickSearchList.get(position);
mUrlBuilder.set(q);
onUpdateUrlBuilder();
mHelper.refresh();
var i = q.name.lastIndexOf("@");
mHelper.goTo(i != -1 ? q.name.substring(i + 1) : null, true);
setState(STATE_NORMAL);
closeDrawer(Gravity.RIGHT);
closeDrawer(GravityCompat.END);
});
} else {
int[] keywords = {11, 12, 13, 15};
@@ -1505,11 +1470,10 @@ public final class GalleryListScene extends SearchBarScene
}
mUrlBuilder.setKeyword(String.valueOf(keywords[position]));
mUrlBuilder.setNextGid(0);
onUpdateUrlBuilder();
mHelper.refresh();
setState(STATE_NORMAL);
closeDrawer(Gravity.RIGHT);
closeDrawer(GravityCompat.END);
});
}
}
@@ -1619,7 +1583,7 @@ public final class GalleryListScene extends SearchBarScene
@Override
void onItemClick(View view, int position) {
GalleryListScene.this.onItemClick(view, position);
GalleryListScene.this.onItemClick(position);
}
@Override
@@ -1635,38 +1599,20 @@ public final class GalleryListScene extends SearchBarScene
}
private class GalleryListHelper extends GalleryInfoContentHelper {
public int pgCounter = 0;
@Override
protected void getPageData(int taskId, int type, int page) {
pgCounter = page;
protected void getPageData(int taskId, int type, int page, String index, boolean isNext) {
MainActivity activity = getMainActivity();
if (null == activity || null == mClient || null == mUrlBuilder) {
if (null == activity || null == mClient || null == mHelper || null == mUrlBuilder) {
return;
}
int prevGid = 0, nextGid = 0;
if (mIsTopList) {
if (jumpTo != null) {
pgCounter = Integer.parseInt(jumpTo);
nextGid = pgCounter;
} else {
nextGid = page;
}
} else if (jumpTo != null) {
nextGid = minGid;
} else if (page != 0) {
if (page >= mHelper.getPageForTop())
nextGid = minGid;
else
prevGid = maxGid;
mUrlBuilder.setJumpTo(String.valueOf(page));
} else {
mUrlBuilder.setIndex(index, isNext);
mUrlBuilder.setJumpTo(jumpTo);
}
mUrlBuilder.setPrevGid(prevGid);
mUrlBuilder.setNextGid(nextGid);
mUrlBuilder.setJumpTo(jumpTo);
jumpTo = null;
if (ListUrlBuilder.MODE_IMAGE_SEARCH == mUrlBuilder.getMode()) {
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_IMAGE_SEARCH);
@@ -1692,6 +1638,7 @@ public final class GalleryListScene extends SearchBarScene
return GalleryListScene.this.getContext();
}
@SuppressLint("NotifyDataSetChanged")
@Override
protected void notifyDataSetChanged() {
if (null != mAdapter) {
@@ -1699,13 +1646,6 @@ public final class GalleryListScene extends SearchBarScene
}
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
if (null != mAdapter) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
}
@Override
protected void notifyItemRangeInserted(int positionStart, int itemCount) {
if (null != mAdapter) {
@@ -21,7 +21,6 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -50,7 +49,6 @@ import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.data.GalleryPreview;
import com.hippo.ehviewer.client.data.PreviewSet;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.widget.ContentLayout;
@@ -63,6 +61,8 @@ import com.hippo.yorozuya.ViewUtils;
import java.util.ArrayList;
import java.util.Locale;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
public class GalleryPreviewsScene extends ToolbarScene {
public static final String KEY_GALLERY_INFO = "gallery_info";
@@ -76,11 +76,6 @@ public class GalleryPreviewsScene extends ToolbarScene {
@Nullable
private GalleryInfo mGalleryInfo;
/*---------------
View life cycle
---------------*/
@Nullable
private ContentLayout mContentLayout;
@Nullable
private EasyRecyclerView mRecyclerView;
@Nullable
@@ -132,11 +127,11 @@ public class GalleryPreviewsScene extends ToolbarScene {
outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo);
}
@Nullable
@NonNull
@Override
public View onCreateViewWithToolbar(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mContentLayout = (ContentLayout) inflater.inflate(
ContentLayout mContentLayout = (ContentLayout) inflater.inflate(
R.layout.scene_gallery_previews, container, false);
mContentLayout.hideFastScroll();
mRecyclerView = mContentLayout.getRecyclerView();
@@ -144,7 +139,6 @@ public class GalleryPreviewsScene extends ToolbarScene {
Context context = getContext();
AssertUtils.assertNotNull(context);
Resources resources = context.getResources();
mAdapter = new GalleryPreviewAdapter();
mRecyclerView.setAdapter(mAdapter);
@@ -191,12 +185,14 @@ public class GalleryPreviewsScene extends ToolbarScene {
super.onViewCreated(view, savedInstanceState);
setTitle(R.string.gallery_previews);
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
if (((GalleryDetail) mGalleryInfo).previewPages > 2)
showMenu(R.menu.scene_gallery_previews);
if (mGalleryInfo != null) {
if (((GalleryDetail) mGalleryInfo).previewPages > 2)
showMenu(R.menu.scene_gallery_previews);
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
public boolean onMenuItemClick(@NonNull MenuItem item) {
Context context = getContext();
if (null == context) {
return false;
@@ -232,10 +228,10 @@ public class GalleryPreviewsScene extends ToolbarScene {
if (null != context && null != mHelper && null != mGalleryInfo) {
GalleryPreview p = mHelper.getDataAtEx(position);
if (p != null) {
Intent intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryInfo);
intent.putExtra(GalleryActivity.KEY_PAGE, p.getPosition());
Intent intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryInfo);
intent.putExtra(ReaderActivity.KEY_PAGE, p.getPosition());
startActivity(intent);
}
}
@@ -251,7 +247,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
list.add(previewSet.getGalleryPreview(mGalleryInfo.gid, i));
}
mHelper.onGetPageData(taskId, result.second, 0, list);
mHelper.onGetPageData(taskId, result.second, 0, null, null, list);
}
}
@@ -347,7 +343,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
private class GalleryPreviewHelper extends ContentLayout.ContentHelper<GalleryPreview> {
@Override
protected void getPageData(final int taskId, int type, int page) {
protected void getPageData(final int taskId, int type, int page, String index, boolean isNext) {
MainActivity activity = getMainActivity();
if (null == activity || null == mClient || null == mGalleryInfo) {
onGetException(taskId, new EhException(getString(R.string.error_cannot_find_gallery)));
@@ -368,6 +364,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return GalleryPreviewsScene.this.getContext();
}
@SuppressLint("NotifyDataSetChanged")
@Override
protected void notifyDataSetChanged() {
if (mAdapter != null) {
@@ -375,13 +372,6 @@ public class GalleryPreviewsScene extends ToolbarScene {
}
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
if (mAdapter != null) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
}
@Override
protected void notifyItemRangeInserted(int positionStart, int itemCount) {
if (mAdapter != null) {
@@ -25,6 +25,7 @@ import android.view.*
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.lifecycle.lifecycleScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
@@ -45,7 +46,6 @@ import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.dao.HistoryInfo
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.GalleryActivity
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
import com.hippo.ehviewer.widget.SimpleRatingView
import com.hippo.scene.Announcer
@@ -54,6 +54,7 @@ import com.hippo.view.ViewTransition
import com.hippo.widget.LoadImageView
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager
import com.hippo.yorozuya.ViewUtils
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import rikka.core.res.resolveColor
@@ -190,7 +191,7 @@ class HistoryScene : ToolbarScene() {
@SuppressLint("RtlHardcoded")
override fun onNavigationClick() {
toggleDrawer(Gravity.LEFT)
toggleDrawer(GravityCompat.START)
}
override fun getMenuResId(): Int {
@@ -267,9 +268,9 @@ class HistoryScene : ToolbarScene() {
) { _: DialogInterface?, which: Int ->
when (which) {
0 -> {
val intent = Intent(activity, GalleryActivity::class.java)
intent.action = GalleryActivity.ACTION_EH
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
val intent = Intent(activity, ReaderActivity::class.java)
intent.action = ReaderActivity.ACTION_EH
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, gi)
startActivity(intent)
}
@@ -371,6 +372,7 @@ class HistoryScene : ToolbarScene() {
val simpleLanguage: TextView = itemView.findViewById(R.id.simple_language)
val pages: TextView = itemView.findViewById(R.id.pages)
val downloaded: ImageView = itemView.findViewById(R.id.downloaded)
val favourited: ImageView = itemView.findViewById(R.id.favourited)
}
private inner class MoveDialogHelper(
@@ -433,6 +435,8 @@ class HistoryScene : ToolbarScene() {
}
holder.downloaded.visibility =
if (mDownloadManager.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
holder.favourited.visibility =
if (gi.favoriteSlot != -2) View.VISIBLE else View.GONE
holder.card.setOnClickListener { onItemClick(gi) }
holder.card.setOnLongClickListener { onItemLongClick(gi) }
}
@@ -9,18 +9,29 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.annotation.CallSuper
import androidx.core.view.GravityCompat
import androidx.core.widget.addTextChangedListener
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import com.google.android.material.search.SearchView.TransitionListener
import com.google.android.material.shape.MaterialShapeDrawable
import com.hippo.app.BaseDialogBuilder
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.widget.SearchDatabase
import com.hippo.scene.StageActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
abstract class SearchBarScene : ToolbarScene() {
@@ -41,7 +52,8 @@ abstract class SearchBarScene : ToolbarScene() {
inflater.inflate(R.layout.scene_searchbar, container, false) as ViewGroup
mToolbar = view.findViewById(R.id.toolbar)
mAppBarLayout = view.findViewById(R.id.appbar)
mAppBarLayout?.statusBarForeground = MaterialShapeDrawable.createWithElevationOverlay(context)
mAppBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(context)
mSearchView = view.findViewById(R.id.searchview)
mRecyclerView = view.findViewById(R.id.search_bar_list)
@@ -52,7 +64,7 @@ abstract class SearchBarScene : ToolbarScene() {
(mToolbar as SearchBar).text = mSearchView!!.text
mSearchView?.hide()
onApplySearch()
false
true
}
mSuggestionList = ArrayList()
mSuggestionAdapter = SuggestionAdapter(LayoutInflater.from(context))
@@ -65,18 +77,46 @@ abstract class SearchBarScene : ToolbarScene() {
else if (newState == SearchView.TransitionState.HIDING)
onSearchViewHidden()
}
mSearchView?.addTransitionListener(mSearchViewOnBackPressedCallback)
requireActivity().onBackPressedDispatcher.addCallback(mSearchViewOnBackPressedCallback)
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
return view.apply { addView(contentView, 0) }
}
override fun onDestroyView() {
super.onDestroyView()
mSearchViewOnBackPressedCallback.remove()
}
private var privLockModeStart: Int? = null
private var privLockModeEnd: Int? = null
@CallSuper
open fun onSearchViewExpanded() {
privLockModeStart = getDrawerLockMode(GravityCompat.START)
privLockModeEnd = getDrawerLockMode(GravityCompat.END)
privLockModeStart?.let {
setDrawerLockMode(
DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
GravityCompat.START
)
}
privLockModeEnd?.let {
setDrawerLockMode(
DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
GravityCompat.END
)
}
(requireActivity() as StageActivity).updateBackPressCallBackStatus()
updateSuggestions()
}
@CallSuper
open fun onSearchViewHidden() {
privLockModeStart?.let { setDrawerLockMode(it, GravityCompat.START) }
privLockModeStart = null
privLockModeEnd?.let { setDrawerLockMode(it, GravityCompat.END) }
privLockModeEnd = null
(requireActivity() as StageActivity).updateBackPressCallBackStatus()
}
@@ -109,7 +149,7 @@ abstract class SearchBarScene : ToolbarScene() {
}
fun onApplySearch() {
val query = mSearchView?.text.toString().trim()
val query = (mToolbar as SearchBar).text.toString().trim()
if (!mAllowEmptySearch && query.isEmpty()) {
return
}
@@ -121,14 +161,6 @@ abstract class SearchBarScene : ToolbarScene() {
mAllowEmptySearch = allowEmptySearch
}
fun isSearchViewShown(): Boolean {
return mSearchView?.isShowing ?: false
}
fun hideSearchView() {
mSearchView?.hide()
}
private fun wrapTagKeyword(keyword: String): String {
var keyword = keyword
keyword = keyword.trim { it <= ' ' }
@@ -219,9 +251,13 @@ abstract class SearchBarScene : ToolbarScene() {
}
inner class TagSuggestion constructor(var mHint: String, var mKeyword: String) : Suggestion() {
inner class TagSuggestion constructor(
private var mHint: String?,
private var mKeyword: String
) :
Suggestion() {
override fun getText(textView: TextView): CharSequence {
override fun getText(textView: TextView): CharSequence? {
return if (textView.id == android.R.id.text1) {
mKeyword
} else {
@@ -238,7 +274,7 @@ abstract class SearchBarScene : ToolbarScene() {
temp = text.substring(0, text.lastIndexOf(" ")) + " " + temp
}
it.setText(temp)
it.setSelection(text.length)
it.setSelection(temp.length)
}
}
}
@@ -275,32 +311,44 @@ abstract class SearchBarScene : ToolbarScene() {
@SuppressLint("NotifyDataSetChanged")
private fun updateSuggestions(scrollToTop: Boolean = true) {
val suggestions = arrayListOf<Suggestion>()
val text = mSearchView?.editText?.text?.toString() ?: return
mSuggestionProvider?.run {
providerSuggestions(text)?.let { suggestions.addAll(it) }
}
mSearchDatabase.getSuggestions(text, 128).forEach { suggestions.add(KeywordSuggestion(it)) }
val ehTagDatabase = EhTagDatabase.getInstance(context)
if (!TextUtils.isEmpty(text) && ehTagDatabase != null && !text.endsWith(" ")) {
val s = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (s.isNotEmpty()) {
val keyword = s[s.size - 1]
val searchHints = ehTagDatabase.suggest(keyword)
for (searchHint in searchHints) {
suggestions.add(TagSuggestion(searchHint.first, searchHint.second))
}
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
val suggestions = mutableListOf<Suggestion>()
mergedSuggestionFlow().collect {
suggestions.add(it)
}
withContext(Dispatchers.Main) {
mSuggestionList = suggestions
mSuggestionAdapter?.notifyDataSetChanged()
}
}
mSuggestionList = suggestions
mSuggestionAdapter?.notifyDataSetChanged()
if (scrollToTop) {
mRecyclerView?.scrollToPosition(0)
}
}
private fun mergedSuggestionFlow(): Flow<Suggestion> = flow {
mSearchView?.editText?.text?.toString()?.let { text ->
mSuggestionProvider?.run { providerSuggestions(text)?.forEach { emit(it) } }
mSearchDatabase.getSuggestions(text, 128).forEach { emit(KeywordSuggestion(it)) }
EhTagDatabase.takeIf { it.isInitialized() }?.run {
if (!TextUtils.isEmpty(text) && !text.endsWith(" ")) {
val s = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (s.isNotEmpty()) {
val keyword = s[s.size - 1]
val translate =
Settings.getShowTagTranslations() && isTranslatable(requireContext())
suggestFlow(keyword, translate, true).collect {
emit(TagSuggestion(it.first, it.second))
}
suggestFlow(keyword, translate).collect {
emit(TagSuggestion(it.first, it.second))
}
}
}
}
}
}
fun setSuggestionProvider(suggestionProvider: SuggestionProvider) {
mSuggestionProvider = suggestionProvider
}
@@ -308,4 +356,22 @@ abstract class SearchBarScene : ToolbarScene() {
fun showSearchBar() {
mAppBarLayout?.setExpanded(true)
}
private val mSearchViewOnBackPressedCallback =
object : OnBackPressedCallback(false), TransitionListener {
override fun handleOnBackPressed() {
mSearchView?.hide()
}
override fun onStateChanged(
searchView: SearchView,
previousState: SearchView.TransitionState,
newState: SearchView.TransitionState
) {
if (newState == SearchView.TransitionState.SHOWING)
isEnabled = true
else if (newState == SearchView.TransitionState.HIDING)
isEnabled = false
}
}
}
@@ -113,7 +113,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
outState.putInt(KEY_REQUEST_ID, mRequestId);
}
@Nullable
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
@@ -280,9 +280,9 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
showProgress(true);
// Clean up for sign in
EhUtils.signOut(context);
EhUtils.signOut();
EhCallback callback = new SignInListener(context,
EhCallback<?, ?> callback = new SignInListener(context,
activity.getStageId(), getTag());
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
@@ -304,13 +304,16 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
hideSoftInput();
showProgress(true);
EhCallback callback = new GetProfileListener(context,
EhCallback<?, ?> callback = new GetProfileListener(context,
activity.getStageId(), getTag());
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_PROFILE)
.setCallback(callback);
EhApplication.getEhClient().execute(request);
EhApplication.getEhClient().execute(new EhRequest()
.setMethod(EhClient.METHOD_GET_UCONFIG));
}
private void redirectTo() {
@@ -65,7 +65,7 @@ public class WebViewSignInScene extends SolidScene {
Context context = getContext();
AssertUtils.assertNotNull(context);
EhUtils.signOut(context);
EhUtils.signOut();
// http://stackoverflow.com/questions/32284642/how-to-handle-an-uncatched-exception
CookieManager cookieManager = CookieManager.getInstance();
@@ -117,7 +117,7 @@ public class WebViewSignInScene extends SolidScene {
return cookies != null ? cookies : Collections.emptyList();
}
private void addCookie(Context context, String domain, Cookie cookie) {
private void addCookie(String domain, Cookie cookie) {
EhApplication.getEhCookieStore().addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
}
@@ -142,8 +142,8 @@ public class WebViewSignInScene extends SolidScene {
} else if (EhCookieStore.KEY_IPD_PASS_HASH.equals(cookie.name())) {
getHash = true;
}
addCookie(context, EhUrl.DOMAIN_EX, cookie);
addCookie(context, EhUrl.DOMAIN_E, cookie);
addCookie(EhUrl.DOMAIN_EX, cookie);
addCookie(EhUrl.DOMAIN_E, cookie);
}
if (getId && getHash) {
@@ -1,202 +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.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.yorozuya.ViewUtils;
public class GalleryGuideView extends ViewGroup implements View.OnClickListener {
private final float[] mPoints = new float[3 * 4];
private int mBgColor;
private Paint mPaint;
private int mStep;
private TextView mLeftText;
private TextView mRightText;
private TextView mMenuText;
private TextView mProgressText;
private TextView mLongClickText;
public GalleryGuideView(Context context) {
super(context);
init(context);
}
public GalleryGuideView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public GalleryGuideView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mBgColor = Color.GRAY;
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(context.getResources().getDimension(R.dimen.gallery_guide_divider_width));
setOnClickListener(this);
setWillNotDraw(false);
bind();
}
private void bind() {
// Clear up
removeAllViews();
mLeftText = null;
mRightText = null;
mMenuText = null;
mProgressText = null;
mLongClickText = null;
switch (mStep) {
case 0:
bind1();
break;
default:
case 1:
bind2();
break;
}
}
private void bind1() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_gallery_guide_1, this);
mLeftText = (TextView) getChildAt(0);
mRightText = (TextView) getChildAt(1);
mMenuText = (TextView) getChildAt(2);
mProgressText = (TextView) getChildAt(3);
}
private void bind2() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_gallery_guide_2, this);
mLongClickText = (TextView) getChildAt(0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (MeasureSpec.EXACTLY != widthMode || MeasureSpec.EXACTLY != heightMode) {
throw new IllegalStateException();
}
switch (mStep) {
case 0:
mLeftText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
mRightText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
mMenuText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize / 2, MeasureSpec.EXACTLY));
mProgressText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize / 2, MeasureSpec.EXACTLY));
break;
default:
case 1:
mLongClickText.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
break;
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
switch (mStep) {
case 0:
mLeftText.layout(0, 0, width / 3, height);
mRightText.layout(width * 2 / 3, 0, width, height);
mMenuText.layout(width / 3, 0, width * 2 / 3, height / 2);
mProgressText.layout(width / 3, height / 2, width * 2 / 3, height);
break;
default:
case 1:
mLongClickText.layout(0, 0, width, height);
break;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (0 == mStep) {
mPoints[0] = w / 3;
mPoints[1] = 0;
mPoints[2] = w / 3;
mPoints[3] = h;
mPoints[4] = w * 2 / 3;
mPoints[5] = 0;
mPoints[6] = w * 2 / 3;
mPoints[7] = h;
mPoints[8] = w / 3;
mPoints[9] = h / 2;
mPoints[10] = w * 2 / 3;
mPoints[11] = h / 2;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(mBgColor);
if (0 == mStep) {
canvas.drawLines(mPoints, mPaint);
}
}
@Override
public void onClick(View v) {
switch (mStep) {
case 0:
mStep++;
bind();
break;
default:
case 1:
Settings.putGuideGallery(false);
ViewUtils.removeFromParent(this);
break;
}
}
}
@@ -1,210 +0,0 @@
/*
* Copyright 2019 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.view.DisplayCutoutCompat;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ObjectUtils;
public class GalleryHeader extends ViewGroup {
private final Rect batteryRect = new Rect();
private final Rect progressRect = new Rect();
private final Rect clockRect = new Rect();
private final int[] location = new int[2];
private DisplayCutoutCompat displayCutout;
private int topInsets = 0;
private View battery;
private View progress;
private View clock;
private int lastX = 0;
private int lastY = 0;
public GalleryHeader(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setDisplayCutout(@Nullable DisplayCutoutCompat displayCutout) {
if (!ObjectUtils.equal(this.displayCutout, displayCutout)) {
this.displayCutout = displayCutout;
requestLayout();
}
}
public void setTopInsets(int topInsets) {
if (this.topInsets != topInsets) {
this.topInsets = topInsets;
requestLayout();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
battery = findViewById(R.id.battery);
progress = findViewById(R.id.progress);
clock = findViewById(R.id.clock);
}
private void measureChild(Rect rect, View view, int width, int paddingLeft, int paddingRight) {
int left;
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
if (view == battery) {
left = paddingLeft + lp.leftMargin;
} else if (view == progress) {
left = paddingLeft + (width - paddingLeft - paddingRight) / 2 - view.getMeasuredWidth() / 2;
} else {
left = width - paddingRight - lp.rightMargin - view.getMeasuredWidth();
}
rect.set(left, lp.topMargin + topInsets, left + view.getMeasuredWidth(), lp.topMargin + topInsets + view.getMeasuredHeight());
}
@RequiresApi(api = Build.VERSION_CODES.P)
private boolean offsetVertically(Rect rect, View view, int width) {
int offset = 0;
measureChild(rect, view, width, 0, 0);
rect.offset(lastX, lastY);
for (Rect notch : displayCutout.getBoundingRects()) {
if (Rect.intersects(notch, rect)) {
offset = Math.max(offset, notch.bottom - lastY);
}
}
if (offset != 0) {
rect.offset(-lastX, -lastY);
rect.offset(0, offset);
return true;
} else {
return false;
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
private int getOffsetLeft(Rect rect, View view, int width) {
int offset = 0;
measureChild(rect, view, width, 0, 0);
rect.offset(lastX, lastY);
for (Rect notch : displayCutout.getBoundingRects()) {
if (Rect.intersects(notch, rect)) {
offset = Math.max(offset, notch.right - lastX);
}
}
return offset;
}
@RequiresApi(api = Build.VERSION_CODES.P)
private int getOffsetRight(Rect rect, View view, int width) {
int offset = 0;
measureChild(rect, view, width, 0, 0);
rect.offset(lastX, lastY);
for (Rect notch : displayCutout.getBoundingRects()) {
if (Rect.intersects(notch, rect)) {
offset = Math.max(offset, lastX + width - notch.left);
}
}
return offset;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
throw new IllegalStateException();
}
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
height = Math.max(height, child.getMeasuredHeight() + lp.topMargin + topInsets);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && displayCutout != null) {
// Check progress covered
if (offsetVertically(progressRect, progress, width)) {
offsetVertically(batteryRect, battery, width);
offsetVertically(clockRect, clock, width);
height = Math.max(progressRect.bottom, Math.max(batteryRect.bottom, clockRect.bottom));
} else {
// Clamp left and right
int paddingLeft = getOffsetLeft(batteryRect, battery, width);
int paddingRight = getOffsetRight(clockRect, clock, width);
measureChild(batteryRect, battery, width, paddingLeft, paddingRight);
measureChild(progressRect, progress, width, paddingLeft, paddingRight);
measureChild(clockRect, clock, width, paddingLeft, paddingRight);
}
} else {
measureChild(batteryRect, battery, width, 0, 0);
measureChild(progressRect, progress, width, 0, 0);
measureChild(clockRect, clock, width, 0, 0);
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
battery.layout(batteryRect.left, batteryRect.top, batteryRect.right, batteryRect.bottom);
progress.layout(progressRect.left, progressRect.top, progressRect.right, progressRect.bottom);
clock.layout(clockRect.left, clockRect.top, clockRect.right, clockRect.bottom);
getLocationOnScreen(location);
if (lastX != location[0] || lastY != location[1]) {
lastX = location[0];
lastY = location[1];
requestLayout();
}
}
@Override
public MarginLayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams;
}
@Override
protected MarginLayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (lp instanceof MarginLayoutParams) {
return new MarginLayoutParams((MarginLayoutParams) lp);
}
return new MarginLayoutParams(lp);
}
}
@@ -27,16 +27,8 @@ import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.Locale
import kotlin.math.max
import kotlin.math.min
abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
@JvmField
var maxGid = -1
@JvmField
var minGid = -1
@JvmField
var jumpTo: String? = null
private val listener: FavouriteStatusRouter.Listener
@@ -58,26 +50,14 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
EhApplication.favouriteStatusRouter.removeListener(listener)
}
override fun onAddData(data: GalleryInfo?) {
data?.let { map[data.gid] = data }
}
override fun onAddData(data: List<GalleryInfo?>) {
for (info in data) {
info?.let {
if (maxGid == -1) maxGid = info.gid.toInt()
if (minGid == -1) minGid = info.gid.toInt()
maxGid = max(maxGid.toLong(), info.gid).toInt()
minGid = min(minGid.toLong(), info.gid).toInt()
map[info.gid] = info
}
}
}
override fun onRemoveData(data: GalleryInfo?) {
data?.let { map.remove(data.gid) }
}
override fun onRemoveData(data: List<GalleryInfo?>) {
for (info in data) {
info?.let { map.remove(info.gid) }
@@ -86,14 +66,6 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
override fun onClearData() {
map.clear()
maxGid = -1
minGid = -1
}
override fun beforeRefresh() {
super.beforeRefresh()
maxGid = -1
minGid = -1
}
override fun saveInstanceState(superState: Parcelable): Parcelable {
@@ -103,8 +75,6 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
val router = EhApplication.favouriteStatusRouter
val id = router.saveDataMap(map)
bundle.putInt(KEY_DATA_MAP, id)
bundle.putInt(KEY_MIN_GID, minGid)
bundle.putInt(KEY_MAX_GID, maxGid)
return bundle
}
@@ -118,28 +88,22 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
this.map = map
}
}
minGid = bundle.getInt(KEY_MIN_GID)
maxGid = bundle.getInt(KEY_MAX_GID)
return super.restoreInstanceState(state)
}
fun goTo(time: Long) {
fun goTo(time: Long, isNext: Boolean) {
val formatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd", Locale.US).withZone(ZoneOffset.UTC)
jumpTo = formatter.format(Instant.ofEpochMilli(time))
doGoToPage(Int.MAX_VALUE / 2)
}
fun goToPage(page: Int) {
jumpTo = page.toString()
doGoToPage(page)
if (isNext) {
goTo(mNext ?: "2", true)
} else {
goTo(mPrev, false)
}
jumpTo = null
}
companion object {
private const val KEY_DATA_MAP = "data_map"
private const val KEY_MAX_GID = "max_gid"
private const val KEY_MIN_GID = "min_gid"
const val KEY_UPPER_PAGE = "upper_page"
const val KEY_LOWER_PAGE = "lower_page"
}
}
@@ -1,78 +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.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.appcompat.widget.AppCompatSeekBar;
public class ReversibleSeekBar extends AppCompatSeekBar {
private boolean mReverse;
public ReversibleSeekBar(Context context) {
super(context);
}
public ReversibleSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReversibleSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setReverse(boolean reverse) {
mReverse = reverse;
invalidate();
}
@Override
public void draw(Canvas canvas) {
boolean reverse = mReverse;
int saveCount = 0;
if (reverse) {
saveCount = canvas.save();
float px = this.getWidth() / 2.0f;
float py = this.getHeight() / 2.0f;
canvas.scale(-1, 1, px, py);
}
super.draw(canvas);
if (reverse) {
canvas.restoreToCount(saveCount);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean reverse = mReverse;
float x = 0.0f, y = 0.0f;
if (reverse) {
x = event.getX();
y = event.getY();
event.setLocation(getWidth() - x, y);
}
boolean result = super.onTouchEvent(event);
if (reverse) {
event.setLocation(x, y);
}
return result;
}
}
@@ -28,7 +28,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.annotation.IntDef
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DefaultItemAnimator
@@ -97,7 +96,7 @@ class SearchLayout @JvmOverloads constructor(
val mCategoryStored = mSharePref.getInt(SEARCH_CATEGORY_PREF, EhConfig.ALL_CATEGORY)
mCategoryGroup = mNormalView.findViewById(R.id.search_category_chipgroup)
for (mPair in mCategoryTable) {
val mChip = IdentifiedChip(context)
val mChip = inflate(context, R.layout.filter_chip, null) as IdentifiedChip
mChip.isCheckable = true
mChip.setText(mPair.second)
mChip.idt = mPair.first
@@ -208,8 +207,15 @@ class SearchLayout @JvmOverloads constructor(
if (mEnableAdvance) {
urlBuilder.advanceSearch = mTableAdvanceSearch.advanceSearch
urlBuilder.minRating = mTableAdvanceSearch.minRating
urlBuilder.pageFrom = mTableAdvanceSearch.pageFrom
urlBuilder.pageTo = mTableAdvanceSearch.pageTo
val pageFrom = mTableAdvanceSearch.pageFrom
val pageTo = mTableAdvanceSearch.pageTo
if (pageTo != -1 && pageTo < 10) {
throw EhException(context.getString(R.string.search_sp_err1))
} else if (pageFrom != -1 && pageTo != -1 && pageTo - pageFrom < 20) {
throw EhException(context.getString(R.string.search_sp_err2))
}
urlBuilder.pageFrom = pageFrom
urlBuilder.pageTo = pageTo
}
}
@@ -328,17 +334,6 @@ class SearchLayout @JvmOverloads constructor(
}
}
inner class IdentifiedChip @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : Chip(
ContextThemeWrapper(
context,
com.google.android.material.R.style.Widget_Material3_Chip_Filter
), attrs
) {
var idt = 0
}
companion object {
const val SEARCH_MODE_NORMAL = 0
const val SEARCH_MODE_IMAGE = 1
@@ -368,4 +363,12 @@ class SearchLayout @JvmOverloads constructor(
)
)
}
}
class IdentifiedChip @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : Chip(
context, attrs
) {
var idt = 0
}
@@ -1,87 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ViewUtils;
public class SeekBarPanel extends LinearLayout {
private final int[] mLocation = new int[2];
private SeekBar mSeekBar;
public SeekBarPanel(Context context) {
super(context);
init();
}
public SeekBarPanel(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public SeekBarPanel(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public SeekBarPanel(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
post(() -> {
var rootWindowInsets = getRootWindowInsets();
if (rootWindowInsets != null) {
setPadding(0, 0, 0, rootWindowInsets.getSystemWindowInsetBottom());
}
});
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSeekBar = (SeekBar) ViewUtils.$$(this, R.id.seek_bar);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (mSeekBar == null) {
return super.onTouchEvent(event);
} else {
ViewUtils.getLocationInAncestor(mSeekBar, mLocation, this);
final float offsetX = -mLocation[0];
final float offsetY = -mLocation[1];
event.offsetLocation(offsetX, offsetY);
mSeekBar.onTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
return true;
}
}
}
@@ -1,70 +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.glgallery;
import android.view.MotionEvent;
class DownUpDetector {
private final DownUpListener mListener;
private boolean mStillDown;
public DownUpDetector(DownUpListener listener) {
mListener = listener;
}
private void setState(boolean down, MotionEvent e) {
if (down == mStillDown) return;
mStillDown = down;
if (down) {
mListener.onDown(e);
} else {
mListener.onUp(e);
}
}
public void onTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
setState(true, ev);
break;
case MotionEvent.ACTION_POINTER_DOWN:
mListener.onPointerDown(ev);
break;
case MotionEvent.ACTION_UP:
mListener.onPointerUp(ev);
setState(false, ev);
break;
case MotionEvent.ACTION_CANCEL:
setState(false, ev);
break;
}
}
public boolean isDown() {
return mStillDown;
}
public interface DownUpListener {
void onDown(MotionEvent e);
void onUp(MotionEvent e);
void onPointerDown(MotionEvent e);
void onPointerUp(MotionEvent e);
}
}
@@ -1,137 +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.glgallery;
import android.content.Context;
import android.hardware.SensorManager;
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import com.hippo.glview.anim.Animation;
abstract class Fling extends Animation {
private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
private static final float START_TENSION = 0.5f;
private static final float END_TENSION = 1.0f;
private static final float P1 = START_TENSION * INFLEXION;
private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
private static final float FLING_FRICTION = ViewConfiguration.getScrollFriction();
private static final int NB_SAMPLES = 100;
private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
private static final Interpolator FLING_INTERPOLATOR = new Interpolator() {
@Override
public float getInterpolation(float input) {
final int index = (int) (NB_SAMPLES * input);
float distanceCoef = 1.f;
float velocityCoef;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (input - t_inf) * velocityCoef;
}
return distanceCoef;
}
};
private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
static {
float x_min = 0.0f;
float y_min = 0.0f;
for (int i = 0; i < NB_SAMPLES; i++) {
final float alpha = (float) i / NB_SAMPLES;
float x_max = 1.0f;
float x, tx, coef;
while (true) {
x = x_min + (x_max - x_min) / 2.0f;
coef = 3.0f * x * (1.0f - x);
tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
if (Math.abs(tx - alpha) < 1E-5) break;
if (tx > alpha) x_max = x;
else x_min = x;
}
SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
float y_max = 1.0f;
float y, dy;
while (true) {
y = y_min + (y_max - y_min) / 2.0f;
coef = 3.0f * y * (1.0f - y);
dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
if (Math.abs(dy - alpha) < 1E-5) break;
if (dy > alpha) y_max = y;
else y_min = y;
}
SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
}
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
}
private final float mPhysicalCoeff;
public Fling(Context context) {
final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* ppi
* 0.84f; // look and feel tuning
setInterpolator(FLING_INTERPOLATOR);
}
private double getSplineDeceleration(int velocity) {
return Math.log(INFLEXION * Math.abs(velocity) / (FLING_FRICTION * mPhysicalCoeff));
}
/* Returns the duration, expressed in milliseconds */
protected int getSplineFlingDuration(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return (int) (1000.0 * Math.exp(l / decelMinusOne));
}
protected double getSplineFlingDistance(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return FLING_FRICTION * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
/**
* Modifies mDuration to the duration it takes to get from start to newFinal using the
* spline interpolation. The previous duration was needed to get to oldFinal.
**/
protected int adjustDuration(int start, int oldFinal, int newFinal, int duration) {
final int oldDistance = oldFinal - start;
final int newDistance = newFinal - start;
final float x = Math.abs((float) newDistance / oldDistance);
final int index = (int) (NB_SAMPLES * x);
if (index < NB_SAMPLES) {
final float x_inf = (float) index / NB_SAMPLES;
final float x_sup = (float) (index + 1) / NB_SAMPLES;
final float t_inf = SPLINE_TIME[index];
final float t_sup = SPLINE_TIME[index + 1];
final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
duration *= timeCoef;
}
return duration;
}
}
@@ -1,197 +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.glgallery;
import com.hippo.glview.glrenderer.BasicTexture;
import com.hippo.glview.glrenderer.Texture;
import com.hippo.glview.image.GLImageMovableTextView;
import com.hippo.glview.image.ImageMovableTextTexture;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.view.Gravity;
import com.hippo.glview.widget.GLFrameLayout;
import com.hippo.glview.widget.GLLinearLayout;
import com.hippo.glview.widget.GLProgressView;
import com.hippo.glview.widget.GLTextureView;
public class GalleryPageView extends GLFrameLayout {
public static final int INVALID_INDEX = -1;
public static final float PROGRESS_GONE = -1.0f;
public static final float PROGRESS_INDETERMINATE = -2.0f;
private final ImageView mImage;
private final GLLinearLayout mInfo;
private final GLImageMovableTextView mPage;
private final GLTextureView mError;
private final GLProgressView mProgress;
private final int mMinHeight;
private int mIndex = INVALID_INDEX;
public GalleryPageView(ImageMovableTextTexture pageTextTexture,
int progressColor, int progressBgColor, int progressSize,
int minHeight, int infoInterval) {
// Add image
mImage = new ImageView();
GravityLayoutParams glp = new GravityLayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
addComponent(mImage, glp);
// Add other panel
mInfo = new GLLinearLayout();
mInfo.setOrientation(GLLinearLayout.VERTICAL);
mInfo.setInterval(infoInterval);
glp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
glp.gravity = Gravity.CENTER;
addComponent(mInfo, glp);
// Add page
mPage = new GLImageMovableTextView();
mPage.setTextTexture(pageTextTexture);
GLLinearLayout.LayoutParams lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mPage, lp);
// Add error
mError = new GLTextureView();
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mError, lp);
// Add progress
mProgress = new GLProgressView();
mProgress.setBgColor(progressBgColor);
mProgress.setColor(progressColor);
mProgress.setMinimumWidth(progressSize);
mProgress.setMinimumHeight(progressSize);
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mProgress, lp);
mMinHeight = minHeight;
}
@Override
protected int getSuggestedMinimumHeight() {
// The height of the actual image may be smaller than mPageMinHeight.
// Set min height as 0 when the image is visible.
// For PageLayoutManager, min height is useless.
if (mImage.getVisibility() == VISIBLE) {
return 0;
} else {
return mMinHeight;
}
}
int getIndex() {
return mIndex;
}
void setIndex(int index) {
mIndex = index;
}
public void showImage() {
mImage.setVisibility(VISIBLE);
mInfo.setVisibility(GONE);
}
public void showInfo() {
// For image valid rect
mImage.setVisibility(INVISIBLE);
mInfo.setVisibility(VISIBLE);
}
private void unbindImage() {
ImageTexture texture = mImage.getImageTexture();
if (texture != null) {
mImage.setImageTexture(null);
texture.recycle();
}
}
public void setImage(ImageTexture imageTexture) {
unbindImage();
if (imageTexture != null) {
mImage.setImageTexture(imageTexture);
}
}
public void setPage(int page) {
mPage.setVisibility(VISIBLE);
mPage.setText(Integer.toString(page));
}
public void hidePage() {
mPage.setVisibility(GONE);
}
public void setProgress(float progress) {
if (progress == PROGRESS_GONE) {
mProgress.setVisibility(GONE);
} else if (progress == PROGRESS_INDETERMINATE) {
mProgress.setVisibility(VISIBLE);
mProgress.setIndeterminate(true);
} else {
mProgress.setVisibility(VISIBLE);
mProgress.setIndeterminate(false);
mProgress.setProgress(progress);
}
}
private void unbindError() {
Texture texture = mError.getTexture();
if (texture != null) {
mError.setTexture(null);
if (texture instanceof BasicTexture) {
((BasicTexture) texture).recycle();
}
}
}
public void setError(String error, GalleryView galleryView) {
unbindError();
if (error == null) {
mError.setVisibility(GONE);
} else {
mError.setVisibility(VISIBLE);
galleryView.bindErrorView(mError, error);
}
}
ImageView getImageView() {
return mImage;
}
boolean isLoaded() {
return mImage.getVisibility() == VISIBLE;
}
boolean isError() {
return mError.getVisibility() == VISIBLE;
}
boolean isUnderInfo(float x, float y) {
return mInfo.bounds().contains((int) x, (int) y);
}
}
@@ -1,264 +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.glgallery;
import android.util.LruCache;
import androidx.annotation.IntDef;
import androidx.annotation.UiThread;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.image.ImageWrapper;
import com.hippo.glview.view.GLRoot;
import com.hippo.image.Image;
import com.hippo.yorozuya.ConcurrentPool;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.OSUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public abstract class GalleryProvider {
public static final int STATE_WAIT = -1;
public static final int STATE_ERROR = -2;
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
private final ImageCache mImageCache = new ImageCache();
private volatile Listener mListener;
private volatile GLRoot mGLRoot;
private boolean mStarted = false;
@UiThread
public void start() {
OSUtils.checkMainLoop();
if (mStarted) {
throw new IllegalStateException("Can't start it twice");
}
mStarted = true;
}
@UiThread
public void stop() {
OSUtils.checkMainLoop();
mImageCache.evictAll();
}
public void setGLRoot(GLRoot glRoot) {
mGLRoot = glRoot;
}
/**
* @return {@link #STATE_WAIT} for wait,
* {@link #STATE_ERROR} for error, {@link #getError()} to get error message,
* 0 for empty
*/
public abstract int size();
public final void request(int index) {
ImageWrapper imageWrapper = mImageCache.get(index);
if (imageWrapper != null) {
notifyPageSucceed(index, imageWrapper);
} else {
onRequest(index);
}
}
public final void forceRequest(int index) {
onForceRequest(index);
}
public void removeCache(int index) {
mImageCache.remove(index);
}
protected abstract void onRequest(int index);
protected abstract void onForceRequest(int index);
public final void cancelRequest(int index) {
onCancelRequest(index);
}
protected abstract void onCancelRequest(int index);
public abstract String getError();
public void setListener(Listener listener) {
mListener = listener;
}
public void notifyDataChanged() {
notify(NotifyTask.TYPE_DATA_CHANGED, -1, 0.0f, null, null);
}
public void notifyDataChanged(int index) {
notify(NotifyTask.TYPE_DATA_CHANGED, index, 0.0f, null, null);
}
public void notifyPageWait(int index) {
notify(NotifyTask.TYPE_WAIT, index, 0.0f, null, null);
}
public void notifyPagePercent(int index, float percent) {
notify(NotifyTask.TYPE_PERCENT, index, percent, null, null);
}
public void notifyPageSucceed(int index, Image image) {
ImageWrapper imageWrapper = new ImageWrapper(image);
mImageCache.add(index, imageWrapper);
notifyPageSucceed(index, imageWrapper);
}
public void notifyPageSucceed(int index, ImageWrapper image) {
notify(NotifyTask.TYPE_SUCCEED, index, 0.0f, image, null);
}
public void notifyPageFailed(int index, String error) {
notify(NotifyTask.TYPE_FAILED, index, 0.0f, null, error);
}
private void notify(@NotifyTask.Type int type, int index, float percent, ImageWrapper image, String error) {
Listener listener = mListener;
if (listener == null) {
return;
}
GLRoot glRoot = mGLRoot;
if (glRoot == null) {
return;
}
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask(listener, mNotifyTaskPool);
}
task.setData(type, index, percent, image, error);
glRoot.addOnGLIdleListener(task);
}
public interface Listener {
void onDataChanged();
void onPageWait(int index);
void onPagePercent(int index, float percent);
void onPageSucceed(int index, ImageWrapper image);
void onPageFailed(int index, String error);
void onDataChanged(int index);
}
private static class NotifyTask implements GLRoot.OnGLIdleListener {
public static final int TYPE_DATA_CHANGED = 0;
public static final int TYPE_WAIT = 1;
public static final int TYPE_PERCENT = 2;
public static final int TYPE_SUCCEED = 3;
public static final int TYPE_FAILED = 4;
private final Listener mListener;
private final ConcurrentPool<NotifyTask> mPool;
@Type
private int mType;
private int mIndex;
private float mPercent;
private ImageWrapper mImage;
private String mError;
public NotifyTask(Listener listener, ConcurrentPool<NotifyTask> pool) {
mListener = listener;
mPool = pool;
}
public void setData(@Type int type, int index, float percent, ImageWrapper image, String error) {
mType = type;
mIndex = index;
mPercent = percent;
mImage = image;
mError = error;
}
@Override
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
switch (mType) {
case TYPE_DATA_CHANGED:
if (mIndex < 0) {
mListener.onDataChanged();
} else {
mListener.onDataChanged(mIndex);
}
break;
case TYPE_WAIT:
mListener.onPageWait(mIndex);
break;
case TYPE_PERCENT:
mListener.onPagePercent(mIndex, mPercent);
break;
case TYPE_SUCCEED:
mListener.onPageSucceed(mIndex, mImage);
break;
case TYPE_FAILED:
mListener.onPageFailed(mIndex, mError);
break;
}
// Clean data
mImage = null;
mError = null;
// Push back
mPool.push(this);
return false;
}
@IntDef({TYPE_DATA_CHANGED, NotifyTask.TYPE_WAIT, TYPE_PERCENT, TYPE_SUCCEED, TYPE_FAILED})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {
}
}
private static class ImageCache extends LruCache<Integer, ImageWrapper> {
private static final int CACHE_SIZE = 512 * 1024 * 1024;
public ImageCache() {
super(CACHE_SIZE);
}
public void add(Integer key, ImageWrapper value) {
if (!value.getAnimated())
if (value.obtain())
put(key, value);
}
@Override
protected int sizeOf(Integer key, ImageWrapper value) {
return value.getWidth() * value.getHeight() * 4;
}
@Override
protected void entryRemoved(boolean evicted, Integer key, ImageWrapper oldValue, ImageWrapper newValue) {
if (oldValue != null) {
oldValue.release();
}
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,186 +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.glgallery;
import android.content.Context;
import android.os.SystemClock;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import androidx.core.view.GestureDetectorCompat;
// This class aggregates three gesture detectors: GestureDetector,
// ScaleGestureDetector, and DownUpDetector.
class GestureRecognizer {
@SuppressWarnings("unused")
private static final String TAG = "GestureRecognizer";
private final GestureDetectorCompat mGestureDetector;
private final ScaleGestureDetector mScaleDetector;
private final DownUpDetector mDownUpDetector;
private final Listener mListener;
public GestureRecognizer(Context context, Listener listener) {
mListener = listener;
MyGestureListener gestureListener = new MyGestureListener();
mGestureDetector = new GestureDetectorCompat(context, gestureListener,
null /* ignoreMultitouch */);
mGestureDetector.setOnDoubleTapListener(gestureListener);
mScaleDetector = new ScaleGestureDetector(
context, new MyScaleListener());
mDownUpDetector = new DownUpDetector(new MyDownUpListener());
}
public void onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
mDownUpDetector.onTouchEvent(event);
}
public boolean isDown() {
return mDownUpDetector.isDown();
}
public void cancelScale() {
long now = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mScaleDetector.onTouchEvent(cancelEvent);
cancelEvent.recycle();
}
public interface Listener {
boolean onSingleTapUp(float x, float y);
boolean onSingleTapConfirmed(float x, float y);
boolean onDoubleTap(float x, float y);
boolean onDoubleTapConfirmed(float x, float y);
void onLongPress(float x, float y);
boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y);
/**
* @param velocityX Finger from top to bottom is positive
* @param velocityY Finger from left to right is positive
*/
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
boolean onScaleBegin(float focusX, float focusY);
boolean onScale(float focusX, float focusY, float scale);
void onScaleEnd();
void onDown(float x, float y);
void onUp();
void onPointerDown(float x, float y);
void onPointerUp();
}
private class MyGestureListener
extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return mListener.onSingleTapUp(e.getX(), e.getY());
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return mListener.onSingleTapConfirmed(e.getX(), e.getY());
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
return mListener.onDoubleTapConfirmed(e.getX(), e.getY());
} else {
return true;
}
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return mListener.onDoubleTap(e.getX(), e.getY());
}
@Override
public void onLongPress(MotionEvent e) {
mListener.onLongPress(e.getX(), e.getY());
}
@Override
public boolean onScroll(
MotionEvent e1, MotionEvent e2, float dx, float dy) {
return mListener.onScroll(
dx, dy, e2.getX() - e1.getX(), e2.getY() - e1.getY(), e2.getX(), e2.getY());
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return mListener.onFling(e1, e2, velocityX, velocityY);
}
}
private class MyScaleListener
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return mListener.onScaleBegin(
detector.getFocusX(), detector.getFocusY());
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
return mListener.onScale(detector.getFocusX(),
detector.getFocusY(), detector.getScaleFactor());
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
mListener.onScaleEnd();
}
}
private class MyDownUpListener implements DownUpDetector.DownUpListener {
@Override
public void onDown(MotionEvent e) {
mListener.onDown(e.getX(), e.getY());
}
@Override
public void onUp(MotionEvent e) {
mListener.onUp();
}
@Override
public void onPointerDown(MotionEvent e) {
mListener.onPointerDown(e.getX(), e.getY());
}
@Override
public void onPointerUp(MotionEvent e) {
mListener.onPointerUp();
}
}
}
@@ -1,597 +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.glgallery;
import android.graphics.Rect;
import android.graphics.RectF;
import com.hippo.glview.anim.AlphaAnimation;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.glrenderer.Texture;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.view.GLView;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.MathUtils;
import java.util.Arrays;
class ImageView extends GLView implements ImageTexture.Callback {
public static final int SCALE_ORIGIN = 0;
public static final int SCALE_FIT_WIDTH = 1;
public static final int SCALE_FIT_HEIGHT = 2;
public static final int SCALE_FIT = 3;
public static final int SCALE_FIXED = 4;
public static final int START_POSITION_TOP_LEFT = 0;
public static final int START_POSITION_TOP_RIGHT = 1;
public static final int START_POSITION_BOTTOM_LEFT = 2;
public static final int START_POSITION_BOTTOM_RIGHT = 3;
public static final int START_POSITION_CENTER = 4;
// TODO adjust scale max and min according to image size and screen size
private static final float SCALE_MIN = 1 / 10.0f;
private static final float SCALE_MAX = 10.0f;
private static final long ALPHA_ANIMATION_DURING = 300L;
private final RectF mDst = new RectF();
private final RectF mSrcActual = new RectF();
private final RectF mDstActual = new RectF();
private final Rect mValidRect = new Rect();
private final AlphaAnimation mAlphaAnimation;
private ImageTexture mImageTexture;
private int mTextureWidth;
private int mTextureHeight;
private int mScaleMode = SCALE_FIT;
private int mStartPosition = START_POSITION_TOP_RIGHT;
private float mScaleValue = 1.0f;
private float mScale = 1.0f;
private boolean mScaleOffsetDirty = true;
private boolean mPositionInRootDirty = true;
public ImageView() {
mAlphaAnimation = new AlphaAnimation(0.0f, 1.0f);
mAlphaAnimation.setDuration(ALPHA_ANIMATION_DURING);
mAlphaAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
}
public static int sanitizeScaleMode(int scaleMode) {
if (scaleMode != SCALE_ORIGIN &&
scaleMode != SCALE_FIT_WIDTH &&
scaleMode != SCALE_FIT_HEIGHT &&
scaleMode != SCALE_FIT &&
scaleMode != SCALE_FIXED) {
return SCALE_FIT;
} else {
return scaleMode;
}
}
public static int sanitizeStartPosition(int startPosition) {
if (startPosition != START_POSITION_TOP_LEFT &&
startPosition != START_POSITION_TOP_RIGHT &&
startPosition != START_POSITION_BOTTOM_LEFT &&
startPosition != START_POSITION_BOTTOM_RIGHT &&
startPosition != START_POSITION_CENTER) {
return START_POSITION_TOP_RIGHT;
} else {
return startPosition;
}
}
@Override
protected int getSuggestedMinimumWidth() {
return Math.max(super.getSuggestedMinimumWidth(),
mImageTexture == null ? 0 : mTextureWidth);
}
@Override
protected int getSuggestedMinimumHeight() {
return Math.max(super.getSuggestedMinimumHeight(),
mImageTexture == null ? 0 : mTextureHeight);
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mImageTexture == null) {
super.onMeasure(widthSpec, heightSpec);
} else {
float ratio = (float) mTextureWidth / mTextureHeight;
int widthSize = MeasureSpec.getSize(widthSpec);
int heightSize = MeasureSpec.getSize(heightSpec);
int widthMode = MeasureSpec.getMode(widthSpec);
int heightMode = MeasureSpec.getMode(heightSpec);
int measureWidth = -1;
int measureHeight = -1;
if (widthMode == MeasureSpec.EXACTLY) {
measureWidth = widthSize;
if (heightMode == MeasureSpec.EXACTLY) {
measureHeight = heightSize;
} else {
measureHeight = (int) (widthSize / ratio);
if (heightMode == MeasureSpec.AT_MOST) {
measureHeight = Math.min(measureHeight, heightSize);
}
}
} else if (heightMode == MeasureSpec.EXACTLY) {
measureHeight = heightSize;
measureWidth = (int) (heightSize * ratio);
if (widthMode == MeasureSpec.AT_MOST) {
measureWidth = Math.min(measureWidth, widthSize);
}
}
if (measureWidth == -1 || measureHeight == -1) {
super.onMeasure(widthSpec, heightSpec);
} else {
setMeasuredSize(measureWidth, measureHeight);
}
}
}
@Override
protected void onSizeChanged(int newW, int newH, int oldW, int oldH) {
mScaleOffsetDirty = true;
mPositionInRootDirty = true;
}
@Override
protected void onPositionInRootChanged(int x, int y, int oldX, int oldY) {
mPositionInRootDirty = true;
if (mImageTexture != null) {
getValidRect(mValidRect);
if (!mValidRect.isEmpty()) {
mImageTexture.start();
} else {
mImageTexture.stop();
}
}
}
public void getScaleDefault(float[] scaleDefault) {
if (mImageTexture == null) {
return;
}
scaleDefault[0] = 1.0f;
scaleDefault[1] = (float) getWidth() / mTextureWidth;
scaleDefault[2] = (float) getHeight() / mTextureHeight;
scaleDefault[3] = Math.max(scaleDefault[1], scaleDefault[2]) * 2;
scaleDefault[0] = MathUtils.clamp(scaleDefault[0], SCALE_MIN, SCALE_MAX);
scaleDefault[1] = MathUtils.clamp(scaleDefault[1], SCALE_MIN, SCALE_MAX);
scaleDefault[2] = MathUtils.clamp(scaleDefault[2], SCALE_MIN, SCALE_MAX);
scaleDefault[3] = MathUtils.clamp(scaleDefault[3], SCALE_MIN, SCALE_MAX);
Arrays.sort(scaleDefault);
}
public ImageTexture getImageTexture() {
return mImageTexture;
}
public void setImageTexture(ImageTexture imageTexture) {
// Remove callback
if (mImageTexture != null) {
mImageTexture.setCallback(null);
mImageTexture.stop();
}
int oldTextureWidth = mTextureWidth;
int oldTextureHeight = mTextureHeight;
mImageTexture = imageTexture;
if (imageTexture != null) {
imageTexture.setCallback(this);
mTextureWidth = imageTexture.getWidth();
mTextureHeight = imageTexture.getHeight();
// Avoid zero and negative
if (mTextureWidth <= 0) {
mTextureWidth = 1;
}
if (mTextureHeight <= 0) {
mTextureHeight = 1;
}
// Start alpha animation, do not show animation for image has no valid rect
getValidRect(mValidRect);
if (!mValidRect.isEmpty()) {
startAnimation(mAlphaAnimation, true);
mImageTexture.start();
}
} else {
mTextureWidth = 1;
mTextureHeight = 1;
}
mScaleOffsetDirty = true;
mPositionInRootDirty = true;
if (oldTextureWidth != mTextureWidth || oldTextureHeight != mTextureHeight) {
requestLayout();
}
}
public boolean isLoaded() {
return mImageTexture != null;
}
public boolean canFlingVertically() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.top < 0.0f || mDst.bottom > getHeight();
}
public boolean canFlingHorizontally() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.left < 0.0f || mDst.right > getWidth();
}
public boolean canFling() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.left < 0.0f || mDst.top < 0.0f || mDst.right > getWidth() || mDst.bottom > getHeight();
}
public int getMaxDx() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.max(0, -(int) mDst.left);
}
public int getMinDx() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.min(0, getWidth() - (int) mDst.right);
}
public int getMaxDy() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.max(0, -(int) mDst.top);
}
public int getMinDy() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.min(0, getHeight() - (int) mDst.bottom);
}
public float getScale() {
return mScale;
}
/**
* If target is shorter then screen, make it in screen center. If target is
* longer then parent, make sure target fill parent over
*/
private void adjustPosition() {
RectF dst = mDst;
int screenWidth = getWidth();
int screenHeight = getHeight();
float targetWidth = dst.width();
float targetHeight = dst.height();
if (targetWidth > screenWidth) {
float fixXOffset = dst.left;
if (fixXOffset > 0) {
dst.left -= fixXOffset;
dst.right -= fixXOffset;
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
dst.left += fixXOffset;
dst.right += fixXOffset;
}
} else {
float left = (screenWidth - targetWidth) / 2;
dst.offsetTo(left, dst.top);
}
if (targetHeight > screenHeight) {
float fixYOffset = dst.top;
if (fixYOffset > 0) {
dst.top -= fixYOffset;
dst.bottom -= fixYOffset;
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
dst.top += fixYOffset;
dst.bottom += fixYOffset;
}
} else {
float top = (screenHeight - targetHeight) / 2;
dst.offsetTo(dst.left, top);
}
}
public void setScaleOffset(int scaleMode, int startPosition, float scaleValue) {
mScaleMode = scaleMode;
mStartPosition = startPosition;
mScaleValue = scaleValue;
int screenWidth = getWidth();
int screenHeight = getHeight();
if (mImageTexture == null || screenWidth == 0 || screenHeight == 0) {
mScaleOffsetDirty = true;
return;
}
int textureWidth = mTextureWidth;
int textureHeight = mTextureHeight;
// Set scale
float targetWidth;
float targetHeight;
switch (scaleMode) {
case SCALE_ORIGIN:
mScale = 1.0f;
targetWidth = textureWidth;
targetHeight = textureHeight;
break;
case SCALE_FIT_WIDTH:
mScale = (float) screenWidth / textureWidth;
targetWidth = screenWidth;
targetHeight = textureHeight * mScale;
break;
case SCALE_FIT_HEIGHT:
mScale = (float) screenHeight / textureHeight;
targetWidth = textureWidth * mScale;
targetHeight = screenHeight;
break;
case SCALE_FIT:
float scaleX = (float) screenWidth / textureWidth;
float scaleY = (float) screenHeight / textureHeight;
if (scaleX < scaleY) {
mScale = scaleX;
targetWidth = screenWidth;
targetHeight = textureHeight * scaleX;
} else {
mScale = scaleY;
targetWidth = textureWidth * scaleY;
targetHeight = screenHeight;
break;
}
break;
case SCALE_FIXED:
default:
mScale = scaleValue;
targetWidth = textureWidth * scaleValue;
targetHeight = textureHeight * scaleValue;
break;
}
// adjust scale, not too big, not too small
if (mScale < SCALE_MIN) {
mScale = SCALE_MIN;
targetWidth = textureWidth * SCALE_MIN;
targetHeight = textureHeight * SCALE_MIN;
} else if (mScale > SCALE_MAX) {
mScale = SCALE_MAX;
targetWidth = textureWidth * SCALE_MAX;
targetHeight = textureHeight * SCALE_MAX;
}
// Set mDst.left and mDst.right
RectF dst = mDst;
switch (startPosition) {
case START_POSITION_TOP_LEFT:
dst.left = 0;
dst.top = 0;
break;
case START_POSITION_TOP_RIGHT:
dst.left = screenWidth - targetWidth;
dst.top = 0;
break;
case START_POSITION_BOTTOM_LEFT:
dst.left = 0;
dst.top = screenHeight - targetHeight;
break;
case START_POSITION_BOTTOM_RIGHT:
dst.left = screenWidth - targetWidth;
dst.top = screenHeight - targetHeight;
break;
case START_POSITION_CENTER:
default:
dst.left = (screenWidth - targetWidth) / 2;
dst.top = (screenHeight - targetHeight) / 2;
break;
}
// Set mDst.right and mDst.bottom
dst.right = dst.left + targetWidth;
dst.bottom = dst.top + targetHeight;
// adjust position
adjustPosition();
mScaleOffsetDirty = false;
mPositionInRootDirty = true;
}
public void scroll(int dx, int dy, int[] remain) {
// Only work after layout
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mScaleOffsetDirty) {
remain[0] = dx;
remain[1] = dy;
return;
}
RectF dst = mDst;
int screenWidth = getWidth();
int screenHeight = getHeight();
float targetWidth = dst.width();
float targetHeight = dst.height();
if (targetWidth > screenWidth) {
dst.left -= dx;
dst.right -= dx;
float fixXOffset = dst.left;
if (fixXOffset > 0) {
dst.left -= fixXOffset;
dst.right -= fixXOffset;
remain[0] = -(int) fixXOffset;
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
dst.left += fixXOffset;
dst.right += fixXOffset;
remain[0] = (int) fixXOffset;
} else {
remain[0] = 0;
}
} else {
remain[0] = dx;
}
if (targetHeight > screenHeight) {
dst.top -= dy;
dst.bottom -= dy;
float fixYOffset = dst.top;
if (fixYOffset > 0) {
dst.top -= fixYOffset;
dst.bottom -= fixYOffset;
remain[1] = -(int) fixYOffset;
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
dst.top += fixYOffset;
dst.bottom += fixYOffset;
remain[1] = (int) fixYOffset;
} else {
remain[1] = 0;
}
} else {
remain[1] = dy;
}
if (dx != remain[0] || dy != remain[1]) {
mPositionInRootDirty = true;
invalidate();
}
}
public void scale(float focusX, float focusY, float scale) {
// Only work after layout
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mScaleOffsetDirty) {
return;
}
if ((mScale == SCALE_MAX && scale >= 1.0f) || (mScale == SCALE_MIN && scale < 1.0f)) {
return;
}
float newScale = mScale * scale;
newScale = MathUtils.clamp(newScale, SCALE_MIN, SCALE_MAX);
mScale = newScale;
RectF dst = mDst;
float left = (focusX - ((focusX - dst.left) * scale));
float top = (focusY - ((focusY - dst.top) * scale));
dst.set(left, top,
(left + (mImageTexture.getWidth() * newScale)),
(top + (mImageTexture.getHeight() * newScale)));
// adjust position
adjustPosition();
mPositionInRootDirty = true;
invalidate();
}
private void applyPositionInRoot() {
int width = mImageTexture.getWidth();
int height = mImageTexture.getHeight();
RectF dst = mDst;
RectF dstActual = mDstActual;
RectF srcActual = mSrcActual;
dstActual.set(dst);
getValidRect(mValidRect);
if (dstActual.intersect(mValidRect.left, mValidRect.top, mValidRect.right, mValidRect.bottom)) {
srcActual.left = MathUtils.lerp(0, width,
MathUtils.delerp(dst.left, dst.right, dstActual.left));
srcActual.right = MathUtils.lerp(0, width,
MathUtils.delerp(dst.left, dst.right, dstActual.right));
srcActual.top = MathUtils.lerp(0, height,
MathUtils.delerp(dst.top, dst.bottom, dstActual.top));
srcActual.bottom = MathUtils.lerp(0, height,
MathUtils.delerp(dst.top, dst.bottom, dstActual.bottom));
} else {
// Can't be seen, set src and dst empty
srcActual.setEmpty();
dstActual.setEmpty();
}
mPositionInRootDirty = false;
}
@Override
public void onRender(GLCanvas canvas) {
Texture texture = mImageTexture;
if (texture == null) {
return;
}
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mPositionInRootDirty) {
applyPositionInRoot();
}
if (!mSrcActual.isEmpty()) {
texture.draw(canvas, mSrcActual, mDstActual);
}
}
@Override
public void invalidateImageTexture(ImageTexture who) {
invalidate();
}
}
@@ -1,963 +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.glgallery;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
import android.view.animation.Interpolator;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import com.hippo.glview.anim.Animation;
import com.hippo.glview.view.GLView;
import com.hippo.glview.widget.GLProgressView;
import com.hippo.glview.widget.GLTextureView;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.MathUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
class PagerLayoutManager extends GalleryView.LayoutManager {
public static final int MODE_LEFT_TO_RIGHT = 0;
public static final int MODE_RIGHT_TO_LEFT = 1;
private static final String TAG = PagerLayoutManager.class.getSimpleName();
private static final Interpolator SMOOTH_SCROLLER_INTERPOLATOR = new Interpolator() {
@Override
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
private final int[] mScrollRemain = new int[2];
private final float[] mScaleDefault = new float[4];
private final SmoothScroller mSmoothScroller;
private final PageFling mPageFling;
private final SmoothScaler mSmoothScaler;
private final Rect mTempRect = new Rect();
private GalleryView.Adapter mAdapter;
private GLProgressView mProgress;
private String mErrorStr;
private GLTextureView mErrorView;
private GalleryPageView mPrevious;
private GalleryPageView mCurrent;
private GalleryPageView mNext;
@Mode
private int mMode = MODE_RIGHT_TO_LEFT;
private int mScaleMode;
private int mStartPosition;
private float mScaleValue;
private int mOffset;
private int mDeltaX;
private int mDeltaY;
private boolean mCanScrollBetweenPages = false;
private boolean mStopAnimationFinger;
private int mInterval;
// Current index
private int mIndex;
public PagerLayoutManager(Context context, @NonNull GalleryView galleryView,
int scaleMode, int startPoint, float scaleValue, int interval) {
super(galleryView);
mScaleMode = scaleMode;
mStartPosition = startPoint;
mScaleValue = scaleValue;
mInterval = interval;
mSmoothScroller = new SmoothScroller();
mPageFling = new PageFling(context);
mSmoothScaler = new SmoothScaler();
}
public void setInterval(int interval) {
if (mInterval == interval) {
return;
}
if (mAdapter != null) {
int index = getInternalCurrentIndex();
GalleryView.Adapter adapter = onDetach();
mInterval = interval;
onAttach(adapter);
setCurrentIndex(index);
mGalleryView.requestFill();
} else {
mInterval = interval;
}
}
private void resetParameters() {
mOffset = 0;
mDeltaX = 0;
mDeltaY = 0;
mCanScrollBetweenPages = false;
mStopAnimationFinger = false;
}
private boolean cancelAllAnimations() {
boolean running = mSmoothScroller.isRunning() ||
mPageFling.isRunning() ||
mSmoothScaler.isRunning();
mSmoothScroller.cancel();
mPageFling.cancel();
mSmoothScaler.cancel();
return running;
}
public void setMode(@Mode int mode) {
if (mMode == mode) {
return;
}
mMode = mode;
if (mAdapter != null) {
// It is attached, refill
// Cancel all animations
cancelAllAnimations();
// Remove all view
removeProgress();
removeErrorView();
removeAllPages();
// Reset parameters
resetParameters();
// Request fill
mGalleryView.requestFill();
}
}
public void setScaleMode(int scaleMode) {
if (mScaleMode == scaleMode) {
return;
}
mScaleMode = scaleMode;
if (mCurrent != null) {
mCurrent.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mPrevious != null) {
mPrevious.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mNext != null) {
mNext.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
}
public void setStartPosition(int startPosition) {
if (mStartPosition == startPosition) {
return;
}
mStartPosition = startPosition;
if (mCurrent != null) {
mCurrent.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mPrevious != null) {
mPrevious.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mNext != null) {
mNext.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
}
@Override
public void onAttach(GalleryView.Adapter adapter) {
AssertUtils.assertNull("The PagerLayoutManager is attached", mAdapter);
AssertUtils.assertNotNull("The adapter is null", adapter);
mAdapter = adapter;
// Reset parameters
resetParameters();
}
private void removeProgress() {
if (mProgress != null) {
mGalleryView.removeComponent(mProgress);
mGalleryView.releaseProgress(mProgress);
mProgress = null;
}
}
private void removeErrorView() {
if (mErrorView != null) {
mGalleryView.removeComponent(mErrorView);
mGalleryView.releaseErrorView(mErrorView);
mErrorView = null;
mErrorStr = null;
}
}
private void removePage(@NonNull GalleryPageView page) {
mGalleryView.removeComponent(page);
mAdapter.unbind(page);
mGalleryView.releasePage(page);
}
private void removeAllPages() {
// Remove gallery view
if (mPrevious != null) {
removePage(mPrevious);
mPrevious = null;
}
if (mCurrent != null) {
removePage(mCurrent);
mCurrent = null;
}
if (mNext != null) {
removePage(mNext);
mNext = null;
}
}
@Override
public GalleryView.Adapter onDetach() {
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
// Cancel all animations
cancelAllAnimations();
// Remove all view
removeProgress();
removeErrorView();
removeAllPages();
// Clear iterator
GalleryView.Adapter adapter = mAdapter;
mAdapter = null;
return adapter;
}
private GalleryPageView getLeftPage() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
return mPrevious;
default:
case MODE_RIGHT_TO_LEFT:
return mNext;
}
}
private GalleryPageView getRightPage() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
return mNext;
default:
case MODE_RIGHT_TO_LEFT:
return mPrevious;
}
}
private GalleryPageView obtainPage() {
GalleryPageView page = mGalleryView.obtainPage();
page.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
return page;
}
private void layoutPage(GalleryPageView page, int widthSpec, int heightSpec,
int left, int top, int right, int bottom) {
Rect rect = mTempRect;
page.getValidRect(rect);
boolean oldValid = !rect.isEmpty();
page.measure(widthSpec, heightSpec);
page.layout(left, top, right, bottom);
page.getValidRect(rect);
boolean newValid = !rect.isEmpty();
if (!oldValid && newValid) {
page.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
}
@Override
public void onFill() {
GalleryView.Adapter adapter = mAdapter;
GalleryView galleryView = mGalleryView;
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", adapter);
int width = galleryView.getWidth();
int height = galleryView.getHeight();
int size = adapter.size();
String errorStr = adapter.getError();
if (size == GalleryProvider.STATE_WAIT) { // Wait here, show progress bar
// Remove error view and all pages
removeErrorView();
removeAllPages();
// Ensure progress
if (mProgress == null) {
mProgress = galleryView.obtainProgress();
galleryView.addComponent(mProgress);
}
// Place progress center
placeCenter(mProgress);
} else if (size <= GalleryProvider.STATE_ERROR || size == 0) { // Get error or empty, show error text
// Ensure error is not null
if (0 == size) {
errorStr = galleryView.getEmptyStr();
} else if (null == errorStr) {
errorStr = galleryView.getDefaultErrorStr();
}
// Remove progress and all pages
removeProgress();
removeAllPages();
// Ensure error view
if (mErrorView == null) {
mErrorView = galleryView.obtainErrorView();
galleryView.addComponent(mErrorView);
}
// Update error string
if (!errorStr.equals(mErrorStr)) {
mErrorStr = errorStr;
galleryView.bindErrorView(mErrorView, errorStr);
}
// Place error view center
placeCenter(mErrorView);
} else {
// Remove progress and error view
removeProgress();
removeErrorView();
// Ensure index in range
int index = mIndex;
if (index < 0) {
index = 0;
mIndex = index;
removeAllPages();
Log.e(TAG, "index < 0, index = " + index);
} else if (index >= size) {
index = size - 1;
mIndex = index;
removeAllPages();
Log.e(TAG, "index >= size, index = " + index + ", size = " + size);
}
// Ensure pages
if (mCurrent == null) {
mCurrent = obtainPage();
galleryView.addComponent(mCurrent);
adapter.bind(mCurrent, index);
}
if (mPrevious == null && index > 0) {
mPrevious = obtainPage();
galleryView.addComponent(mPrevious);
adapter.bind(mPrevious, index - 1);
} else if (mPrevious != null && index == 0) {
removePage(mPrevious);
}
if (mNext == null && index < size - 1) {
mNext = obtainPage();
galleryView.addComponent(mNext);
adapter.bind(mNext, index + 1);
} else if (mNext != null && index == size - 1) {
removePage(mNext);
}
GalleryPageView leftPage = getLeftPage();
GalleryPageView rightPage = getRightPage();
// Fix offset
final int min = rightPage == null ? 0 : -width - mInterval + 1;
final int max = leftPage == null ? 0 : width + mInterval - 1;
mOffset = MathUtils.clamp(mOffset, min, max);
// Measure and layout pages
final int offset = mOffset;
final int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY);
final int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.EXACTLY);
if (mCurrent != null) {
layoutPage(mCurrent, widthSpec, heightSpec,
offset, 0, width + offset, height);
}
if (leftPage != null) {
layoutPage(leftPage, widthSpec, heightSpec,
-mInterval - width + offset, 0, -mInterval + offset, height);
}
if (rightPage != null) {
layoutPage(rightPage, widthSpec, heightSpec,
width + mInterval + offset, 0, width + mInterval + width + offset, height);
}
}
}
@Override
public void onDown() {
mDeltaX = 0;
mDeltaY = 0;
mStopAnimationFinger = cancelAllAnimations();
}
@Override
public void onUp() {
if (mCurrent == null) {
return;
}
// Scroll
if (mOffset != 0) {
int width = mGalleryView.getWidth();
int dx;
if (mOffset >= mInterval && getLeftPage() != null) {
dx = mOffset - width - mInterval;
} else if (mOffset <= -mInterval && getRightPage() != null) {
dx = mOffset + width + mInterval;
} else {
dx = mOffset;
}
final float pageDelta = 7 * (float) Math.abs(mOffset) / (width + mInterval);
int duration = (int) ((pageDelta + 1) * 100);
mSmoothScroller.startSmoothScroll(dx, 0, duration);
}
}
@Override
public void onDoubleTapConfirmed(float x, float y) {
if (mCurrent == null || !mCurrent.getImageView().isLoaded()) {
return;
}
float[] scales = mScaleDefault;
ImageView image = mCurrent.getImageView();
image.getScaleDefault(scales);
float scale = image.getScale();
float endScale = scales[0];
for (float value : scales) {
if (scale < value - 0.01f) {
endScale = value;
break;
}
}
mSmoothScaler.startSmoothScaler(x, y, scale, endScale, 300);
}
@Override
public void onLongPress(float x, float y) {
}
private void pagePrevious() {
if (mIndex <= 0) {
return;
}
mIndex--;
if (mNext != null) {
removePage(mNext);
}
mNext = mCurrent;
mCurrent = mPrevious;
mPrevious = null;
if (mIndex > 0) {
mPrevious = obtainPage();
mGalleryView.addComponent(mPrevious);
mAdapter.bind(mPrevious, mIndex - 1);
}
}
private void pageNext() {
GalleryView.Adapter adapter = mAdapter;
int size = adapter.size();
if (mIndex >= size - 1) {
return;
}
mIndex++;
if (mPrevious != null) {
removePage(mPrevious);
}
mPrevious = mCurrent;
mCurrent = mNext;
mNext = null;
if (mIndex < size - 1) {
mNext = obtainPage();
mGalleryView.addComponent(mNext);
adapter.bind(mNext, mIndex + 1);
}
}
private void pageLeft() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
pagePrevious();
break;
default:
case MODE_RIGHT_TO_LEFT:
pageNext();
break;
}
}
private void pageRight() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
pageNext();
break;
default:
case MODE_RIGHT_TO_LEFT:
pagePrevious();
break;
}
}
private int scrollBetweenPages(int dx) {
GalleryPageView leftPage = getLeftPage();
GalleryPageView rightPage = getRightPage();
int width = mGalleryView.getWidth();
int remain;
if (dx < 0) { // Try to show left
int limit;
if (leftPage == null) {
limit = 0;
} else {
limit = width + mInterval;
}
if (dx > mOffset - limit) {
remain = 0;
mOffset -= dx;
} else {
// Go to left page if left page not null
if (leftPage != null) {
pageLeft();
}
remain = dx + limit - mOffset;
mOffset = 0;
}
} else { // Try to show right
int limit;
if (rightPage == null) {
limit = 0;
} else {
limit = -width - mInterval;
}
if (dx < mOffset - limit) {
remain = 0;
mOffset -= dx;
} else {
// Go to right page if right page not null
if (rightPage != null) {
pageRight();
}
remain = dx + limit - mOffset;
mOffset = 0;
}
}
return remain;
}
public void scrollInternal(float dx, float dy, float x, float y) {
if (mCurrent == null) {
return;
}
boolean needFill = false;
boolean canImageScroll = true;
int remainX = (int) dx;
int remainY = (int) dy;
if (mGalleryView.isFirstScroll()) {
mCanScrollBetweenPages = Math.abs(dx) > Math.abs(dy) * 1.5;
}
while (remainX != 0 || remainY != 0) {
if (mOffset == 0 && canImageScroll) {
ImageView image = mCurrent.getImageView();
image.scroll(remainX, remainY, mScrollRemain);
remainX = mScrollRemain[0];
remainY = mScrollRemain[1];
canImageScroll = false;
mDeltaX = 0;
mDeltaY = 0;
} else if (remainX == 0 ||
(getLeftPage() == null && mOffset == 0 && remainX < 0) ||
(getRightPage() == null && mOffset == 0 && remainX > 0)) {
// On edge
remainX = 0;
remainY = 0;
} else if (mCanScrollBetweenPages) {
remainX = scrollBetweenPages(remainX);
canImageScroll = true;
needFill = true;
mDeltaX = 0;
mDeltaY = 0;
} else {
remainX = 0;
remainY = 0;
mDeltaX = 0;
mDeltaY = 0;
}
}
if (needFill) {
mGalleryView.requestFill();
}
}
@Override
public void onScroll(float dx, float dy, float totalX, float totalY, float x, float y) {
scrollInternal(dx, dy, x, y);
}
@Override
public void onFling(float velocityX, float velocityY) {
if (mCurrent == null || mOffset != 0 || !mCurrent.getImageView().isLoaded() ||
!mCurrent.getImageView().canFling()) {
return;
}
ImageView image = mCurrent.getImageView();
mPageFling.startFling((int) velocityX, image.getMinDx(), image.getMaxDx(),
(int) velocityY, image.getMinDy(), image.getMaxDy());
}
@Override
public boolean canScale() {
return mCurrent != null && mOffset == 0 && mCurrent.getImageView().isLoaded();
}
@Override
public void onScale(float focusX, float focusY, float scale) {
if (mCurrent == null || !mCurrent.getImageView().isLoaded()) {
return;
}
mCurrent.getImageView().scale(focusX, focusY, scale);
mScaleValue = mCurrent.getImageView().getScale();
}
@Override
public boolean onUpdateAnimation(long time) {
boolean invalidate = mSmoothScroller.calculate(time);
invalidate |= mPageFling.calculate(time);
invalidate |= mSmoothScaler.calculate(time);
return invalidate;
}
@Override
public void onDataChanged() {
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
// Cancel all animations
cancelAllAnimations();
// Remove all views
removeProgress();
removeErrorView();
removeAllPages();
// Reset parameters
resetParameters();
mGalleryView.requestFill();
}
@Override
public void onPageLeft() {
int size = mAdapter.size();
if (size <= 0 || mCurrent == null) {
return;
}
if (mMode == MODE_LEFT_TO_RIGHT) {
if (mIndex != 0) {
setCurrentIndex(mIndex - 1);
}
} else {
if (mIndex < size - 1) {
setCurrentIndex(mIndex + 1);
}
}
}
@Override
public void onPageRight() {
int size = mAdapter.size();
if (size <= 0 || mCurrent == null) {
return;
}
if (mMode == MODE_LEFT_TO_RIGHT) {
if (mIndex < size - 1) {
setCurrentIndex(mIndex + 1);
}
} else {
if (mIndex != 0) {
setCurrentIndex(mIndex - 1);
}
}
}
@Override
public boolean isTapOrPressEnable() {
return !mStopAnimationFinger;
}
@Override
public GalleryPageView findPageByIndex(int index) {
if (mCurrent != null && mCurrent.getIndex() == index) {
return mCurrent;
}
if (mPrevious != null && mPrevious.getIndex() == index) {
return mPrevious;
}
if (mNext != null && mNext.getIndex() == index) {
return mNext;
}
return null;
}
@Override
public int getCurrentIndex() {
if (mCurrent != null) {
return mCurrent.getIndex();
} else {
return GalleryPageView.INVALID_INDEX;
}
}
@Override
public void setCurrentIndex(int index) {
int size = mAdapter.size();
if (size <= 0) {
// Can't get size now, assume size is MAX
size = Integer.MAX_VALUE;
}
if (index == mIndex || index < 0 || index >= size) {
return;
}
if (mCurrent == null) {
mIndex = index;
} else if (index == mIndex - 1) {
// Cancel all animations
cancelAllAnimations();
// Reset parameters
resetParameters();
// Go to previous
pagePrevious();
// Request fill
mGalleryView.requestFill();
} else if (index == mIndex + 1) {
// Cancel all animations
cancelAllAnimations();
// Reset parameters
resetParameters();
// Go to next
pageNext();
// Request fill
mGalleryView.requestFill();
} else {
mIndex = index;
// It is attached, refill
// Cancel all animations
cancelAllAnimations();
// Remove all view
removeProgress();
removeErrorView();
removeAllPages();
// Reset parameters
resetParameters();
// Request fill
mGalleryView.requestFill();
}
}
@Override
public int getIndexUnder(float x, float y) {
if (mCurrent == null) {
return GalleryPageView.INVALID_INDEX;
} else {
int intX = (int) x;
int intY = (int) y;
if (mCurrent.bounds().contains(intX, intY)) {
return mCurrent.getIndex();
} else if (mPrevious != null && mPrevious.bounds().contains(intX, intY)) {
return mPrevious.getIndex();
} else if (mNext != null && mNext.bounds().contains(intX, intY)) {
return mNext.getIndex();
} else {
return GalleryPageView.INVALID_INDEX;
}
}
}
@Override
int getInternalCurrentIndex() {
int currentIndex = getCurrentIndex();
if (currentIndex == GalleryPageView.INVALID_INDEX) {
currentIndex = mIndex;
}
return currentIndex;
}
@IntDef({MODE_LEFT_TO_RIGHT, MODE_RIGHT_TO_LEFT})
@Retention(RetentionPolicy.SOURCE)
private @interface Mode {
}
private class SmoothScroller extends Animation {
private int mDx;
private int mDy;
private int mLastX;
private int mLastY;
public SmoothScroller() {
setInterpolator(SMOOTH_SCROLLER_INTERPOLATOR);
}
public void startSmoothScroll(int dx, int dy, int duration) {
mDx = dx;
mDy = dy;
mLastX = 0;
mLastY = 0;
setDuration(duration);
start();
mGalleryView.invalidate();
}
@Override
protected void onCalculate(float progress) {
int x = (int) (mDx * progress);
int y = (int) (mDy * progress);
int offsetX = x - mLastX;
while (offsetX != 0) {
int oldOffsetX = offsetX;
offsetX = scrollBetweenPages(offsetX);
// Avoid loop infinitely
if (offsetX == oldOffsetX) {
break;
} else {
mGalleryView.requestFill();
}
}
mLastX = x;
mLastY = y;
}
}
private class PageFling extends Fling {
private final int[] mTemp = new int[2];
private int mVelocityX;
private int mVelocityY;
private int mDx;
private int mDy;
private int mLastX;
private int mLastY;
public PageFling(Context context) {
super(context);
}
public void startFling(int velocityX, int minX, int maxX,
int velocityY, int minY, int maxY) {
mVelocityX = velocityX;
mVelocityY = velocityY;
mDx = (int) (getSplineFlingDistance(velocityX) * Math.signum(velocityX));
mDy = (int) (getSplineFlingDistance(velocityY) * Math.signum(velocityY));
mLastX = 0;
mLastY = 0;
int durationX = getSplineFlingDuration(velocityX);
int durationY = getSplineFlingDuration(velocityY);
if (mDx < minX) {
durationX = adjustDuration(0, mDx, minX, durationX);
mDx = minX;
}
if (mDx > maxX) {
durationX = adjustDuration(0, mDx, maxX, durationX);
mDx = maxX;
}
if (mDy < minY) {
durationY = adjustDuration(0, mDy, minY, durationY);
mDy = minY;
}
if (mDy > maxY) {
durationY = adjustDuration(0, mDy, maxY, durationY);
mDy = maxY;
}
if (mDx == 0 && mDy == 0) {
return;
}
setDuration(Math.max(durationX, durationY));
start();
mGalleryView.invalidate();
}
@Override
protected void onCalculate(float progress) {
int x = (int) (mDx * progress);
int y = (int) (mDy * progress);
int offsetX = x - mLastX;
int offsetY = y - mLastY;
if (mCurrent != null && (offsetX != 0 || offsetY != 0)) {
mCurrent.getImageView().scroll(-offsetX, -offsetY, mTemp);
}
mLastX = x;
mLastY = y;
}
}
private class SmoothScaler extends Animation {
private float mFocusX;
private float mFocusY;
private float mStartScale;
private float mEndScale;
private float mLastScale;
public SmoothScaler() {
setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
}
public void startSmoothScaler(float focusX, float focusY,
float startScale, float endScale, int duration) {
mFocusX = focusX;
mFocusY = focusY;
mStartScale = startScale;
mEndScale = endScale;
mLastScale = startScale;
setDuration(duration);
start();
mGalleryView.invalidate();
}
@Override
protected void onCalculate(float progress) {
if (mCurrent == null) {
return;
}
float scale = MathUtils.lerp(mStartScale, mEndScale, progress);
mCurrent.getImageView().scale(mFocusX, mFocusY, scale / mLastScale);
mLastScale = scale;
mScaleValue = scale;
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,163 +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.glgallery;
import androidx.annotation.NonNull;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.image.ImageWrapper;
import com.hippo.glview.view.GLRootView;
public class SimpleAdapter extends GalleryView.Adapter implements GalleryProvider.Listener {
private final GalleryProvider mProvider;
private final ImageTexture.Uploader mUploader;
private boolean mShowIndex = true;
public SimpleAdapter(@NonNull GLRootView glRootView, @NonNull GalleryProvider provider) {
mProvider = provider;
mUploader = new ImageTexture.Uploader(glRootView);
}
public void clearUploader() {
mUploader.clear();
}
public void setShowIndex(boolean show) {
mShowIndex = show;
}
@Override
public void onBind(GalleryPageView view, int index) {
mProvider.request(index);
view.showInfo();
view.setImage(null);
if (mShowIndex) {
view.setPage(index + 1);
} else {
view.hidePage();
}
view.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
view.setError(null, null);
}
@Override
public void onUnbind(GalleryPageView view, int index) {
mProvider.cancelRequest(index);
view.setImage(null);
view.setError(null, null);
}
@Override
public String getError() {
return mProvider.getError();
}
@Override
public int size() {
return mProvider.size();
}
@Override
public void onDataChanged() {
mGalleryView.onDataChanged();
}
@Override
public void onPageWait(int index) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
page.showInfo();
page.setImage(null);
if (mShowIndex) {
page.setPage(index + 1);
} else {
page.hidePage();
}
page.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
page.setError(null, null);
}
}
@Override
public void onPagePercent(int index, float percent) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
page.showInfo();
page.setImage(null);
if (mShowIndex) {
page.setPage(index + 1);
} else {
page.hidePage();
}
page.setProgress(percent);
page.setError(null, null);
}
}
@Override
public void onPageSucceed(int index, ImageWrapper image) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
if (image.obtain()) {
ImageTexture imageTexture = new ImageTexture(image);
mUploader.addTexture(imageTexture);
page.showImage();
page.setImage(imageTexture);
if (mShowIndex) {
page.setPage(index + 1);
} else {
page.hidePage();
}
page.setProgress(GalleryPageView.PROGRESS_GONE);
page.setError(null, null);
} else {
// The image is recycled, request again.
// TODO request loop ?
mProvider.request(index);
}
}
}
@Override
public void onPageFailed(int index, String error) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
page.showInfo();
page.setImage(null);
if (mShowIndex) {
page.setPage(index + 1);
} else {
page.hidePage();
}
page.setProgress(GalleryPageView.PROGRESS_GONE);
page.setError(error, mGalleryView);
}
}
@Override
public void onDataChanged(int index) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
mProvider.request(index);
}
}
private GalleryPageView findPageByIndex(int index) {
return mGalleryView != null ? mGalleryView.findPageByIndex(index) : null;
}
}
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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.glview.anim;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.yorozuya.MathUtils;
public class AlphaAnimation extends CanvasAnimation {
private final float mStartAlpha;
private final float mEndAlpha;
private float mCurrentAlpha;
public AlphaAnimation(float from, float to) {
mStartAlpha = from;
mEndAlpha = to;
mCurrentAlpha = from;
}
@Override
public void apply(GLCanvas canvas) {
canvas.multiplyAlpha(mCurrentAlpha);
}
@Override
public int getCanvasSaveFlags() {
return GLCanvas.SAVE_FLAG_ALPHA;
}
@Override
protected void onCalculate(float progress) {
mCurrentAlpha = MathUtils.clamp(mStartAlpha +
(mEndAlpha - mStartAlpha) * progress, 0f, 1f);
}
}
@@ -1,165 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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.glview.anim;
import android.os.SystemClock;
import android.view.animation.Interpolator;
import com.hippo.yorozuya.MathUtils;
// Animation calculates a value according to the current input time.
//
// 1. First we need to use setDuration(int) to set the duration of the
// animation. The duration is in milliseconds.
// 2. Then we should call start(). The actual start time is the first value
// passed to calculate(long).
// 3. Each time we want to get an animation value, we call
// calculate(long currentTimeMillis) to ask the Animation to calculate it.
// The parameter passed to calculate(long) should be nonnegative.
// 4. Use get() to get that value.
//
// In step 3, onCalculate(float progress) is called so subclasses can calculate
// the value according to progress (progress is a value in [0,1]).
//
// Before onCalculate(float) is called, There is an optional interpolator which
// can change the progress value. The interpolator can be set by
// setInterpolator(Interpolator). If the interpolator is used, the value passed
// to onCalculate may be (for example, the overshoot effect).
//
// The isActive() method returns true after the animation start() is called and
// before calculate is passed a value which reaches the duration of the
// animation.
//
// The start() method can be called again to restart the Animation.
//
abstract public class Animation {
/**
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
* or a positive value, the animation restarts from the beginning.
*/
public static final int RESTART = 1;
/**
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
* or a positive value, the animation reverses direction on every iteration.
*/
public static final int REVERSE = 2;
/**
* This value used used with the {@link #setRepeatCount(int)} property to repeat
* the animation indefinitely.
*/
public static final int INFINITE = -1;
private static final long ANIMATION_START = -1;
private static final long NO_ANIMATION = -2;
private long mStartTime = NO_ANIMATION;
private long mDuration;
private Interpolator mInterpolator;
private int mRepeatCount;
private int mRepeatMode;
private int mRunnedCount;
private long mLastFrameTime;
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
}
public void setDuration(long duration) {
mDuration = duration;
}
public void setRepeatCount(int repeatCount) {
mRepeatCount = repeatCount;
}
public void setRepeatMode(int repeatMode) {
mRepeatMode = repeatMode;
}
public boolean isRunning() {
return mStartTime > 0 || mStartTime == ANIMATION_START;
}
public long getLastFrameTime() {
return mLastFrameTime;
}
public void start() {
if (mStartTime == NO_ANIMATION) {
mStartTime = ANIMATION_START;
mRunnedCount = 0;
mLastFrameTime = 0;
}
}
public void startAt(long time) {
start();
setStartTime(time);
}
public void startNow() {
start();
setStartTime(SystemClock.uptimeMillis());
}
public void setStartTime(long time) {
mStartTime = time;
}
public void cancel() {
mStartTime = NO_ANIMATION;
}
public void reset() {
mStartTime = ANIMATION_START;
mRunnedCount = 0;
mLastFrameTime = 0;
}
// TODO mRepeatMode
public boolean calculate(long currentTimeMillis) {
if (mStartTime == NO_ANIMATION) {
return false;
}
if (mStartTime == ANIMATION_START) {
mStartTime = currentTimeMillis;
}
mLastFrameTime = currentTimeMillis;
long elapse = currentTimeMillis - mStartTime;
float x = MathUtils.clamp(mDuration == 0.0f ? 1.0f : (float) elapse / mDuration, 0.0f, 1.0f); // Avoid NaN
Interpolator i = mInterpolator;
onCalculate(i != null ? i.getInterpolation(x) : x);
// It is ok to call cancel() in onCalculate()
if (mStartTime != NO_ANIMATION && elapse >= mDuration) {
mRunnedCount++;
if (mRunnedCount >= mRepeatCount && mRepeatCount != INFINITE) {
onFinish();
mStartTime = NO_ANIMATION;
} else {
mStartTime += elapse;
}
}
return mStartTime != NO_ANIMATION;
}
abstract protected void onCalculate(float progress);
protected void onFinish() {
}
}
@@ -1,26 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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.glview.anim;
import com.hippo.glview.glrenderer.GLCanvas;
public abstract class CanvasAnimation extends Animation {
public abstract int getCanvasSaveFlags();
public abstract void apply(GLCanvas canvas);
}
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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.glview.anim;
import com.hippo.yorozuya.MathUtils;
public class FloatAnimation extends Animation {
private float mFrom;
private float mTo;
private float mCurrent;
public void setRange(float from, float to) {
mFrom = from;
mTo = to;
}
@Override
protected void onCalculate(float progress) {
mCurrent = MathUtils.lerp(mFrom, mTo, progress);
}
public float get() {
return mCurrent;
}
}
Loaded 100 of 355 files, more files were not shown because too many files have changed in this diff. Show more