Compare commits

..
125 Commits
Author SHA1 Message Date
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
txyyh e568e688f5 Release 1.8.4.0-alpha02
Releases / Build (push) Has been cancelled
2022-12-09 10:59:34 +00:00
FooIbar a201c930c2 Fix "Unable to load PublicSuffixDatabase.gz" 2022-12-09 18:27:03 +08:00
小白-白 7d927dde6b Resolve #505 2022-12-09 18:20:28 +08:00
tarsin f76fece81d Increase LruCache capability to 512 MB 2022-12-09 18:10:42 +08:00
tarsin 069d7e2382 Cleanup ImageDecoder Wrapper
We have removed all user of these
No logical changes
2022-12-09 18:08:26 +08:00
FooIbar e2e0562719 Resolve #501 2022-12-09 10:01:36 +08:00
FooIbar 347aaadb43 Resolve #500 2022-12-09 10:01:36 +08:00
renovate[bot] 6ddd64232f Update plugin com.google.devtools.ksp to v1.8.0-RC-1.0.8 (#503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-09 08:20:18 +08:00
renovate[bot] e532b3b05b Update dependency gradle to v8.0-milestone-5 2022-12-08 23:12:38 +00:00
小白-白 d7c17d80e7 Close db before export, Resolve #361 2022-12-08 15:40:22 +08:00
tarsin b35980cf60 Drop outdated workaround 2022-12-08 11:06:18 +08:00
tarsin 263853d6a7 Always collapse appbar when created, set CollapseMode to scale 2022-12-08 10:37:59 +08:00
tarsin 6a100b8250 Use CollapsingToolbar.Large, Resolve #482 2022-12-08 10:02:48 +08:00
renovate[bot] 893560e589 Update room_version to v2.5.0-rc01 (#498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-08 09:18:03 +08:00
renovate[bot] 6b7ce7de5b Update fragment_version to v1.6.0-alpha04 (#496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-08 09:06:20 +08:00
renovate[bot] 6082d8cc1a Update plugin org.jetbrains.kotlin.android to v1.8.0-RC (#497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-08 09:06:03 +08:00
renovate[bot] ec37c9cab2 Update dependency androidx.constraintlayout:constraintlayout to v2.2.0-alpha05 (#495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-08 08:23:02 +08:00
txyyh 309e2a715e Release 1.8.4.0-alpha01
Releases / Build (push) Has been cancelled
2022-12-07 12:28:52 +00:00
tarsin fbb4afa4f7 Fix searchlayout style 2022-12-07 20:26:54 +08:00
tarsin 90863b95f4 Fix fab position 2022-12-07 20:18:56 +08:00
tarsin 3576eaf3ff Try to fix thumb load 2022-12-07 20:08:53 +08:00
tarsin f14c9741ea Increase card margin 2022-12-07 19:47:49 +08:00
tarsin 6d04e51e10 Fix amoled mode apply 2022-12-07 19:26:17 +08:00
tarsin 6ea1aa6c1a Fix searchbar 2022-12-07 18:51:44 +08:00
tarsin c86e1a9409 Drop unused widget 2022-12-07 18:00:50 +08:00
tarsin f1845f2d4e Use transparent card background 2022-12-07 14:41:59 +08:00
tarsin a20ca4ea2e Convert FavouriteScene to New SearchBar, Kill hippo's searchbar 2022-12-07 14:37:32 +08:00
tarsin 0effc0d181 Fix searchfab 2022-12-07 14:18:03 +08:00
tarsin 7ce273ec67 Initial migrate GalleryListScene to New SearchBar, #397 2022-12-07 13:47:10 +08:00
tarsin 39e080cdca Fix thumb flicker 2022-12-06 18:14:17 +08:00
tarsin 17584ae5fb Avoid double authenticating on Android 10 2022-12-06 12:01:58 +08:00
小白-白 c46c72d66d Add private category 2022-12-05 18:05:16 +08:00
小白-白 36f00ab568 Fix disowned parsing 2022-12-05 18:05:16 +08:00
tarsin dae7dacf3d Rework SimilarityScan 2022-12-05 11:43:52 +08:00
tarsin b69b72cd1b Resolve #489 2022-12-05 09:57:42 +08:00
tarsin 68d1e39410 Refactor dependency with lazykt 2022-12-04 22:31:02 +08:00
tarsin d4482dec23 Rename .java to .kt 2022-12-04 22:31:02 +08:00
tarsin 8f36cecc7a Cleanup Code 2022-12-04 18:23:15 +08:00
tarsin a59fbcc9c8 Thanks coil 2022-12-04 12:47:14 +08:00
tarsin 25cddb2d61 Migrate to coil imageloader, Drop Conaco and BeerBelly as well
TODO: Fix SimilarityScan
2022-12-04 12:43:44 +08:00
tarsin 9a7af235b8 Fix gif render, Resolve #485 2022-12-03 09:26:08 +08:00
FooIbar c9751ab2a2 Fix simpleTags parsing (#484)
* Fix simpleTags parsing

* Adjust for low-power tags in extended mode
2022-12-03 08:49:19 +08:00
tarsin 5e13986cc0 Prefault archive mapping page tables for small files 2022-12-02 17:33:54 +08:00
tarsin 35e2cc535e Fix #483 2022-12-02 14:24:49 +08:00
tarsin 81355a0248 Resolve #479 2022-12-02 12:03:39 +08:00
tarsin 3e98502e2d Resolve #480 2022-12-02 12:00:04 +08:00
360 changed files with 17825 additions and 28069 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 -4
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,18 +66,19 @@ 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
Here is the libraries
- [AOSP & AndroidX](http://source.android.com/)
- [Coil](https://coil-kt.github.io/coil/)
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
- [GnuTLS](https://gnutls.org/)
- [material-components-android](https://github.com/material-components/material-components-android)
+27 -16
View File
@@ -31,8 +31,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 28
targetSdkVersion 33
versionCode 180024
versionName "1.8.3.3"
versionCode 180029
versionName "1.8.5.1"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -50,6 +50,12 @@ android {
kotlinOptions {
jvmTarget = '19'
freeCompilerArgs += [
// https://kotlinlang.org/docs/compiler-reference.html#progressive
"-progressive",
// Generate smaller bytecode by not generating runtime not-null assertions.
"-Xno-call-assertions",
"-Xno-param-assertions",
"-Xno-receiver-assertions",
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
]
@@ -88,6 +94,8 @@ android {
buildFeatures {
buildConfig true
compose true
dataBinding true
viewBinding true
}
composeOptions {
@@ -117,17 +125,16 @@ 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 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.compose.material3:material3:1.0.1"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
def core_version = "1.9.0"
@@ -137,7 +144,7 @@ dependencies {
implementation "androidx.customview:customview:1.2.0-alpha02"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.6.0-alpha03"
def fragment_version = "1.6.0-alpha04"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
@@ -162,7 +169,7 @@ dependencies {
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
def room_version = "2.5.0-beta02"
def room_version = "2.5.0-rc01"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-paging:$room_version"
@@ -176,19 +183,23 @@ 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'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
implementation '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 -2
View File
@@ -14,8 +14,10 @@
-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 { }
-renamesourcefileattribute
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
-repackageclasses
-allowaccessmodification
-mergeinterfacesaggressively
-overloadaggressively
+16 -7
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,14 @@
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 +101,8 @@
</activity>
<activity
android:name="com.hippo.ehviewer.ui.GalleryActivity"
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:colorMode="wideColorGamut"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask">
@@ -160,6 +160,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)
+59 -3
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,10 @@ 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
const char supportExt[9][6] = {
"jpeg",
@@ -75,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)
@@ -199,7 +232,10 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
EH_UNUSED(env);
EH_UNUSED(thiz);
archive_ctx *ctx = NULL;
archiveAddr = mmap64(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
int mmap_flags = MAP_PRIVATE;
if (size <= POPULATE_THRESHOLD)
mmap_flags |= MAP_POPULATE;
archiveAddr = mmap64(0, size, PROT_READ, mmap_flags, fd, 0);
if (archiveAddr == MAP_FAILED) {
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
return 0;
@@ -216,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
@@ -246,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;
}
@@ -254,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);
@@ -290,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
@@ -333,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);
@@ -347,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);
@@ -358,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);
}
-166
View File
@@ -1,166 +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_nativeRender(JNIEnv *env, jclass clazz, jobject srcBitmap, jint src_x,
jint src_y, jobject dst, jint dst_x, jint dst_y, jint width,
jint height) {
AndroidBitmapInfo dstInfo;
AndroidBitmapInfo srcInfo;
void *srcPixels = NULL;
void *dstPixels = NULL;
AndroidBitmap_lockPixels(env, srcBitmap, &srcPixels);
AndroidBitmap_lockPixels(env, dst, &dstPixels);
AndroidBitmap_getInfo(env, srcBitmap, &srcInfo);
AndroidBitmap_getInfo(env, dst, &dstInfo);
copy_pixels(srcPixels, srcInfo.width, srcInfo.height, src_x, src_y, dstPixels, dstInfo.width,
dstInfo.height, dst_x, dst_y,
width,
height);
AndroidBitmap_unlockPixels(env, dst);
AndroidBitmap_unlockPixels(env, srcBitmap);
}
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);
}
}
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)
}
-1
View File
@@ -20,7 +20,6 @@ package com.hippo
import java.io.FileDescriptor
object Native {
@JvmStatic
fun initialize() {
System.loadLibrary("ehviewer")
}
@@ -1,28 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD})
public @interface Implemented {
Class value();
}
@@ -1,493 +0,0 @@
/*
* Copyright (C) 2015-2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.beerbelly;
import android.util.Log;
import android.util.LruCache;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public abstract class BeerBelly<V> {
private static final String TAG = BeerBelly.class.getSimpleName();
private final boolean mHasMemoryCache;
private final boolean mHasDiskCache;
@Nullable
private MemoryCache<V> mMemoryCache;
@Nullable
private DiskCache<V> mDiskCache;
public BeerBelly(BeerBellyParams params) {
params.isValid();
mHasMemoryCache = params.hasMemoryCache;
mHasDiskCache = params.hasDiskCache;
if (mHasMemoryCache) {
initMemoryCache(params.memoryCacheMaxSize);
}
if (mHasDiskCache) {
initDiskCache(params.diskCacheDir, params.diskCacheMaxSize);
}
}
private void initMemoryCache(int maxSize) {
mMemoryCache = new MemoryCache<>(maxSize, this);
}
private void initDiskCache(File cacheDir, int maxSize) {
// Set up disk cache
try {
mDiskCache = new DiskCache<>(cacheDir, maxSize, this);
} catch (IOException e) {
Log.e(TAG, "Can't create disk cache", e);
}
}
protected abstract int sizeOf(String key, V value);
protected abstract void memoryEntryAdded(V value);
protected abstract void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue);
protected abstract V read(@NonNull InputStreamPipe isPipe);
protected abstract boolean write(OutputStream os, V value);
@Nullable
public LruCache<String, V> getMemoryCache() {
return mMemoryCache;
}
@Nullable
public SimpleDiskCache getDiskCache() {
if (mDiskCache != null) {
return mDiskCache.mDiskCache;
} else {
return null;
}
}
/**
* Check if have memory cache
*
* @return true if have memory cache
*/
public boolean hasMemoryCache() {
return mHasMemoryCache;
}
/**
* Check if have disk cache
*
* @return true if have disk cache
*/
public boolean hasDiskCache() {
return mHasDiskCache;
}
/**
* Get memory cache usage size
*
* @return {@code -1} for no memory cache
*/
public int memorySize() {
if (mHasMemoryCache && mMemoryCache != null) {
return mMemoryCache.size();
} else {
return -1;
}
}
/**
* Get memory cache max size
*
* @return {@code -1} for no memory cache
*/
public int memoryMaxSize() {
if (mHasMemoryCache && mMemoryCache != null) {
return mMemoryCache.maxSize();
} else {
return -1;
}
}
/**
* Get disk cache usage size
*
* @return {@code -1L} for no memory cache
*/
public long diskSize() {
if (mHasDiskCache && mDiskCache != null) {
return mDiskCache.size();
} else {
return -1L;
}
}
/**
* Get disk cache max size
*
* @return {@code -1L} for no memory cache
*/
public long diskMaxSize() {
if (mHasDiskCache && mDiskCache != null) {
return mDiskCache.getMaxSize();
} else {
return -1L;
}
}
/**
* Get value from memory cache
*
* @param key the key to get value
* @return the value you get, null for miss or no memory cache
*/
public V getFromMemory(@NonNull String key) {
if (mHasMemoryCache && mMemoryCache != null) {
return mMemoryCache.get(key);
} else {
return null;
}
}
/**
* Get value from disk cache. Override {@link #read(InputStreamPipe)} to do it
*
* @param key the key to get value
* @return the value you get, null for miss or no memory cache or get error
*/
public V getFromDisk(@NonNull String key) {
if (mHasDiskCache && mDiskCache != null) {
return mDiskCache.get(key);
} else {
return null;
}
}
/**
* Get value from memory cache and disk cache. If miss in memory cache and
* get in disk cache, it will put value from disk cache to memory cache.
*
* @param key the key to get value
* @return the value you get
*/
public V get(@NonNull String key) {
V value = getFromMemory(key);
if (value != null) {
// Get it in memory cache
return value;
}
value = getFromDisk(key);
if (value != null) {
// Get it in disk cache
putToMemory(key, value);
return value;
}
return null;
}
/**
* Put value to memory cache
*
* @param key the key
* @param value the value
* @return false if no memory cache
*/
public boolean putToMemory(@NonNull String key, @NonNull V value) {
if (mHasMemoryCache && mMemoryCache != null) {
memoryEntryAdded(value);
mMemoryCache.put(key, value);
return true;
} else {
return false;
}
}
/**
* Put value to disk cache
*
* @param key the key
* @param value the value
* @return false if no disk cache or get error
*/
public boolean putToDisk(@NonNull String key, @NonNull V value) {
if (mHasDiskCache && mDiskCache != null) {
return mDiskCache.put(key, value);
} else {
return false;
}
}
/**
* Put value to memory cache and disk cache
*
* @param key the key
* @param value the value
*/
public void put(@NonNull String key, @NonNull V value) {
putToMemory(key, value);
putToDisk(key, value);
}
public void removeFromMemory(@NonNull String key) {
if (mHasMemoryCache && mMemoryCache != null) {
mMemoryCache.remove(key);
}
}
public void removeFromDisk(@NonNull String key) {
if (mHasDiskCache && mDiskCache != null) {
mDiskCache.remove(key);
}
}
public void remove(@NonNull String key) {
removeFromMemory(key);
removeFromDisk(key);
}
public boolean pullFromDiskCache(@NonNull String key, @NonNull OutputStream os) {
if (mHasDiskCache && mDiskCache != null) {
return mDiskCache.pull(key, os);
} else {
return false;
}
}
/**
* @param key the key
* @param is the input stream to store
* @return false if no disk cache or get error
*/
public boolean putRawToDisk(@NonNull String key, @NonNull InputStream is) {
if (mHasDiskCache && mDiskCache != null) {
return mDiskCache.putRaw(key, is);
} else {
return false;
}
}
/**
* Evicts all of the items from the memory cache
*/
public void clearMemory() {
if (mHasMemoryCache && mMemoryCache != null) {
mMemoryCache.evictAll();
}
}
/**
* Clear disk cache
*/
public void clearDisk() {
if (mHasDiskCache && mDiskCache != null) {
mDiskCache.clear();
}
}
public void flush() {
if (mHasDiskCache && mDiskCache != null) {
mDiskCache.flush();
}
}
/**
* Evicts all of the items from the memory cache and lets the system know
* now would be a good time to garbage collect
*/
public void clear() {
clearMemory();
clearDisk();
}
/**
* A holder class that contains cache parameters.
*/
public static class BeerBellyParams {
/**
* is memory cache available
*/
public boolean hasMemoryCache = false;
/**
* the maximum number of bytes the memory cache should use to store
*/
public int memoryCacheMaxSize = 0;
/**
* is disk cache available
*/
public boolean hasDiskCache = false;
/**
* the dir to store disk cache
*/
public File diskCacheDir = null;
/**
* the maximum number of bytes the disk cache should use to store
*/
public int diskCacheMaxSize = 0;
/**
* Check BeerBellyParams is valid
*/
public void isValid() throws IllegalStateException {
if (!hasMemoryCache && !hasDiskCache) {
throw new IllegalStateException("No memory cache and no disk cache. What can I do for you?");
}
if (hasMemoryCache && memoryCacheMaxSize <= 0) {
throw new IllegalStateException("Memory cache max size must be bigger than 0.");
}
if (hasDiskCache) {
if (diskCacheDir == null) {
throw new IllegalStateException("Disk cache dir can't be null.");
}
if (diskCacheMaxSize <= 0) {
throw new IllegalStateException("Disk cache max size must be bigger than 0.");
}
}
}
}
private static class DiskCache<E> {
private static final int IO_BUFFER_SIZE = 8 * 1024;
private final SimpleDiskCache mDiskCache;
private final BeerBelly<E> mParent;
private final File mCacheDir;
private final int mMaxSize;
public DiskCache(File cacheDir, int size, BeerBelly<E> parent) throws IOException {
mDiskCache = new SimpleDiskCache(cacheDir, size);
mParent = parent;
mCacheDir = cacheDir;
mMaxSize = size;
}
public File getCacheDir() {
return mCacheDir;
}
public int getMaxSize() {
return mMaxSize;
}
public long size() {
return mDiskCache.size();
}
public void flush() {
mDiskCache.flush();
}
public void clear() {
mDiskCache.clear();
}
public E get(String key) {
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
if (isPipe == null) {
return null;
} else {
return mParent.read(isPipe);
}
}
public boolean put(String key, E value) {
OutputStreamPipe osPipe = mDiskCache.getOutputStreamPipe(key);
BufferedOutputStream buffOut = null;
try {
osPipe.obtain();
OutputStream os = osPipe.open();
buffOut = new BufferedOutputStream(os, IO_BUFFER_SIZE);
return mParent.write(os, value);
} catch (IOException e) {
return false;
} finally {
Util.closeQuietly(buffOut);
osPipe.close();
osPipe.release();
}
}
public boolean putRaw(String key, InputStream is) {
return mDiskCache.put(key, is);
}
public void remove(String key) {
mDiskCache.remove(key);
}
public boolean pull(@NonNull String key, @NonNull OutputStream os) {
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
if (isPipe == null) {
return false;
} else {
try {
isPipe.obtain();
InputStream is = isPipe.open();
Util.copy(is, os);
return true;
} catch (IOException e) {
return false;
} finally {
isPipe.close();
isPipe.release();
}
}
}
}
private class MemoryCache<E> extends LruCache<String, E> {
public BeerBelly<E> mParent;
public MemoryCache(int maxSize, BeerBelly<E> parent) {
super(maxSize);
mParent = parent;
}
@Override
protected int sizeOf(String key, E value) {
return mParent.sizeOf(key, value);
}
@Override
protected void entryRemoved(boolean evicted, String key, E oldValue, E newValue) {
mParent.memoryEntryRemoved(evicted, key, oldValue, newValue);
}
}
}
@@ -246,21 +246,7 @@ public final class DiskLruCache implements Closeable {
if (!from.renameTo(to)) {
throw new IOException();
}
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
}
private static String inputStreamToString(InputStream in) throws IOException {
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
@@ -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,223 +0,0 @@
/*
* Copyright 2015-2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.conaco;
import android.os.Process;
import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.UiThread;
import com.hippo.beerbelly.BeerBelly;
import com.hippo.yorozuya.thread.PriorityThreadFactory;
import com.hippo.yorozuya.thread.SerialThreadExecutor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.OkHttpClient;
@SuppressWarnings("rawtypes")
public final class Conaco<V> {
public static final int SOURCE_MEMORY = 0;
public static final int SOURCE_DISK = 1;
public static final int SOURCE_NETWORK = 2;
private static final String TAG = Conaco.class.getSimpleName();
private final ValueHelper<V> mHelper;
private final ValueCache<V> mCache;
private final OkHttpClient mOkHttpClient;
private final Register<V> mRegister;
private final SerialThreadExecutor mDiskExecutor;
private final ThreadPoolExecutor mNetworkExecutor;
private final AtomicInteger mIdGenerator;
private final boolean mDebug;
private Conaco(Builder<V> builder) {
mHelper = builder.objectHelper;
BeerBelly.BeerBellyParams beerBellyParams = new BeerBelly.BeerBellyParams();
beerBellyParams.hasMemoryCache = builder.hasMemoryCache;
beerBellyParams.memoryCacheMaxSize = builder.memoryCacheMaxSize;
beerBellyParams.hasDiskCache = builder.hasDiskCache;
beerBellyParams.diskCacheDir = builder.diskCacheDir;
beerBellyParams.diskCacheMaxSize = builder.diskCacheMaxSize;
mCache = new ValueCache<>(beerBellyParams, mHelper);
mOkHttpClient = builder.okHttpClient;
mRegister = new Register<>();
mDiskExecutor = new SerialThreadExecutor(3000L, new LinkedList<>(),
new PriorityThreadFactory("Conaco-Disk", Process.THREAD_PRIORITY_BACKGROUND));
mNetworkExecutor = new ThreadPoolExecutor(3, 3, 5L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new PriorityThreadFactory("Conaco-Network", Process.THREAD_PRIORITY_BACKGROUND));
mIdGenerator = new AtomicInteger();
mDebug = builder.debug;
}
public BeerBelly getBeerBelly() {
return mCache;
}
private void onUnregisterConacoTask(ConacoTask<V> task) {
ConacoTask<V> next = mRegister.getByKey(task.getKey());
if (next != null) {
startConacoTask(next);
}
}
private void startConacoTask(ConacoTask<V> task) {
Unikery<V> unikery = task.getUnikery();
// Can't unikery is gone, finish the task now
if (unikery == null) {
finishConacoTask(task);
return;
}
String key = task.getKey();
V value = null;
// Get from memory
if (key != null && task.useMemoryCache() && mHelper.useMemoryCache(key, null)) {
value = mCache.getFromMemory(key);
}
if (value == null || !unikery.onGetValue(value, SOURCE_MEMORY)) {
unikery.onMiss(SOURCE_MEMORY);
task.start();
} else {
// Get the object, finish the task
finishConacoTask(task);
}
}
@UiThread
public void load(ConacoTask.Builder<V> builder) {
builder.isValid();
if (mDebug) {
Log.d(TAG, "Key " + builder.getKey());
Log.d(TAG, "Url " + builder.getUrl());
}
Unikery<V> unikery = builder.getUnikery();
// Cancel first
cancel(unikery);
// Build conaco task
int id;
//noinspection StatementWithEmptyBody
while ((id = mIdGenerator.getAndIncrement()) == Unikery.INVALID_ID) ;
unikery.setTaskId(id);
builder.setId(id)
.setHelper(mHelper)
.setCache(mCache)
.setOkHttpClient(mOkHttpClient)
.setDiskExecutor(mDiskExecutor)
.setNetworkExecutor(mNetworkExecutor)
.setConaco(this);
ConacoTask<V> task = builder.build();
if (!mRegister.register(id, task)) {
startConacoTask(task);
} else {
// The key is repeated, wait
unikery.onWait();
}
}
@UiThread
public void cancel(Unikery<V> unikery) {
int id = unikery.getTaskId();
if (id != Unikery.INVALID_ID) {
unikery.setTaskId(Unikery.INVALID_ID);
ConacoTask<V> task = mRegister.unregister(id);
if (task != null) {
task.stop();
// Don't need unikery anymore
task.clearUnikery();
// Callback
onUnregisterConacoTask(task);
} else {
Log.e(TAG, "Can't find conaco task by id " + id);
}
}
}
public boolean isLoading(Unikery<V> unikery) {
int id = unikery.getTaskId();
return id != Unikery.INVALID_ID && mRegister.contain(id);
}
/**
* Unregister task, reset unikery id, clear unikery in task, call next
*/
void finishConacoTask(ConacoTask<V> task) {
mRegister.unregister(task.getId());
Unikery unikery = task.getUnikery();
if (unikery != null) {
unikery.setTaskId(Unikery.INVALID_ID);
}
task.clearUnikery();
onUnregisterConacoTask(task);
}
@IntDef({SOURCE_MEMORY, SOURCE_DISK, SOURCE_NETWORK})
@Retention(RetentionPolicy.SOURCE)
public @interface Source {
}
public static class Builder<T> extends BeerBelly.BeerBellyParams {
/**
* The client to get image from internet
*/
public OkHttpClient okHttpClient = null;
/**
* Decode, get size and others
*/
public ValueHelper<T> objectHelper = null;
public boolean debug = false;
@Override
public void isValid() throws IllegalStateException {
super.isValid();
if (okHttpClient == null) {
throw new IllegalStateException("No http client? How can I load image via url?");
}
}
public Conaco<T> build() {
isValid();
return new Conaco<>(this);
}
}
}
@@ -1,484 +0,0 @@
/*
* Copyright 2015-2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.conaco;
import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.okhttp.ChromeRequestBuilder;
import com.hippo.streampipe.OutputStreamPipe;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
@SuppressWarnings({"rawtypes", "deprecation"})
public final class ConacoTask<V> {
private static final String TAG = ConacoTask.class.getSimpleName();
private final int mId;
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
private final String mKey;
private final String mUrl;
private final boolean mUseMemoryCache;
private final boolean mUseDiskCache;
private final boolean mUseNetwork;
private final ValueHelper<V> mHelper;
private final ValueCache<V> mCache;
private final OkHttpClient mOkHttpClient;
private final Executor mDiskExecutor;
private final Executor mNetworkExecutor;
private final Conaco<V> mConaco;
private DiskLoadTask mDiskLoadTask;
private NetworkLoadTask mNetworkLoadTask;
private Call mCall;
private boolean mStart;
private volatile boolean mStop;
private ConacoTask(Builder<V> builder) {
mId = builder.mId;
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
mKey = builder.mKey;
mUrl = builder.mUrl;
mUseMemoryCache = builder.mUseMemoryCache;
mUseDiskCache = builder.mUseDiskCache;
mUseNetwork = builder.mUseNetwork;
mHelper = builder.mHelper;
mCache = builder.mCache;
mOkHttpClient = builder.mOkHttpClient;
mDiskExecutor = builder.mDiskExecutor;
mNetworkExecutor = builder.mNetworkExecutor;
mConaco = builder.mConaco;
}
int getId() {
return mId;
}
String getKey() {
return mKey;
}
boolean useMemoryCache() {
return mUseMemoryCache;
}
@Nullable
Unikery<V> getUnikery() {
return mUnikeryWeakReference.get();
}
void clearUnikery() {
mUnikeryWeakReference.clear();
}
private void onFinish() {
if (!mStop) {
mConaco.finishConacoTask(this);
}/* else {
// It is done by Conaco
}*/
}
@UiThread
void start() {
if (mStop || mStart) {
return;
}
mStart = true;
Unikery unikery = mUnikeryWeakReference.get();
if (unikery != null && unikery.getTaskId() == mId) {
if (mUseDiskCache) {
mDiskLoadTask = new DiskLoadTask();
mDiskLoadTask.executeOnExecutor(mDiskExecutor);
return;
} else if (mUseNetwork) {
unikery.onMiss(Conaco.SOURCE_DISK);
unikery.onRequest();
mNetworkLoadTask = new NetworkLoadTask();
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
return;
} else {
unikery.onMiss(Conaco.SOURCE_DISK);
unikery.onMiss(Conaco.SOURCE_NETWORK);
unikery.onFailure();
}
}
onFinish();
}
@UiThread
void stop() {
if (mStop) {
return;
}
mStop = true;
// Stop jobs
if (mDiskLoadTask != null) { // Getting from disk
mDiskLoadTask.cancel(false);
} else if (mNetworkLoadTask != null) { // Getting from network
mNetworkLoadTask.cancel(false);
if (mCall != null) {
mCall = null;
}
}
Unikery unikery = mUnikeryWeakReference.get();
if (unikery != null) {
unikery.onCancel();
}
// Conaco handle the clean up
}
private boolean isNotNecessary(AsyncTask asyncTask) {
Unikery unikery = mUnikeryWeakReference.get();
return mStop || asyncTask.isCancelled() || unikery == null || unikery.getTaskId() != mId;
}
@SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"})
public static class Builder<T> {
private int mId;
private Unikery<T> mUnikery;
private String mKey;
private String mUrl;
private boolean mUseMemoryCache = true;
private boolean mUseDiskCache = true;
private boolean mUseNetwork = true;
private ValueHelper<T> mHelper;
private ValueCache<T> mCache;
private OkHttpClient mOkHttpClient;
private Executor mDiskExecutor;
private Executor mNetworkExecutor;
private Conaco<T> mConaco;
public Builder<T> setId(int id) {
mId = id;
return this;
}
public Unikery<T> getUnikery() {
return mUnikery;
}
public Builder<T> setUnikery(Unikery<T> unikery) {
mUnikery = unikery;
return this;
}
public String getKey() {
return mKey;
}
public Builder<T> setKey(String key) {
mKey = key;
return this;
}
public String getUrl() {
return mUrl;
}
public Builder<T> setUrl(String url) {
mUrl = url;
return this;
}
boolean isUseMemoryCache() {
return mUseMemoryCache;
}
public Builder<T> setUseMemoryCache(boolean useMemoryCache) {
mUseMemoryCache = useMemoryCache;
return this;
}
boolean isUseDiskCache() {
return mUseDiskCache;
}
public Builder<T> setUseDiskCache(boolean useDiskCache) {
mUseDiskCache = useDiskCache;
return this;
}
boolean isUseNetwork() {
return mUseNetwork;
}
public Builder<T> setUseNetwork(boolean useNetwork) {
mUseNetwork = useNetwork;
return this;
}
ValueHelper<T> getHelper() {
return mHelper;
}
Builder<T> setHelper(ValueHelper<T> helper) {
mHelper = helper;
return this;
}
Builder<T> setCache(ValueCache<T> cache) {
mCache = cache;
return this;
}
Builder<T> setOkHttpClient(OkHttpClient okHttpClient) {
mOkHttpClient = okHttpClient;
return this;
}
Builder<T> setDiskExecutor(Executor diskExecutor) {
mDiskExecutor = diskExecutor;
return this;
}
Builder<T> setNetworkExecutor(Executor networkExecutor) {
mNetworkExecutor = networkExecutor;
return this;
}
Builder<T> setConaco(Conaco<T> conaco) {
mConaco = conaco;
return this;
}
public void isValid() {
if (mUnikery == null) {
throw new IllegalStateException("Must set unikery");
}
if (mKey == null && mUrl == null) {
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
}
}
public ConacoTask<T> build() {
return new ConacoTask<>(this);
}
}
@SuppressLint("StaticFieldLeak")
private class DiskLoadTask extends AsyncTask<Void, Void, V> {
@Override
protected V doInBackground(Void... params) {
if (isNotNecessary(this)) {
return null;
} else {
V value = null;
// Then check disk cache
if (mKey != null) {
if (value == null && mUseDiskCache) {
value = mCache.getFromDisk(mKey);
}
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
mCache.putToMemory(mKey, value);
}
}
return value;
}
}
@Override
protected void onPostExecute(V value) {
mDiskLoadTask = null;
if (isCancelled() || mStop) {
onCancelled(value);
} else {
Unikery<V> unikery = mUnikeryWeakReference.get();
if (unikery != null && unikery.getTaskId() == mId) {
boolean getValue = false;
if ((value == null || !(getValue = unikery.onGetValue(value, Conaco.SOURCE_DISK))) && mUseNetwork) {
unikery.onMiss(Conaco.SOURCE_DISK);
unikery.onRequest();
mNetworkLoadTask = new NetworkLoadTask();
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
return;
} else if (!getValue) {
unikery.onMiss(Conaco.SOURCE_DISK);
unikery.onFailure();
}
}
onFinish();
}
}
@Override
protected void onCancelled(V holder) {
onFinish();
}
}
@SuppressLint("StaticFieldLeak")
private class NetworkLoadTask extends AsyncTask<Void, Long, V> implements ProgressNotifier {
@Override
public void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize) {
if (!isNotNecessary(this)) {
publishProgress(singleReceivedSize, receivedSize, totalSize);
}
}
private boolean putToDiskCache(InputStream is, long length) {
SimpleDiskCache diskCache = mCache.getDiskCache();
if (diskCache == null) {
return false;
}
OutputStreamPipe pipe = diskCache.getOutputStreamPipe(mKey);
try {
pipe.obtain();
OutputStream os = pipe.open();
final byte[] buffer = new byte[1024 * 4];
long receivedSize = 0;
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
receivedSize += bytesRead;
notifyProgress(bytesRead, receivedSize, length);
}
return true;
} catch (IOException e) {
return false;
} finally {
pipe.close();
pipe.release();
}
}
@Override
protected V doInBackground(Void... params) {
if (isNotNecessary(this)) {
return null;
}
V value;
InputStream is = null;
try {
Log.d("TAG", "Conaco " + mUrl);
// Load it from internet
Request request = new ChromeRequestBuilder(mUrl).build();
mCall = mOkHttpClient.newCall(request);
Response response = mCall.execute();
ResponseBody body = response.body();
if (body == null) {
return null;
}
is = body.byteStream();
if (isNotNecessary(this)) {
return null;
}
if (mKey != null) {
if (putToDiskCache(is, body.contentLength())) {
// Get object from disk cache
value = mCache.getFromDisk(mKey);
if (value == null) {
// Maybe bad download, remove it from disk cache
mCache.removeFromDisk(mKey);
} else if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
// Put it to memory
mCache.putToMemory(mKey, value);
}
return value;
} else {
// Maybe bad download, remove it from disk cache
mCache.removeFromDisk(mKey);
return null;
}
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
mCall = null;
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
// Ignore
}
}
}
@Override
protected void onPostExecute(V holder) {
mNetworkLoadTask = null;
if (isCancelled() || mStop) {
onCancelled(holder);
} else {
Unikery<V> unikery = mUnikeryWeakReference.get();
if (unikery != null && unikery.getTaskId() == mId) {
if (holder == null || !unikery.onGetValue(holder, Conaco.SOURCE_NETWORK)) {
unikery.onFailure();
}
}
onFinish();
}
}
@Override
protected void onCancelled(V value) {
onFinish();
}
@Override
protected void onProgressUpdate(Long... values) {
Unikery unikery = mUnikeryWeakReference.get();
if (!mStop && !isCancelled() && unikery != null && unikery.getTaskId() == mId) {
unikery.onProgress(values[0], values[1], values[2]);
}
}
}
}
@@ -1,22 +0,0 @@
/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.conaco;
public interface ProgressNotifier {
void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize);
}
@@ -1,72 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.conaco;
import com.hippo.yorozuya.collect.Multimap;
import com.hippo.yorozuya.collect.SparseILArray;
import java.util.List;
class Register<V> {
private final SparseILArray<ConacoTask<V>> mIdMap = new SparseILArray<>();
private final Multimap<String, ConacoTask<V>> mKeyMap = Multimap.create();
/**
* @return true for the key is already registered
*/
public synchronized boolean register(int id, ConacoTask<V> task) {
boolean repeatedKey = false;
String taskKey = task.getKey();
// For no-key task, there no need to check
if (taskKey != null) {
repeatedKey = mKeyMap.countElements(taskKey) != 0;
}
// Append task
mKeyMap.putElement(taskKey, task);
mIdMap.append(id, task);
return repeatedKey;
}
public synchronized ConacoTask<V> unregister(int id) {
ConacoTask<V> task = mIdMap.remove(id);
if (task != null) {
mKeyMap.removeElement(task.getKey(), task);
}
return task;
}
public synchronized boolean contain(int id) {
return mIdMap.indexOfKey(id) >= 0;
}
public synchronized ConacoTask<V> getByKey(String key) {
if (key == null) {
return null;
}
List<ConacoTask<V>> list = mKeyMap.get(key);
if (list != null && list.size() > 0) {
return list.get(0);
} else {
return null;
}
}
}
@@ -1,77 +0,0 @@
/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.conaco;
import androidx.annotation.NonNull;
public interface Unikery<V> {
int INVALID_ID = -1;
/**
* Get the conaco task id
*
* @return the conaco task id
*/
int getTaskId();
/**
* Store the conaco task id
*
* @param id the conaco task id
*/
void setTaskId(int id);
/**
* On miss in the source
*/
void onMiss(@Conaco.Source int source);
/**
* On start http request
*/
void onRequest();
/**
* On http request progress
*/
void onProgress(long singleReceivedSize, long receivedSize, long totalSize);
/**
* On start to wait because repeated key
*/
void onWait();
/**
* On get the value
*
* @return {@code true} for the {@code Unikery} accepts the value
* and the task ends. {@code false} for the {@code Unikery}
* reject the value and the task goes on or failed
*/
boolean onGetValue(@NonNull V value, @Conaco.Source int source);
/**
* On failed to get value
*/
void onFailure();
/**
* On user cancel the conaco task
*/
void onCancel();
}
@@ -1,61 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.conaco;
import androidx.annotation.NonNull;
import com.hippo.beerbelly.BeerBelly;
import com.hippo.streampipe.InputStreamPipe;
import java.io.OutputStream;
class ValueCache<V> extends BeerBelly<V> {
private final ValueHelper<V> mHelper;
public ValueCache(BeerBelly.BeerBellyParams params, ValueHelper<V> helper) {
super(params);
mHelper = helper;
}
@Override
protected int sizeOf(String key, V value) {
return mHelper.sizeOf(key, value);
}
@Override
protected void memoryEntryAdded(V value) {
mHelper.onAddToMemoryCache(value);
}
@Override
protected void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue) {
if (oldValue != null) {
mHelper.onRemoveFromMemoryCache(key, oldValue);
}
}
@Override
protected V read(@NonNull InputStreamPipe isPipe) {
return mHelper.decode(isPipe);
}
@Override
protected boolean write(OutputStream os, V value) {
throw new UnsupportedOperationException("Not support write object");
}
}
@@ -1,55 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.conaco;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.streampipe.InputStreamPipe;
public interface ValueHelper<V> {
/**
* Decode object for the {@code InputStreamPipe}
*
* @param isPipe the {@code InputStreamPipe}
* @return the decoded object
*/
@Nullable
V decode(@NonNull InputStreamPipe isPipe);
/**
* Get the size of the object
*/
int sizeOf(@NonNull String key, @NonNull V value);
/**
* Called when the object added to memory cache
*/
void onAddToMemoryCache(@NonNull V oldValue);
/**
* Called when the object removed from memory cache
*/
void onRemoveFromMemoryCache(@NonNull String key, @NonNull V oldValue);
/**
* Use cache memory or not. Sometimes large object
* should not be stored in memory cache.
*/
boolean useMemoryCache(@NonNull String key, V holder);
}
@@ -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;
}
}
@@ -1,215 +0,0 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.drawable;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.annotation.ColorInt;
import androidx.annotation.Keep;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.MathUtils;
/**
* A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate between them.
*/
public class DrawerArrowDrawable extends Drawable {
// The angle in degrees that the arrow head is inclined at.
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
private final Paint mPaint = new Paint();
private final float mBarThickness;
// The length of top and bottom bars when they merge into an arrow
private final float mTopBottomArrowSize;
// The length of middle bar
private final float mBarSize;
// The length of the middle bar when arrow is shaped
private final float mMiddleArrowSize;
// The space between bars when they are parallel
private final float mBarGap;
// Whether bars should spin or not during progress
private final boolean mSpin;
// Use Path instead of canvas operations so that if color has transparency, overlapping sections
// wont look different
private final Path mPath = new Path();
// The reported intrinsic size of the drawable.
private final int mSize;
// the amount that overlaps w/ bar size when rotation is max
private final float mMaxCutForBarSize;
// Whether we should mirror animation when animation is reversed.
private boolean mVerticalMirror = false;
// The interpolated version of the original progress
private float mProgress;
/**
* @param context used to get the configuration for the drawable from
*/
public DrawerArrowDrawable(Context context, int color) {
Resources resources = context.getResources();
mPaint.setAntiAlias(true);
mPaint.setColor(color);
mSize = resources.getDimensionPixelSize(R.dimen.dad_drawable_size);
// round this because having this floating may cause bad measurements
mBarSize = Math.round(resources.getDimension(R.dimen.dad_bar_size));
// round this because having this floating may cause bad measurements
mTopBottomArrowSize = Math.round(resources.getDimension(R.dimen.dad_top_bottom_bar_arrow_size));
mBarThickness = resources.getDimension(R.dimen.dad_thickness);
// round this because having this floating may cause bad measurements
mBarGap = Math.round(resources.getDimension(R.dimen.dad_gap_between_bars));
mSpin = resources.getBoolean(R.bool.dad_spin_bars);
mMiddleArrowSize = resources.getDimension(R.dimen.dad_middle_bar_arrow_size);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.MITER);
mPaint.setStrokeCap(Paint.Cap.BUTT);
mPaint.setStrokeWidth(mBarThickness);
mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE));
}
/**
* If set, canvas is flipped when progress reached to end and going back to start.
*/
protected void setVerticalMirror(boolean verticalMirror) {
mVerticalMirror = verticalMirror;
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
// Interpolated widths of arrow bars
final float arrowSize = MathUtils.lerp(mBarSize, mTopBottomArrowSize, mProgress);
final float middleBarSize = MathUtils.lerp(mBarSize, mMiddleArrowSize, mProgress);
// Interpolated size of middle bar
final float middleBarCut = Math.round(MathUtils.lerp(0, mMaxCutForBarSize, mProgress));
// The rotation of the top and bottom bars (that make the arrow head)
final float rotation = MathUtils.lerp(0, ARROW_HEAD_ANGLE, mProgress);
// The whole canvas rotates as the transition happens
final float canvasRotate = MathUtils.lerp(-180, 0, mProgress);
final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
mPath.rewind();
final float topBottomBarOffset = MathUtils.lerp(mBarGap + mBarThickness, -mMaxCutForBarSize,
mProgress);
final float arrowEdge = -middleBarSize / 2;
// draw middle bar
mPath.moveTo(arrowEdge + middleBarCut, 0);
mPath.rLineTo(middleBarSize - middleBarCut * 2, 0);
// bottom bar
mPath.moveTo(arrowEdge, topBottomBarOffset);
mPath.rLineTo(arrowWidth, arrowHeight);
// top bar
mPath.moveTo(arrowEdge, -topBottomBarOffset);
mPath.rLineTo(arrowWidth, -arrowHeight);
mPath.close();
canvas.save();
// Rotate the whole canvas if spinning, if not, rotate it 180 to get
// the arrow pointing the other way for RTL.
canvas.translate(bounds.centerX(), bounds.centerY());
if (mSpin) {
canvas.rotate(canvasRotate * (mVerticalMirror ? -1 : 1));
}
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
public void setColor(@ColorInt int color) {
mPaint.setColor(color);
invalidateSelf();
}
@Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getIntrinsicHeight() {
return mSize;
}
@Override
public int getIntrinsicWidth() {
return mSize;
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Keep
public float getProgress() {
return mProgress;
}
@Keep
public void setProgress(float progress) {
if (progress == 1f) {
setVerticalMirror(true);
} else if (progress == 0f) {
setVerticalMirror(false);
}
mProgress = progress;
invalidateSelf();
}
public void setMenu(long duration) {
setShape(false, duration);
}
public void setArrow(long duration) {
setShape(true, duration);
}
public void setShape(boolean arrow, long duration) {
if (!((!arrow && mProgress == 0f) || (arrow && mProgress == 1f))) {
float endProgress = arrow ? 1f : 0f;
if (duration <= 0) {
setProgress(endProgress);
} else {
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", endProgress);
oa.setDuration(duration);
oa.setAutoCancel(true);
oa.start();
}
}
}
}
@@ -18,31 +18,23 @@ package com.hippo.drawable;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import androidx.annotation.NonNull;
import com.hippo.conaco.Conaco;
import com.hippo.conaco.ConacoTask;
import com.hippo.conaco.Unikery;
import com.hippo.image.ImageBitmap;
import com.hippo.widget.ObservedTextView;
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
ObservedTextView.OnWindowAttachListener {
import coil.Coil;
import coil.request.Disposable;
import coil.request.ImageRequest;
public class UnikeryDrawable extends WrapDrawable implements ObservedTextView.OnWindowAttachListener {
private static final String TAG = UnikeryDrawable.class.getSimpleName();
private final ObservedTextView mTextView;
private final Conaco<ImageBitmap> mConaco;
private int mTaskId = Unikery.INVALID_ID;
private String mUrl;
private Disposable task;
private ImageBitmap imageBitmap;
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
public UnikeryDrawable(ObservedTextView textView) {
mTextView = textView;
mTextView.setOnWindowAttachListener(this);
mConaco = conaco;
}
@Override
@@ -52,23 +44,29 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
@Override
public void onDetachedFromWindow() {
mConaco.cancel(this);
if (task != null && !task.isDisposed())
task.dispose();
clearDrawable();
}
public void load(String url) {
if (url != null) {
mUrl = url;
mConaco.load(new ConacoTask.Builder<ImageBitmap>().setUnikery(this).setUrl(url).setKey(url));
var imageLoader = Coil.imageLoader(mTextView.getContext());
var request = new ImageRequest.Builder(mTextView.getContext()).data(url).memoryCacheKey(url).diskCacheKey(url).target(
drawable -> null,
drawable -> null,
drawable -> {
onGetValue(drawable);
return null;
}
).build();
task = imageLoader.enqueue(request);
}
}
private void clearDrawable() {
setDrawable(null);
if (imageBitmap != null) {
imageBitmap.release();
imageBitmap = null;
}
}
@Override
@@ -91,65 +89,16 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
}
}
@Override
public int getTaskId() {
return mTaskId;
}
@Override
public void setTaskId(int id) {
mTaskId = id;
}
@Override
public void invalidateSelf() {
CharSequence cs = mTextView.getText();
mTextView.setText(cs);
}
@Override
public void onMiss(int source) {
}
@Override
public void onRequest() {
}
@Override
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {
}
@Override
public void onWait() {
}
@Override
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
Drawable drawable;
try {
drawable = value.getDrawable();
} catch (Exception e) {
Log.d(TAG, "The ImageBitmap is recycled", e);
return false;
}
public void onGetValue(Drawable drawable) {
clearDrawable();
setDrawable(drawable);
imageBitmap = value;
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable)
animatedImageDrawable.start();
return true;
}
@Override
public void onFailure() {
// Empty
}
@Override
public void onCancel() {
// Empty
}
}
@@ -1,586 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Debug;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.collection.LruCache;
import androidx.core.os.LocaleListCompat;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.hippo.Native;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.conaco.Conaco;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhDns;
import com.hippo.ehviewer.client.EhEngine;
import com.hippo.ehviewer.client.EhRequestBuilder;
import com.hippo.ehviewer.client.EhSSLSocketFactory;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhX509TrustManager;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.parser.EventPaneParser;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.EhActivity;
import com.hippo.image.Image;
import com.hippo.image.ImageBitmap;
import com.hippo.network.StatusCodeException;
import com.hippo.scene.SceneApplication;
import com.hippo.unifile.UniFile;
import com.hippo.util.BitmapUtils;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.util.ReadableTime;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IntIdGenerator;
import com.hippo.yorozuya.OSUtils;
import com.hippo.yorozuya.SimpleHandler;
import java.io.File;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class EhApplication extends SceneApplication {
public static final boolean BETA = false;
private static final String TAG = EhApplication.class.getSimpleName();
private static final String KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id";
private static final boolean DEBUG_CONACO = false;
private static final boolean DEBUG_PRINT_NATIVE_MEMORY = false;
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
private static final long DEBUG_PRINT_INTERVAL = 3000L;
public static boolean locked = true;
public static long locked_last_leave_time = 0;
private static EhApplication instance;
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
private final HashMap<Integer, Object> mGlobalStuffMap = new HashMap<>();
private final List<Activity> mActivityList = new ArrayList<>();
private EhCookieStore mEhCookieStore;
private EhClient mEhClient;
private EhProxySelector mEhProxySelector;
private OkHttpClient mOkHttpClient;
private Cache mOkHttpCache;
private ImageBitmapHelper mImageBitmapHelper;
private Conaco<ImageBitmap> mConaco;
private LruCache<Long, GalleryDetail> mGalleryDetailCache;
private SimpleDiskCache mSpiderInfoCache;
private DownloadManager mDownloadManager;
private Hosts mHosts;
private FavouriteStatusRouter mFavouriteStatusRouter;
private boolean initialized = false;
public static EhApplication getInstance() {
return instance;
}
public static EhCookieStore getEhCookieStore(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mEhCookieStore == null) {
application.mEhCookieStore = new EhCookieStore(context);
}
return application.mEhCookieStore;
}
@NonNull
public static EhClient getEhClient(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mEhClient == null) {
application.mEhClient = new EhClient(application);
}
return application.mEhClient;
}
@NonNull
public static EhProxySelector getEhProxySelector(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mEhProxySelector == null) {
application.mEhProxySelector = new EhProxySelector();
}
return application.mEhProxySelector;
}
@NonNull
public static OkHttpClient getOkHttpClient(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mOkHttpClient == null) {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cookieJar(getEhCookieStore(application))
.cache(getOkHttpCache(application))
.dns(new EhDns(application))
.proxySelector(getEhProxySelector(application));
if (Settings.getDF()) {
X509TrustManager trustManager;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager trustManager1)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
trustManager = trustManager1;
} catch (Exception e) {
e.printStackTrace();
trustManager = new EhX509TrustManager();
}
builder.sslSocketFactory(new EhSSLSocketFactory(), trustManager);
}
application.mOkHttpClient = builder.build();
}
return application.mOkHttpClient;
}
@NonNull
public static Cache getOkHttpCache(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mOkHttpCache == null) {
application.mOkHttpCache = new Cache(new File(application.getCacheDir(), "http_cache"), 50L * 1024L * 1024L);
}
return application.mOkHttpCache;
}
@NonNull
public static ImageBitmapHelper getImageBitmapHelper(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mImageBitmapHelper == null) {
application.mImageBitmapHelper = new ImageBitmapHelper();
}
return application.mImageBitmapHelper;
}
private static int getMemoryCacheMaxSize() {
return Math.min(20 * 1024 * 1024, (int) OSUtils.getAppMaxMemory());
}
@NonNull
public static Conaco<ImageBitmap> getConaco(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mConaco == null) {
Conaco.Builder<ImageBitmap> builder = new Conaco.Builder<>();
builder.hasMemoryCache = true;
builder.memoryCacheMaxSize = getMemoryCacheMaxSize();
builder.hasDiskCache = true;
builder.diskCacheDir = new File(context.getCacheDir(), "thumb");
builder.diskCacheMaxSize = 320 * 1024 * 1024; // 320MB
builder.okHttpClient = getOkHttpClient(context);
builder.objectHelper = getImageBitmapHelper(context);
builder.debug = DEBUG_CONACO;
application.mConaco = builder.build();
}
return application.mConaco;
}
@NonNull
public static LruCache<Long, GalleryDetail> getGalleryDetailCache(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mGalleryDetailCache == null) {
// Max size 25, 3 min timeout
application.mGalleryDetailCache = new LruCache<>(25);
getFavouriteStatusRouter().addListener((gid, slot) -> {
GalleryDetail gd = application.mGalleryDetailCache.get(gid);
if (gd != null) {
gd.favoriteSlot = slot;
}
});
}
return application.mGalleryDetailCache;
}
@NonNull
public static SimpleDiskCache getSpiderInfoCache(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (null == application.mSpiderInfoCache) {
application.mSpiderInfoCache = new SimpleDiskCache(
new File(context.getCacheDir(), "spider_info"), 20 * 1024 * 1024); // 20M
}
return application.mSpiderInfoCache;
}
@NonNull
public static DownloadManager getDownloadManager() {
return getDownloadManager(instance);
}
@NonNull
public static DownloadManager getDownloadManager(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mDownloadManager == null) {
application.mDownloadManager = new DownloadManager(application);
}
return application.mDownloadManager;
}
@NonNull
public static Hosts getHosts(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mHosts == null) {
application.mHosts = new Hosts(application, "hosts.db");
}
return application.mHosts;
}
@NonNull
public static FavouriteStatusRouter getFavouriteStatusRouter() {
return getFavouriteStatusRouter(getInstance());
}
@NonNull
public static FavouriteStatusRouter getFavouriteStatusRouter(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mFavouriteStatusRouter == null) {
application.mFavouriteStatusRouter = new FavouriteStatusRouter();
}
return application.mFavouriteStatusRouter;
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
ProcessLifecycleOwner.get().getLifecycle().addObserver(new EhlifecycleObserver());
instance = this;
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
try {
// Always save crash file if onCreate() is not done
if (!initialized || Settings.getSaveCrashLog()) {
Crash.saveCrashLog(instance, e);
}
} catch (Throwable ignored) {
}
if (handler != null) {
handler.uncaughtException(t, e);
}
});
super.onCreate();
Image.initialize(this);
Native.initialize();
GetText.initialize(this);
StatusCodeException.initialize(this);
Settings.initialize(this);
ReadableTime.initialize(this);
AppConfig.initialize(this);
SpiderDen.initialize(this);
EhDB.initialize(this);
EhEngine.initialize();
BitmapUtils.initialize(this);
// Locales can be managed by system automatically above Snow Cone v2
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
// Do io tasks in new thread
//noinspection deprecation
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
// Check no media file
try {
UniFile downloadLocation = Settings.getDownloadLocation();
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation);
} else {
CommonOperations.ensureNoMediaFile(downloadLocation);
}
} catch (Throwable t) {
t.printStackTrace();
ExceptionUtils.throwIfFatal(t);
}
// Clear temp files
try {
clearTempDir();
} catch (Throwable t) {
t.printStackTrace();
ExceptionUtils.throwIfFatal(t);
}
return null;
}
}.executeOnExecutor(IoThreadPoolExecutor.getInstance());
// Check app update
update();
// Update version code
try {
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
Settings.putVersionCode(pi.versionCode);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0));
if (DEBUG_PRINT_NATIVE_MEMORY || DEBUG_PRINT_IMAGE_COUNT) {
debugPrint();
}
initialized = true;
theDawnOfNewDay();
}
private void theDawnOfNewDay() {
if (!Settings.getRequestNews()) {
return;
}
EhCookieStore store = getEhCookieStore(this);
HttpUrl eh = HttpUrl.get(EhUrl.HOST_E);
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(eh, EhCookieStore.KEY_IPD_PASS_HASH)) {
IoThreadPoolExecutor.getInstance().execute(() -> {
String referer = EhUrl.REFERER_E;
Request request = new EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build();
Call call = getOkHttpClient(this).newCall(request);
try {
Response response = call.execute();
ResponseBody responseBody = response.body();
if (responseBody == null) {
return;
}
String body = responseBody.string();
String html = EventPaneParser.parse(body);
if (html != null) {
showEventPane(html);
}
} catch (Throwable e) {
e.printStackTrace();
}
});
}
}
public void showEventPane(String html) {
Activity activity = getTopActivity();
if (activity != null) {
activity.runOnUiThread(() -> {
AlertDialog dialog = new BaseDialogBuilder(activity)
.setMessage(Html.fromHtml(html))
.setPositiveButton(android.R.string.ok, null)
.create();
dialog.setOnShowListener(d -> {
final View messageView = dialog.findViewById(android.R.id.message);
if (messageView instanceof TextView) {
((TextView) messageView).setMovementMethod(LinkMovementMethod.getInstance());
}
});
try {
dialog.show();
} catch (Throwable t) {
// ignore
}
});
}
}
private void clearTempDir() {
File dir = AppConfig.getTempDir();
if (null != dir) {
FileUtils.deleteContent(dir);
}
dir = AppConfig.getExternalTempDir();
if (null != dir) {
FileUtils.deleteContent(dir);
}
}
private void update() {
int version = Settings.getVersionCode();
if (version < 52) {
Settings.putGuideGallery(true);
}
}
public void clearMemoryCache() {
if (null != mConaco) {
mConaco.getBeerBelly().clearMemory();
}
if (null != mGalleryDetailCache) {
mGalleryDetailCache.evictAll();
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
clearMemoryCache();
}
}
private void debugPrint() {
new Runnable() {
@Override
public void run() {
if (DEBUG_PRINT_NATIVE_MEMORY) {
Log.i(TAG, "Native memory: " + FileUtils.humanReadableByteCount(
Debug.getNativeHeapAllocatedSize(), false));
}
SimpleHandler.getInstance().postDelayed(this, DEBUG_PRINT_INTERVAL);
}
}.run();
}
public int putGlobalStuff(@NonNull Object o) {
int id = mIdGenerator.nextId();
mGlobalStuffMap.put(id, o);
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId());
return id;
}
public boolean containGlobalStuff(int id) {
return mGlobalStuffMap.containsKey(id);
}
public Object getGlobalStuff(int id) {
return mGlobalStuffMap.get(id);
}
public Object removeGlobalStuff(int id) {
return mGlobalStuffMap.remove(id);
}
public void removeGlobalStuff(Object o) {
mGlobalStuffMap.values().removeAll(Collections.singleton(o));
}
public void registerActivity(Activity activity) {
mActivityList.add(activity);
}
public void unregisterActivity(Activity activity) {
mActivityList.remove(activity);
}
@Nullable
public EhActivity getTopActivity() {
if (!mActivityList.isEmpty()) {
return (EhActivity) mActivityList.get(mActivityList.size() - 1);
} else {
return null;
}
}
// Avoid crash on some "energy saving" devices
@Override
public ComponentName startService(Intent service) {
try {
return super.startService(service);
} catch (Throwable t) {
t.printStackTrace();
ExceptionUtils.throwIfFatal(t);
return null;
}
}
// Avoid crash on some "energy saving" devices
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
try {
return super.bindService(service, conn, flags);
} catch (Throwable t) {
t.printStackTrace();
ExceptionUtils.throwIfFatal(t);
return false;
}
}
// Avoid crash on some "energy saving" devices
@Override
public void unbindService(ServiceConnection conn) {
try {
super.unbindService(conn);
} catch (Throwable t) {
t.printStackTrace();
ExceptionUtils.throwIfFatal(t);
}
}
// Avoid crash on some "energy saving" devices
@Override
public ComponentName startForegroundService(Intent service) {
try {
return super.startForegroundService(service);
} catch (Throwable t) {
t.printStackTrace();
ExceptionUtils.throwIfFatal(t);
return null;
}
}
static class EhlifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onPause(@NonNull LifecycleOwner owner) {
if (!locked)
locked_last_leave_time = System.currentTimeMillis() / 1000;
locked = true;
}
}
}
@@ -0,0 +1,348 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.content.ComponentCallbacks2
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.LruCache
import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.hippo.Native
import com.hippo.app.BaseDialogBuilder
import com.hippo.beerbelly.SimpleDiskCache
import com.hippo.ehviewer.client.EhClient
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhDns
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhRequestBuilder
import com.hippo.ehviewer.client.EhSSLSocketFactory
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhX509TrustManager
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.parser.EventPaneParser
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.spider.SpiderDen
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.EhActivity
import com.hippo.scene.SceneApplication
import com.hippo.util.BitmapUtils
import com.hippo.util.ExceptionUtils
import com.hippo.util.ReadableTime
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IntIdGenerator
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import java.io.File
import java.net.Proxy
import java.security.KeyStore
import java.util.Arrays
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderFactory {
private val mIdGenerator = IntIdGenerator()
private val mGlobalStuffMap = HashMap<Int, Any>()
private val mActivityList = ArrayList<Activity>()
val topActivity: EhActivity?
get() = if (mActivityList.isNotEmpty()) {
mActivityList[mActivityList.size - 1] as EhActivity
} else {
null
}
fun recreateAllActivity() {
mActivityList.forEach { it.recreate() }
}
@OptIn(DelicateCoroutinesApi::class)
@SuppressLint("StaticFieldLeak")
override fun onCreate() {
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
application = this
val handler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
try {
if (Settings.getSaveCrashLog()) {
Crash.saveCrashLog(application, e)
}
} catch (ignored: Throwable) {
}
handler?.uncaughtException(t, e)
}
super<SceneApplication>.onCreate()
Native.initialize()
GetText.initialize(this)
Settings.initialize()
ReadableTime.initialize(this)
AppConfig.initialize(this)
SpiderDen.initialize(this)
EhDB.initialize(this)
EhEngine.initialize()
BitmapUtils.initialize(this)
EhTagDatabase.update()
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
GlobalScope.launch {
withContext(Dispatchers.IO) {
try {
val downloadLocation = Settings.getDownloadLocation()
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation)
} else {
CommonOperations.ensureNoMediaFile(downloadLocation)
}
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
// Clear temp files
try {
clearTempDir()
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
}
}
GlobalScope.launch {
theDawnOfNewDay()
}
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0))
}
private suspend fun theDawnOfNewDay() {
if (!Settings.getRequestNews()) {
return
}
val store = ehCookieStore
val eh = EhUrl.HOST_E.toHttpUrl()
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(
eh,
EhCookieStore.KEY_IPD_PASS_HASH
)
) {
withContext(Dispatchers.IO) {
val referer = EhUrl.REFERER_E
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
val call = okHttpClient.newCall(request)
try {
call.execute().use { response ->
val responseBody = response.body
val body = responseBody.string()
val html = EventPaneParser.parse(body)
if (html != null) {
showEventPane(html)
}
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}
fun showEventPane(html: String) {
if (Settings.getHideHvEvents() && html.contains("You have encountered a monster!")) {
return
}
val activity = topActivity
activity?.runOnUiThread {
val dialog = BaseDialogBuilder(activity)
.setMessage(Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY))
.setPositiveButton(android.R.string.ok, null)
.create()
dialog.setOnShowListener {
val messageView = dialog.findViewById<View>(android.R.id.message)
if (messageView is TextView) {
messageView.movementMethod = LinkMovementMethod.getInstance()
}
}
try {
dialog.show()
} catch (t: Throwable) {
// ignore
}
}
}
private fun clearTempDir() {
var dir = AppConfig.getTempDir()
if (null != dir) {
FileUtils.deleteContent(dir)
}
dir = AppConfig.getExternalTempDir()
if (null != dir) {
FileUtils.deleteContent(dir)
}
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
galleryDetailCache.evictAll()
}
}
fun putGlobalStuff(o: Any): Int {
val id = mIdGenerator.nextId()
mGlobalStuffMap[id] = o
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId())
return id
}
fun containGlobalStuff(id: Int): Boolean {
return mGlobalStuffMap.containsKey(id)
}
fun getGlobalStuff(id: Int): Any? {
return mGlobalStuffMap[id]
}
fun removeGlobalStuff(id: Int): Any? {
return mGlobalStuffMap.remove(id)
}
fun removeGlobalStuff(o: Any) {
mGlobalStuffMap.values.removeAll(setOf(o))
}
fun registerActivity(activity: Activity) {
mActivityList.add(activity)
}
fun unregisterActivity(activity: Activity) {
mActivityList.remove(activity)
}
override fun onPause(owner: LifecycleOwner) {
if (!locked)
locked_last_leave_time = System.currentTimeMillis() / 1000
locked = true
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this).apply {
okHttpClient(okHttpClient)
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
// Coil spawns a new thread for every image load by default
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8))
decoderDispatcher(Dispatchers.IO.limitedParallelism(2))
transformationDispatcher(Dispatchers.IO.limitedParallelism(2))
}.build()
}
companion object {
private const val KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id"
var locked = true
var locked_last_leave_time: Long = 0
@JvmStatic
lateinit var application: EhApplication
private set
@JvmStatic
val ehCookieStore by lazy { EhCookieStore(application) }
@JvmStatic
val ehClient by lazy { EhClient() }
@JvmStatic
val ehProxySelector by lazy { EhProxySelector() }
@JvmStatic
val okHttpClient by lazy {
val builder = OkHttpClient.Builder()
.cookieJar(ehCookieStore)
.cache(Cache(File(application.cacheDir, "http_cache"), 50L * 1024L * 1024L))
.dns(EhDns)
.proxySelector(ehProxySelector)
if (Settings.getDF()) {
var trustManager: X509TrustManager
try {
val trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
trustManagerFactory.init(null as KeyStore?)
val trustManagers = trustManagerFactory.trustManagers
check(!(trustManagers.size != 1 || trustManagers[0] !is X509TrustManager)) {
"Unexpected default trust managers:" + Arrays.toString(
trustManagers
)
}
trustManager = trustManagers[0] as X509TrustManager
} catch (e: Exception) {
e.printStackTrace()
trustManager = EhX509TrustManager
}
builder.sslSocketFactory(EhSSLSocketFactory, trustManager)
builder.proxy(Proxy.NO_PROXY)
}
builder.build()
}
@JvmStatic
val galleryDetailCache by lazy {
LruCache<Long, GalleryDetail>(25).also {
favouriteStatusRouter.addListener { gid, slot ->
it[gid]?.favoriteSlot = slot
}
}
}
@JvmStatic
val spiderInfoCache by lazy {
SimpleDiskCache(
File(application.cacheDir, "spider_info"),
20 * 1024 * 1024
)
}
@JvmStatic
val downloadManager by lazy { DownloadManager(application) }
@JvmStatic
val hosts by lazy { Hosts(application, "hosts.db") }
@JvmStatic
val favouriteStatusRouter by lazy { FavouriteStatusRouter() }
}
}
+35 -35
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);
}
}
@@ -398,11 +390,10 @@ public class EhDB {
db.filterDao().update(filter);
}
private static <T> boolean copyDao(BasicDao<T> from, BasicDao<T> to) {
private static <T> void copyDao(BasicDao<T> from, BasicDao<T> to) {
List<T> list = from.list();
for (T item : list)
to.insert(item);
return false;
}
public static synchronized boolean exportDB(Context context, Uri uri) {
@@ -414,20 +405,18 @@ public class EhDB {
try {
// Copy data to a export db
if (copyDao(db.downloadsDao(), newDb.downloadsDao()))
return false;
if (copyDao(db.downloadLabelDao(), newDb.downloadLabelDao()))
return false;
if (copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao()))
return false;
if (copyDao(db.historyDao(), newDb.historyDao()))
return false;
if (copyDao(db.quickSearchDao(), newDb.quickSearchDao()))
return false;
if (copyDao(db.localFavoritesDao(), newDb.localFavoritesDao()))
return false;
if (copyDao(db.filterDao(), newDb.filterDao()))
return false;
copyDao(db.downloadsDao(), newDb.downloadsDao());
copyDao(db.downloadLabelDao(), newDb.downloadLabelDao());
copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao());
copyDao(db.historyDao(), newDb.historyDao());
copyDao(db.quickSearchDao(), newDb.quickSearchDao());
copyDao(db.localFavoritesDao(), newDb.localFavoritesDao());
copyDao(db.filterDao(), newDb.filterDao());
// Close export db so we can copy it
if (newDb.isOpen()) {
newDb.close();
}
// Copy export db to data dir
File dbFile = context.getDatabasePath(ehExportName);
@@ -447,6 +436,7 @@ public class EhDB {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
// Delete failed file
return false;
} finally {
@@ -460,6 +450,8 @@ public class EhDB {
public static synchronized String importDB(Context context, Uri uri) {
try {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
// Copy file to cache dir
File file = File.createTempFile("importDatabase", "");
FileOutputStream outputStream = new FileOutputStream(file);
byte[] buff = new byte[1024];
@@ -474,6 +466,7 @@ public class EhDB {
inputStream.close();
outputStream.close();
// Check database version
SQLiteDatabase oldDB = SQLiteDatabase.openDatabase(
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
int newVersion = CUR_DB_VER;
@@ -482,16 +475,17 @@ public class EhDB {
upgradeDB(oldDB, oldVersion);
oldDB.setVersion(newVersion);
} else if (oldVersion > newVersion) {
return context.getString(R.string.cant_read_the_file);
return context.getString(R.string.db_version_too_high);
}
// Crete temp room database from cache file
String tmpDBName = "tmp.db";
context.deleteDatabase(tmpDBName);
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
.createFromFile(file).allowMainThreadQueries().build();
// Download label
DownloadManager manager = EhApplication.getDownloadManager(context);
DownloadManager manager = EhApplication.getDownloadManager();
try {
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
manager.addDownloadLabel(downloadLabelList);
@@ -565,6 +559,12 @@ public class EhDB {
} catch (Exception ignored) {
}
// Delete temp database
if (oldRoomDatabase.isOpen()) {
oldRoomDatabase.close();
}
context.deleteDatabase(tmpDBName);
return null;
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
@@ -1,72 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.conaco.ValueHelper;
import com.hippo.image.ImageBitmap;
import com.hippo.streampipe.InputStreamPipe;
import java.io.FileInputStream;
import java.io.IOException;
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
private static final int MAX_CACHE_SIZE = 512 * 512;
@Nullable
@Override
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
try {
isPipe.obtain();
FileInputStream is = (FileInputStream) isPipe.open();
return ImageBitmap.decode(is);
} catch (OutOfMemoryError | IOException e) {
return null;
} finally {
isPipe.close();
isPipe.release();
}
}
@Override
public int sizeOf(@NonNull String key, @NonNull ImageBitmap value) {
return value.getWidth() * value.getHeight() * 4 /* value.getByteCount() TODO Update Image */;
}
@Override
public void onAddToMemoryCache(@NonNull ImageBitmap oldValue) {
oldValue.obtain();
}
@Override
public void onRemoveFromMemoryCache(@NonNull String key, @NonNull ImageBitmap oldValue) {
oldValue.release();
}
@Override
public boolean useMemoryCache(@NonNull String key, ImageBitmap value) {
if (value != null) {
return value.getWidth() * value.getHeight() <= MAX_CACHE_SIZE
/* value.getByteCount() <= MAX_CACHE_BYTE_COUNT TODO Update Image */;
} else {
return true;
}
}
}
@@ -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,24 +46,17 @@ 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";
public static final String KEY_THUMB_SIZE = "thumb_size";
public static final String KEY_THUMB_SIZE = "thumb_size_";
public static final String KEY_THUMB_RESOLUTION = "thumb_resolution";
private static final String KEY_SHOW_COMMENTS = "show_gallery_comments";
private static final boolean DEFAULT_SHOW_COMMENTS = true;
public static final String KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
public static final int DEFAULT_DEFAULT_CATEGORIES = EhUtils.ALL_CATEGORY;
public static final int DEFAULT_DEFAULT_CATEGORIES = EhConfig.ALL_CATEGORY;
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
private static final String KEY_PREVIEW_NUM = "preview_num";
private static final int DEFAULT_PREVIEW_NUM = 60;
/********************
****** Privacy and Security
********************/
@@ -82,7 +68,6 @@ public class Settings {
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
public static final String KEY_NOTIFICATION_REQUIRED = "notification_required";
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
@@ -90,47 +75,41 @@ 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;
private static final int DEFAULT_LIST_MODE = 0;
private static final int DEFAULT_DETAIL_SIZE = 0;
private static final int DEFAULT_THUMB_SIZE = 1;
private static final int DEFAULT_THUMB_SIZE = 120;
private static final int DEFAULT_THUMB_RESOLUTION = 0;
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
@@ -143,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;
@@ -191,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
********************/
@@ -234,47 +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 boolean DEFAULT_BETA_UPDATE_CHANNEL = EhApplication.BETA;
private static final String KEY_SKIP_UPDATE_VERSION = "skip_update_version";
private static final int DEFAULT_SKIP_UPDATE_VERSION = 0;
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = true;
private static final 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";
@@ -283,19 +195,17 @@ public class Settings {
private static final boolean DEFAULT_REQUEST_NEWS = true;
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
public static boolean LIST_THUMB_SIZE_INITED = false;
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 +222,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 +258,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);
@@ -425,14 +299,6 @@ public class Settings {
sSettingsPre.edit().putString(key, Integer.toString(value)).apply();
}
public static int getVersionCode() {
return getInt(KEY_VERSION_CODE, DEFAULT_VERSION_CODE);
}
public static void putVersionCode(int value) {
putInt(KEY_VERSION_CODE, value);
}
@Nullable
public static String getDisplayName() {
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
@@ -442,22 +308,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);
}
@@ -490,14 +344,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);
}
@@ -508,17 +354,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() {
@@ -531,30 +373,20 @@ 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() {
return getIntFromStr(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE);
return dip2px(getInt(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE));
}
@DimenRes
public static int getThumbSizeResId() {
switch (getThumbSize()) {
case 0:
return R.dimen.gallery_grid_column_width_large;
default:
case 1:
return R.dimen.gallery_grid_column_width_middle;
case 2:
return R.dimen.gallery_grid_column_width_small;
}
public static int dip2px(int dpValue) {
final float scale = EhApplication.getApplication().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public static int getThumbResolution() {
@@ -569,6 +401,10 @@ public class Settings {
return getBoolean(KEY_REQUEST_NEWS, DEFAULT_REQUEST_NEWS);
}
public static boolean getHideHvEvents() {
return getBoolean(KEY_HIDE_HV_EVENTS, DEFAULT_HIDE_HV_EVENTS);
}
public static boolean getShowJpnTitle() {
return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE);
}
@@ -593,32 +429,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);
}
@@ -631,152 +449,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());
}
@@ -827,26 +513,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);
}
@@ -861,10 +535,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);
@@ -959,104 +629,10 @@ public class Settings {
putInt(KEY_DEFAULT_FAV_SLOT, value);
}
public static boolean getAskAnalytics() {
return getBoolean(KEY_ASK_ANALYTICS, DEFAULT_ASK_ANALYTICS);
}
public static void putAskAnalytics(boolean value) {
putBoolean(KEY_ASK_ANALYTICS, value);
}
public static boolean getEnableAnalytics() {
return getBoolean(KEY_ENABLE_ANALYTICS, DEFAULT_ENABLE_ANALYTICS);
}
public static void putEnableAnalytics(boolean value) {
putBoolean(KEY_ENABLE_ANALYTICS, value);
}
public static String getUserID() {
boolean writeXml = false;
boolean writeFile = false;
String userID = getString(KEY_USER_ID, null);
File file = AppConfig.getFileInExternalAppDir(FILENAME_USER_ID);
if (!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 boolean getBetaUpdateChannel() {
return getBoolean(KEY_BETA_UPDATE_CHANNEL, DEFAULT_BETA_UPDATE_CHANNEL);
}
public static void putBetaUpdateChannel(boolean value) {
putBoolean(KEY_BETA_UPDATE_CHANNEL, value);
}
public static int getSkipUpdateVersion() {
return getInt(KEY_SKIP_UPDATE_VERSION, DEFAULT_SKIP_UPDATE_VERSION);
}
public static void putSkipUpdateVersion(int value) {
putInt(KEY_SKIP_UPDATE_VERSION, value);
}
public static boolean getSaveParseErrorBody() {
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);
}
@@ -1073,10 +649,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);
}
@@ -1097,14 +669,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);
}
@@ -1129,46 +693,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(context);
}
@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);
}
}
@@ -15,7 +15,6 @@
*/
package com.hippo.ehviewer.client
import android.content.Context
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.Hosts
import com.hippo.ehviewer.Settings
@@ -23,8 +22,8 @@ import okhttp3.Dns
import java.net.InetAddress
import java.net.UnknownHostException
class EhDns(context: Context) : Dns {
private val hosts = EhApplication.getHosts(context)
object EhDns : Dns {
private val hosts = EhApplication.hosts
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
init {
@@ -43,7 +42,7 @@ class EhDns(context: Context) : 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,14 +351,13 @@ 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();
String html = EventPaneParser.parse(body);
if (html != null) {
EhApplication.getInstance().showEventPane(html);
EhApplication.getApplication().showEventPane(html);
}
return GalleryDetailParser.parse(body);
} catch (Throwable e) {
@@ -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();
@@ -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(context);
// 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;
@@ -32,10 +31,8 @@ import java.util.regex.Pattern;
public class EhUtils {
public static final int NONE = -1; // Use it for homepage
public static final int UNKNOWN = 0x400;
public static final int ALL_CATEGORY = EhUtils.UNKNOWN - 1;
//DOUJINSHI|MANGA|ARTIST_CG|GAME_CG|WESTERN|NON_H|IMAGE_SET|COSPLAY|ASIAN_PORN|MISC;
public static final int PRIVATE = 0x400;
public static final int UNKNOWN = 0x800;
public static final int BG_COLOR_DOUJINSHI = 0xfff44336;
public static final int BG_COLOR_MANGA = 0xffff9800;
@@ -51,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 = {
@@ -68,6 +65,7 @@ public class EhUtils {
EhConfig.ASIAN_PORN,
EhConfig.NON_H,
EhConfig.WESTERN,
PRIVATE,
UNKNOWN};
private static final String[][] CATEGORY_STRINGS = {
@@ -81,6 +79,7 @@ public class EhUtils {
new String[]{"asianporn", "Asian Porn"},
new String[]{"non-h"},
new String[]{"western"},
new String[]{"private"},
new String[]{"unknown"}
};
@@ -106,41 +105,30 @@ 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) {
EhApplication.getEhCookieStore(context).signOut();
public static void signOut() {
EhApplication.getEhCookieStore().signOut();
Settings.putAvatar(null);
Settings.putDisplayName(null);
Settings.putNeedSignIn(true);
}
public static boolean needSignedIn(Context context) {
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore(context).hasSignedIn();
public static boolean needSignedIn() {
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore().hasSignedIn();
}
public static String getSuitableTitle(GalleryInfo gi) {
@@ -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) {
}
@@ -19,6 +19,7 @@ package com.hippo.ehviewer.client.parser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryCommentList;
@@ -67,6 +68,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 +80,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 +119,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;
}
@@ -178,7 +206,7 @@ public class GalleryDetailParser {
// Uploader
Element gdn = gm.getElementById("gdn");
if (null != gdn) {
gd.disowned = "opacity:0.5".equals(gdn.attr("style"));
gd.disowned = gdn.attr("style").contains("opacity:0.5");
gd.uploader = StringUtils.trim(gdn.text());
} else {
gd.uploader = "";
@@ -191,14 +219,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 +253,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);
@@ -24,7 +24,6 @@ import androidx.annotation.NonNull;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.data.GalleryTagGroup;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.JsoupUtils;
@@ -67,16 +66,6 @@ public class GalleryListParser {
new String[]{"224", "128", "224"},
};
private static int parsePages(Document d, String body) throws ParseException {
try {
Elements es = d.getElementsByClass("ptt").first().child(0).child(0).children();
return Integer.parseInt(es.get(es.size() - 2).text().trim());
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
throw new ParseException("Can't parse gallery list pages", body);
}
}
private static String parseRating(String ratingStyle) {
Matcher m = PATTERN_RATING.matcher(ratingStyle);
int num1 = Integer.MIN_VALUE;
@@ -147,23 +136,21 @@ public class GalleryListParser {
children = child.children();
}
gi.title = child.text().trim();
Element tbody = JsoupUtils.getElementByTag(glname, "tbody");
if (tbody != null) {
ArrayList<String> tags = new ArrayList<>();
GalleryTagGroup[] groups = GalleryDetailParser.parseTagGroups(tbody.children());
for (GalleryTagGroup group : groups) {
for (int j = 0; j < group.size(); j++) {
tags.add(group.groupName + ":" + group.getTagAt(j));
}
}
gi.simpleTags = tags.toArray(new String[tags.size()]);
}
}
if (gi.title == null) {
return null;
}
// Tags
Elements gts = e.select(".gt, .gtl");
if (gts.size() != 0) {
ArrayList<String> tags = new ArrayList<>();
for (Element gt : gts) {
tags.add(gt.attr("title"));
}
gi.simpleTags = tags.toArray(new String[0]);
}
// Category
gi.category = EhUtils.UNKNOWN;
Element ce = JsoupUtils.getElementByClass(e, "cn");
@@ -267,10 +254,12 @@ public class GalleryListParser {
if (children.size() > uploaderIndex) {
Element div = children.get(uploaderIndex);
if (div != null) {
gi.disowned = "opacity:0.5".equals(div.attr("style"));
gi.disowned = div.attr("style").contains("opacity:0.5");
Element a = div.children().first();
if (a != null) {
gi.uploader = a.text().trim();
} else {
gi.uploader = div.text().trim();
}
}
}
@@ -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() {
@@ -67,7 +67,7 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
.setName(getString(R.string.download_service))
.build()
)
mDownloadManager = EhApplication.getDownloadManager(applicationContext)
mDownloadManager = EhApplication.downloadManager
mDownloadManager!!.setDownloadListener(this)
ensureDownloadingBuilder()
mDownloadingBuilder!!.setContentTitle(getString(R.string.download_service))
@@ -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,7 +335,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
Image image = null;
if (buffer != null) {
try {
image = Image.decode(buffer, false, () -> {
image = Image.decode(buffer, () -> {
archiveAccessor.releaseByteBuffer(buffer);
return null;
});
@@ -347,7 +347,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
if (image != null) {
notifyPageSucceed(index, image);
} else {
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
notifyPageFailed(index);
}
}
}
@@ -24,7 +24,6 @@ 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;
@@ -44,9 +43,11 @@ import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
private static final String TAG = DirGalleryProvider.class.getSimpleName();
public class DirPageLoader extends PageLoader2 implements Runnable {
private static final String TAG = DirPageLoader.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static final FilenameFilter imageFilter =
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
@@ -60,21 +61,19 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
};
private final UniFile mDir;
private final Stack<Integer> mRequests = new Stack<>();
private final AtomicInteger mDecodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private final AtomicInteger mDecodingIndex = new AtomicInteger(-1);
private final AtomicReference<UniFile[]> mFileList = new AtomicReference<>();
@Nullable
private Thread mBgThread;
private volatile int mSize = STATE_WAIT;
private volatile int mSize = PageLoader.STATE_WAIT;
private String mError;
public DirGalleryProvider(@NonNull UniFile dir) {
public DirPageLoader(@NonNull UniFile dir) {
mDir = dir;
}
@Override
public void start() {
super.start();
mBgThread = new PriorityThread(this, TAG + '-' + sIdGenerator.incrementAndGet(),
Process.THREAD_PRIORITY_BACKGROUND);
mBgThread.start();
@@ -83,7 +82,6 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
@Override
public void stop() {
super.stop();
if (mBgThread != null) {
mBgThread.interrupt();
mBgThread = null;
@@ -213,7 +211,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
UniFile[] files = mDir.listFiles(imageFilter);
if (files == null) {
mSize = STATE_ERROR;
mSize = PageLoader.STATE_ERROR;
mError = GetText.getString(R.string.error_not_folder_path);
// Notify to to show error
@@ -251,24 +249,24 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
// Check index valid
if (index < 0 || index >= files.length) {
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
mDecodingIndex.lazySet(-1);
notifyPageFailed(index);
continue;
}
InputStream is = null;
try {
is = files[index].openInputStream();
Image image = Image.decode((FileInputStream) is, false);
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
Image image = Image.decode((FileInputStream) is);
mDecodingIndex.lazySet(-1);
notifyPageSucceed(index, image);
} catch (IOException e) {
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_reading_failed));
mDecodingIndex.lazySet(-1);
notifyPageFailed(index);
} finally {
IOUtils.closeQuietly(is);
}
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
mDecodingIndex.lazySet(-1);
}
// Clear file list
@@ -23,37 +23,37 @@ 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) {
public EhPageLoader(Context context, GalleryInfo galleryInfo) {
mContext = context;
mGalleryInfo = galleryInfo;
}
@Override
public void start() {
super.start();
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mContext, 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 +120,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 +145,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 +187,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 +208,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 = {
@@ -40,7 +40,7 @@ class AccountPreference @JvmOverloads constructor(
private var message: String? = null
init {
val store = EhApplication.getEhCookieStore(context)
val store = EhApplication.ehCookieStore
val eCookies = store.getCookies(EhUrl.HOST_E.toHttpUrl())
val exCookies = store.getCookies(EhUrl.HOST_EX.toHttpUrl())
val cookies: MutableList<Cookie> = LinkedList(eCookies)
@@ -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)
}
}
@@ -55,7 +55,7 @@ public class CleanRedundancyPreference extends TaskPreference {
public ClearTask(@NonNull Context context) {
super(context);
mManager = EhApplication.getDownloadManager(context.getApplicationContext());
mManager = EhApplication.getDownloadManager();
}
// True for cleared
@@ -161,7 +161,7 @@ class ProxyPreference @JvmOverloads constructor(
Settings.putProxyIp(ip)
Settings.putProxyPort(port)
updateSummary(type, ip, port)
EhApplication.getEhProxySelector(getContext()).updateProxy()
EhApplication.ehProxySelector.updateProxy()
dialog.dismiss()
}
}
@@ -74,8 +74,8 @@ public class RestoreDownloadPreference extends TaskPreference {
public RestoreTask(@NonNull Context context) {
super(context);
mManager = EhApplication.getDownloadManager(context.getApplicationContext());
mHttpClient = EhApplication.getOkHttpClient(context.getApplicationContext());
mManager = EhApplication.getDownloadManager();
mHttpClient = EhApplication.getOkHttpClient();
}
private RestoreItem getRestoreItem(UniFile file) {
@@ -58,7 +58,7 @@ public class SignInRequiredPreference extends Preference implements Preference.O
@Override
public boolean onPreferenceClick(Preference preference) {
EhCookieStore store = EhApplication.getEhCookieStore(getContext());
EhCookieStore store = EhApplication.getEhCookieStore();
HttpUrl e = HttpUrl.get(EhUrl.HOST_E);
HttpUrl ex = HttpUrl.get(EhUrl.HOST_EX);
@@ -28,7 +28,7 @@ import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.ehviewer.client.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) {
@@ -179,10 +179,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 +261,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();
@@ -48,9 +48,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;
@@ -85,6 +83,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@@ -157,9 +156,9 @@ 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) {
mHttpClient = EhApplication.getOkHttpClient(application);
mSpiderInfoCache = EhApplication.getSpiderInfoCache(application);
private SpiderQueen(@NonNull GalleryInfo galleryInfo) {
mHttpClient = EhApplication.getOkHttpClient();
mSpiderInfoCache = EhApplication.getSpiderInfoCache();
mGalleryInfo = galleryInfo;
mSpiderDen = new SpiderDen(mGalleryInfo);
@@ -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,
@@ -183,8 +182,8 @@ public final class SpiderQueen implements Runnable {
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
if (queen == null) {
EhApplication application = (EhApplication) context.getApplicationContext();
queen = new SpiderQueen(application, galleryInfo);
context.getApplicationContext();
queen = new SpiderQueen(galleryInfo);
sQueenMap.put(galleryInfo.gid, queen);
// Set mode
queen.setMode(mode);
@@ -358,12 +357,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 +370,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 +398,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 +418,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 +514,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 +632,6 @@ public final class SpiderQueen implements Runnable {
return null;
}
OutputStream os = null;
try {
pipe.obtain();
@@ -661,7 +647,6 @@ public final class SpiderQueen implements Runnable {
} finally {
pipe.close();
pipe.release();
IOUtils.closeQuietly(os);
}
}
@@ -751,14 +736,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 +761,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 +807,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,7 +840,7 @@ 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
}
@@ -1150,7 +1132,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;
@@ -1284,12 +1266,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 +1273,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 +1551,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 +1572,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 +1595,7 @@ public final class SpiderQueen implements Runnable {
private void resetDecodeIndex() {
synchronized (mDecodeRequestQueue) {
mDecodeIndexArray[mThreadIndex] = GalleryPageView.INVALID_INDEX;
mDecodeIndexArray[mThreadIndex] = -1;
}
}
@@ -1679,7 +1655,7 @@ public final class SpiderQueen implements Runnable {
if (is != null) {
try {
image = Image.decode(((FileInputStream) is), false);
image = Image.decode(((FileInputStream) is));
} catch (ImageDecoder.DecodeException e) {
e.printStackTrace();
} finally {
@@ -53,7 +53,7 @@ public final class CommonOperations {
EhDB.putLocalFavorites(galleryInfo);
listener.onSuccess(null);
} else if (slot >= 0 && slot <= 9) {
EhClient client = EhApplication.getEhClient(activity);
EhClient client = EhApplication.getEhClient();
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
request.setArgs(galleryInfo.gid, galleryInfo.token, slot, "");
@@ -100,7 +100,7 @@ public final class CommonOperations {
public static void removeFromFavorites(Activity activity, GalleryInfo galleryInfo,
final EhClient.Callback<Void> listener) {
EhDB.removeLocalFavorites(galleryInfo.gid);
EhClient client = EhApplication.getEhClient(activity);
EhClient client = EhApplication.getEhClient();
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
request.setArgs(galleryInfo.gid, galleryInfo.token, -1, "");
@@ -114,13 +114,13 @@ public final class CommonOperations {
public static void startDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Objects.requireNonNull(EhApplication.getInstance().getTopActivity()).checkAndRequestNotificationPermission();
Objects.requireNonNull(EhApplication.getApplication().getTopActivity()).checkAndRequestNotificationPermission();
}
doStartDownload(activity, galleryInfos, forceDefault);
}
private static void doStartDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
final DownloadManager dm = EhApplication.getDownloadManager(activity);
final DownloadManager dm = EhApplication.getDownloadManager();
LongList toStart = new LongList();
List<GalleryInfo> toAdd = new ArrayList<>();
@@ -30,8 +30,8 @@ import androidx.annotation.RequiresApi
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhApplication.locked_last_leave_time
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import rikka.core.res.resolveColor
@@ -70,6 +70,8 @@ abstract class EhActivity : AppCompatActivity() {
WindowInsetsHelper.LISTENER
)
super.onCreate(savedInstanceState)
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
window.decorView.post {
val rootWindowInsets = window.decorView.rootWindowInsets
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
@@ -91,7 +93,7 @@ abstract class EhActivity : AppCompatActivity() {
override fun onResume() {
val locked_resume_time = System.currentTimeMillis() / 1000
val locked_delay_time = locked_resume_time - locked_last_leave_time
val locked_delay_time = locked_resume_time - EhApplication.locked_last_leave_time
if (locked_delay_time < Settings.getSecurityDelay() * 60) {
EhApplication.locked = false
} else if (Settings.getSecurity() && isAuthenticationSupported() && EhApplication.locked) {
File diff suppressed because it is too large. Load diff
@@ -15,12 +15,10 @@
*/
package com.hippo.ehviewer.ui
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -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());
@@ -309,8 +309,6 @@ public final class MainActivity extends StageActivity
} else {
onRestore(savedInstanceState);
}
EhTagDatabase.update(this);
}
@@ -45,39 +45,68 @@ class SecurityActivity : AppCompatActivity() {
onSuccess()
}
@Synchronized
private fun startAuthentication(
title: String,
subtitle: String? = null,
confirmationRequired: Boolean = true,
) {
if (isAuthenticating) return
isAuthenticating = true
startClass2BiometricOrCredentialAuthentication(
title = title,
subtitle = subtitle,
confirmationRequired = confirmationRequired,
executor = ContextCompat.getMainExecutor(this),
callback = AuthenticationCallback()
callback = callback(
{ _, _, _ -> onFailed() },
{ _, _ -> onSuccess() },
{ onFailed() }
)
)
}
inner class AuthenticationCallback : AuthPromptCallback() {
override fun onAuthenticationSucceeded(
inline fun callback(
crossinline onAuthenticationError: (
activity: FragmentActivity?,
errorCode: Int,
errString: CharSequence
) -> Unit,
crossinline onAuthenticationSucceeded: (
activity: FragmentActivity?,
result: BiometricPrompt.AuthenticationResult
) {
onSuccess()
}
) -> Unit,
crossinline onAuthenticationFailed: (
activity: FragmentActivity?
) -> Unit
) = object : AuthPromptCallback() {
override fun onAuthenticationError(
activity: FragmentActivity?,
errorCode: Int,
errString: CharSequence
) {
moveTaskToBack(true)
}
) = onAuthenticationError(activity, errorCode, errString)
override fun onAuthenticationSucceeded(
activity: FragmentActivity?,
result: BiometricPrompt.AuthenticationResult
) = onAuthenticationSucceeded(activity, result)
override fun onAuthenticationFailed(activity: FragmentActivity?) =
onAuthenticationFailed(activity)
}
private fun onSuccess() {
isAuthenticating = false
EhApplication.locked = false
finish()
}
private fun onFailed() {
moveTaskToBack(true)
isAuthenticating = false
}
companion object {
private var isAuthenticating = 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,6 +16,7 @@
package com.hippo.ehviewer.ui.fragment;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
@@ -70,7 +71,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 +108,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 +292,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) {
@@ -305,43 +306,42 @@ public class AdvancedFragment extends BasePreferenceFragment {
}
private void backupFavorite() {
EhClient mClient = EhApplication.getEhClient(requireContext());
EhClient mClient = EhApplication.getEhClient();
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
favTotal = 0;
favIndex = 1;
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.nextPage != null) {
try {
Thread.sleep(Settings.getDownloadDelay());
} catch (InterruptedException e) {
e.printStackTrace();
}
favIndex++;
favListUrlBuilder.setNext(favResult.nextPage);
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
favListUrlBuilder.setNext(result.nextPage);
request.setArgs(favListUrlBuilder.build());
mClient.execute(request);
} else {
showTip(R.string.settings_advanced_backup_favorite_success, BaseScene.LENGTH_SHORT);
@@ -363,7 +363,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
}
});
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
request.setArgs(favListUrlBuilder.build());
mClient.execute(request);
}
@@ -372,7 +372,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));
}
@@ -19,7 +19,9 @@ import android.app.Activity
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.ListPreference
import androidx.preference.Preference
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhTagDatabase
@@ -35,6 +37,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 }
@@ -46,7 +49,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!!)
}
@@ -74,11 +80,11 @@ 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) {
requireActivity().recreate()
EhApplication.application.recreateAllActivity()
}
return true
}
@@ -43,7 +43,7 @@ class HostsFragment : BaseFragment(), View.OnClickListener {
private var adapter: HostsAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
EhApplication.getHosts(requireContext()).apply {
EhApplication.hosts.apply {
hosts = this
data = all
}
@@ -84,7 +84,7 @@ public class MyTagsFragment extends BaseFragment {
// Copy cookies from okhttp cookie store to CookieManager
url = EhUrl.getMyTagsUrl();
EhCookieStore store = EhApplication.getEhCookieStore(requireContext());
EhCookieStore store = EhApplication.getEhCookieStore();
for (Cookie cookie : store.getCookies(HttpUrl.parse(url))) {
cookieManager.setCookie(url, cookie.toString());
}
@@ -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;
}
}
@@ -91,7 +91,7 @@ public class UConfigFragment extends BaseFragment {
// Copy cookies from okhttp cookie store to CookieManager
url = EhUrl.getUConfigUrl();
EhCookieStore store = EhApplication.getEhCookieStore(requireContext());
EhCookieStore store = EhApplication.getEhCookieStore();
for (Cookie cookie : store.getCookies(HttpUrl.parse(url))) {
cookieManager.setCookie(url, cookie.toString());
}
@@ -142,7 +142,7 @@ public class UConfigFragment extends BaseFragment {
String cookiesString = cookieManager.getCookie(url);
if (cookiesString != null && !cookiesString.isEmpty()) {
EhCookieStore store = EhApplication.getEhCookieStore(requireContext());
EhCookieStore store = EhApplication.getEhCookieStore();
HttpUrl eUrl = HttpUrl.parse(EhUrl.HOST_E);
HttpUrl exUrl = HttpUrl.parse(EhUrl.HOST_EX);
if (eUrl == null || exUrl == null) {
@@ -200,11 +200,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
@@ -18,4 +18,9 @@ abstract class CollapsingToolbarScene : ToolbarScene() {
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
return view.apply { addView(contentView, 0) }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mAppBarLayout?.setExpanded(false, true)
}
}
@@ -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,9 +207,9 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
return;
}
EhUtils.signOut(context);
EhUtils.signOut();
EhCookieStore store = EhApplication.getEhCookieStore(context);
EhCookieStore store = EhApplication.getEhCookieStore();
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E));
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_EX));
store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, 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();
}
}
}
@@ -70,7 +70,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 +88,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")
@@ -162,7 +162,7 @@ public class DownloadsScene extends ToolbarScene
if (context == null) {
return;
}
List<DownloadLabel> listLabel = EhApplication.getDownloadManager(context).getLabelList();
List<DownloadLabel> listLabel = EhApplication.getDownloadManager().getLabelList();
mLabels = new ArrayList<>(listLabel.size() + 1);
// Add default label name
mLabels.add(getString(R.string.default_download_label_name));
@@ -214,7 +214,7 @@ public class DownloadsScene extends ToolbarScene
Context context = getContext();
AssertUtils.assertNotNull(context);
mDownloadManager = EhApplication.getDownloadManager(context);
mDownloadManager = EhApplication.getDownloadManager();
mDownloadManager.addDownloadInfoListener(this);
if (savedInstanceState == null) {
@@ -233,7 +233,7 @@ public class DownloadsScene extends ToolbarScene
if (null == manager) {
Context context = getContext();
if (null != context) {
manager = EhApplication.getDownloadManager(context);
manager = EhApplication.getDownloadManager();
}
} else {
mDownloadManager = null;
@@ -316,7 +316,7 @@ public class DownloadsScene extends ToolbarScene
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
// Workaround
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
container.addView(mFabLayout);
mTip = (TextView) ViewUtils.$$(view, R.id.tip);
@@ -394,7 +394,7 @@ public class DownloadsScene extends ToolbarScene
}
if (null != mFabLayout) {
removeAboveSnackView(mFabLayout);
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
mFabLayout = null;
}
@@ -596,9 +596,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;
}
@@ -697,7 +697,7 @@ public class DownloadsScene extends ToolbarScene
break;
}
case 4: {// Move
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager(context).getLabelList();
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager().getLabelList();
List<String> labelList = new ArrayList<>(labelRawList.size() + 1);
labelList.add(getString(R.string.default_download_label_name));
for (int i = 0, n = labelRawList.size(); i < n; i++) {
@@ -906,7 +906,7 @@ public class DownloadsScene extends ToolbarScene
String label = mLabels.get(position);
if (mDownloadManager == null) {
if (context != null) {
mDownloadManager = EhApplication.getDownloadManager(context);
mDownloadManager = EhApplication.getDownloadManager();
}
}
List<DownloadInfo> list = null;
@@ -1040,7 +1040,7 @@ public class DownloadsScene extends ToolbarScene
} else {
label = mLabels[which];
}
EhApplication.getDownloadManager(context).changeLabel(mDownloadInfoList, label);
EhApplication.getDownloadManager().changeLabel(mDownloadInfoList, label);
if (mLabelAdapter != null) {
mLabelAdapter.notifyDataSetChanged();
}
@@ -1162,7 +1162,7 @@ public class DownloadsScene extends ToolbarScene
return;
}
DownloadInfo info = mList.get(position);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb, true);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb);
holder.title.setText(EhUtils.getSuitableTitle(info));
holder.uploader.setText(info.uploader);
holder.rating.setRating(info.rating);
@@ -1246,12 +1246,12 @@ public class DownloadsScene extends ToolbarScene
mBuilder.setError(getString(R.string.label_text_is_empty));
} else if (getString(R.string.default_download_label_name).equals(text)) {
mBuilder.setError(getString(R.string.label_text_is_invalid));
} else if (EhApplication.getDownloadManager(context).containLabel(text)) {
} else if (EhApplication.getDownloadManager().containLabel(text)) {
mBuilder.setError(getString(R.string.label_text_exist));
} else {
mBuilder.setError(null);
mDialog.dismiss();
EhApplication.getDownloadManager(context).renameLabel(mOriginalLabel, text);
EhApplication.getDownloadManager().renameLabel(mOriginalLabel, text);
if (mLabelAdapter != null) {
initLabels();
mLabelAdapter.notifyDataSetChanged();
@@ -1286,12 +1286,12 @@ public class DownloadsScene extends ToolbarScene
mBuilder.setError(getString(R.string.label_text_is_empty));
} else if (getString(R.string.default_download_label_name).equals(text)) {
mBuilder.setError(getString(R.string.label_text_is_invalid));
} else if (EhApplication.getDownloadManager(context).containLabel(text)) {
} else if (EhApplication.getDownloadManager().containLabel(text)) {
mBuilder.setError(getString(R.string.label_text_exist));
} else {
mBuilder.setError(null);
mDialog.dismiss();
EhApplication.getDownloadManager(context).addLabel(text);
EhApplication.getDownloadManager().addLabel(text);
initLabels();
if (mLabelAdapter != null && mLabels != null) {
mLabelAdapter.notifyItemInserted(mLabels.size() - 1);
@@ -1317,7 +1317,7 @@ public class DownloadsScene extends ToolbarScene
return false;
}
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
EhApplication.getDownloadManager().moveLabel(fromPosition - 1, toPosition - 1);
final String item = mLabels.remove(fromPosition);
mLabels.add(toPosition, item);
mLabelAdapter.notifyDataSetChanged();
@@ -34,7 +34,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -47,10 +46,8 @@ import com.google.android.material.datepicker.DateValidatorPointBackward;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.annotation.Implemented;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.ehviewer.EhApplication;
@@ -70,12 +67,10 @@ import com.hippo.ehviewer.ui.annotation.DrawerLifeCircle;
import com.hippo.ehviewer.ui.annotation.ViewLifeCircle;
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
import com.hippo.ehviewer.widget.GalleryInfoContentHelper;
import com.hippo.ehviewer.widget.SearchBar;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.widget.ContentLayout;
import com.hippo.widget.FabLayout;
import com.hippo.widget.SearchBarMover;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.ObjectUtils;
import com.hippo.yorozuya.SimpleHandler;
@@ -91,9 +86,9 @@ import java.util.List;
import rikka.core.res.ResourcesKt;
// TODO Get favorite, modify favorite, add favorite, what a mess!
@SuppressLint("RtlHardcoded")
public class FavoritesScene extends BaseScene implements
FastScroller.OnDragHandlerListener, SearchBarMover.Helper, SearchBar.Helper,
@SuppressLint({"NotifyDataSetChanged", "RtlHardcoded"})
public class FavoritesScene extends SearchBarScene implements
FastScroller.OnDragHandlerListener,
FabLayout.OnClickFabListener, FabLayout.OnExpandListener,
EasyRecyclerView.CustomChoiceListener {
@@ -108,15 +103,10 @@ public class FavoritesScene extends BaseScene implements
public int current; // -1 for error
public int limit; // -1 for error
@Nullable
private ContentLayout mContentLayout;
@Nullable
@ViewLifeCircle
private EasyRecyclerView mRecyclerView;
@Nullable
@ViewLifeCircle
private SearchBar mSearchBar;
@Nullable
@ViewLifeCircle
private FabLayout mFabLayout;
@Nullable
@ViewLifeCircle
@@ -124,12 +114,6 @@ public class FavoritesScene extends BaseScene implements
@Nullable
@ViewLifeCircle
private FavoritesHelper mHelper;
@Nullable
@ViewLifeCircle
private SearchBarMover mSearchBarMover;
@Nullable
@ViewLifeCircle
private DrawerArrowDrawable mLeftDrawable;
private AddDeleteDrawable mActionFabDrawable;
@Nullable
private DrawerLayout mDrawerLayout;
@@ -189,7 +173,7 @@ public class FavoritesScene extends BaseScene implements
Context context = getContext();
AssertUtils.assertNotNull(context);
mClient = EhApplication.getEhClient(context);
mClient = EhApplication.getEhClient();
mFavCatArray = Settings.getFavCat();
mFavCountArray = Settings.getFavCount();
mFavLocalCount = Settings.getFavLocalCount();
@@ -245,19 +229,25 @@ public class FavoritesScene extends BaseScene implements
mUrlBuilder = null;
}
@Nullable
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater,
@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) -> {
onApplySearch(query);
return null;
});
mDrawerLayout = (DrawerLayout) ViewUtils.$$(activity, R.id.draw_view);
mRecyclerView = mContentLayout.getRecyclerView();
FastScroller fastScroller = mContentLayout.getFastScroller();
mSearchBar = (SearchBar) ViewUtils.$$(view, R.id.search_bar);
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
AssertUtils.assertNotNull(container);
container.addView(mFabLayout);
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
mFabLayout
@@ -283,14 +273,9 @@ public class FavoritesScene extends BaseScene implements
fastScroller.setPadding(fastScroller.getPaddingLeft(), fastScroller.getPaddingTop() + paddingTopSB,
fastScroller.getPaddingRight(), fastScroller.getPaddingBottom());
mLeftDrawable = new DrawerArrowDrawable(context, ResourcesKt.resolveColor(getTheme(), android.R.attr.colorControlNormal));
mSearchBar.setLeftDrawable(mLeftDrawable);
mSearchBar.setRightDrawable(ContextCompat.getDrawable(context, R.drawable.v_magnify_x24));
mSearchBar.setHelper(this);
mSearchBar.setAllowEmptySearch(false);
setAllowEmptySearch(false);
updateSearchBar();
updateJumpFab();
mSearchBarMover = new SearchBarMover(this, mSearchBar, mRecyclerView);
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
mActionFabDrawable = new AddDeleteDrawable(context, colorID);
@@ -305,7 +290,7 @@ public class FavoritesScene extends BaseScene implements
// Restore search mode
if (mSearchMode) {
mSearchMode = false;
enterSearchMode(false);
enterSearchMode();
}
// Only refresh for the first time
@@ -321,7 +306,7 @@ public class FavoritesScene extends BaseScene implements
// They changed, call it
private void updateSearchBar() {
Context context = getContext();
if (null == context || null == mUrlBuilder || null == mSearchBar || null == mFavCatArray) {
if (null == context || null == mUrlBuilder || null == mFavCatArray) {
return;
}
@@ -338,17 +323,17 @@ public class FavoritesScene extends BaseScene implements
String keyword = mUrlBuilder.getKeyword();
if (TextUtils.isEmpty(keyword)) {
if (!ObjectUtils.equal(favCatName, mOldFavCat)) {
mSearchBar.setTitle(getString(R.string.favorites_title, favCatName));
setSearchBarHint(getString(R.string.favorites_title, favCatName));
}
} else {
if (!ObjectUtils.equal(favCatName, mOldFavCat) || !ObjectUtils.equal(keyword, mOldKeyword)) {
mSearchBar.setTitle(getString(R.string.favorites_title_2, favCatName, keyword));
setSearchBarHint(getString(R.string.favorites_title_2, favCatName, keyword));
}
}
// Update hint
if (!ObjectUtils.equal(favCatName, mOldFavCat)) {
mSearchBar.setEditTextHint(getString(R.string.favorites_search_bar_hint, favCatName));
setEditTextHint(getString(R.string.favorites_search_bar_hint, favCatName));
}
mOldFavCat = favCatName;
@@ -380,17 +365,13 @@ public class FavoritesScene extends BaseScene implements
mRecyclerView = null;
}
if (null != mFabLayout) {
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
removeAboveSnackView(mFabLayout);
mFabLayout = null;
}
mAdapter = null;
mSearchBar = null;
mSearchBarMover = null;
mLeftDrawable = null;
mOldFavCat = null;
mOldKeyword = null;
}
@@ -445,33 +426,31 @@ public class FavoritesScene extends BaseScene implements
} else if (mFabLayout != null && mFabLayout.isExpanded()) {
mFabLayout.toggle();
} else if (mSearchMode) {
exitSearchMode(true);
exitSearchMode();
} else if (isSearchViewShown()) {
hideSearchView();
} else {
finish();
}
}
@Override
@Implemented(FastScroller.OnDragHandlerListener.class)
public void onStartDragHandler() {
// Lock right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
}
@Override
@Implemented(FastScroller.OnDragHandlerListener.class)
public void onEndDragHandler() {
// Restore right drawer
if (null != mRecyclerView && !mRecyclerView.isInCustomChoice()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
}
if (mSearchBarMover != null) {
mSearchBarMover.returnSearchBarPosition();
}
showSearchBar();
}
public boolean onItemClick(View view, int position) {
public boolean onItemClick(int position) {
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) {
// Skip if in search mode
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
@@ -490,12 +469,7 @@ public class FavoritesScene extends BaseScene implements
return true;
}
// Ensure outOfCustomChoiceMode to avoid error
//if (mRecyclerView != null) {
// mRecyclerView.isInCustomChoice();
//}
exitSearchMode(true);
exitSearchMode();
mUrlBuilder.setKeyword(null);
mUrlBuilder.setFavCat(newFavCat);
@@ -534,78 +508,6 @@ public class FavoritesScene extends BaseScene implements
return true;
}
@Override
@Implemented(SearchBarMover.Helper.class)
public boolean isValidView(RecyclerView recyclerView) {
return recyclerView == mRecyclerView;
}
@Override
@Implemented(SearchBarMover.Helper.class)
public RecyclerView getValidRecyclerView() {
return mRecyclerView;
}
@Override
@Implemented(SearchBarMover.Helper.class)
public boolean forceShowSearchBar() {
return false;
}
@Override
@Implemented(SearchBar.Helper.class)
public void onClickTitle() {
// Skip if in search mode
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
return;
}
if (!mSearchMode) {
enterSearchMode(true);
}
}
@Override
@Implemented(SearchBar.Helper.class)
public void onClickLeftIcon() {
// Skip if in search mode
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
return;
}
if (mSearchMode) {
exitSearchMode(true);
} else {
toggleDrawer(Gravity.LEFT);
}
}
@Override
@Implemented(SearchBar.Helper.class)
public void onClickRightIcon() {
// Skip if in search mode
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
return;
}
if (!mSearchMode) {
enterSearchMode(true);
} else if (mSearchBar != null) {
if (mSearchBar.getEditText().length() == 0) {
exitSearchMode(true);
} else {
mSearchBar.applySearch();
}
}
}
@Override
@Implemented(SearchBar.Helper.class)
public void onSearchEditTextClick() {
}
@Override
@Implemented(SearchBar.Helper.class)
public void onApplySearch(String query) {
// Skip if in search mode
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
@@ -616,24 +518,13 @@ public class FavoritesScene extends BaseScene implements
return;
}
// Ensure outOfCustomChoiceMode to avoid error
//if (mRecyclerView != null) {
// mRecyclerView.isInCustomChoice();
//}
exitSearchMode(true);
exitSearchMode();
mUrlBuilder.setKeyword(query);
updateSearchBar();
mHelper.refresh();
}
@Override
@Implemented(SearchBar.Helper.class)
public void onSearchEditTextBackPressed() {
onBackPressed();
}
@Override
public void onExpand(boolean expanded) {
if (expanded) {
@@ -644,7 +535,6 @@ public class FavoritesScene extends BaseScene implements
}
@Override
@Implemented(FabLayout.OnClickFabListener.class)
public void onClickPrimaryFab(FabLayout view, FloatingActionButton fab) {
if (mRecyclerView != null && mFabLayout != null) {
if (mRecyclerView.isInCustomChoice()) {
@@ -665,7 +555,7 @@ public class FavoritesScene extends BaseScene 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));
@@ -680,13 +570,10 @@ public class FavoritesScene extends BaseScene implements
.setSelection(toDate)
.build();
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
datePicker.addOnPositiveButtonClickListener(v -> {
mHelper.goTo(v);
});
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v));
}
@Override
@Implemented(FabLayout.OnClickFabListener.class)
public void onClickSecondaryFab(FabLayout view, FloatingActionButton fab, int position) {
Context context = getContext();
if (null == context || null == mRecyclerView || null == mHelper) {
@@ -695,17 +582,14 @@ public class FavoritesScene extends BaseScene 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.goToPage(Integer.MAX_VALUE - 1);
}
view.setExpanded(false);
return;
@@ -723,11 +607,9 @@ public class FavoritesScene extends BaseScene 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);
@@ -736,9 +618,8 @@ public class FavoritesScene extends BaseScene 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)
@@ -746,9 +627,8 @@ public class FavoritesScene extends BaseScene 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];
@@ -759,7 +639,6 @@ public class FavoritesScene extends BaseScene implements
.setItems(array, helper)
.setOnCancelListener(helper)
.show();
break;
}
}
}
@@ -785,7 +664,6 @@ public class FavoritesScene extends BaseScene implements
}
@Override
@Implemented(EasyRecyclerView.CustomChoiceListener.class)
public void onIntoCustomChoice(EasyRecyclerView view) {
if (mFabLayout != null) {
showSelectionFabs();
@@ -802,7 +680,6 @@ public class FavoritesScene extends BaseScene implements
}
@Override
@Implemented(EasyRecyclerView.CustomChoiceListener.class)
public void onOutOfCustomChoice(EasyRecyclerView view) {
if (mFabLayout != null) {
showNormalFabs();
@@ -818,7 +695,6 @@ public class FavoritesScene extends BaseScene implements
}
@Override
@Implemented(EasyRecyclerView.CustomChoiceListener.class)
public void onItemCheckedStateChanged(EasyRecyclerView view, int position, long id, boolean checked) {
if (view.getCheckedItemCount() == 0) {
@@ -826,29 +702,24 @@ public class FavoritesScene extends BaseScene implements
}
}
private void enterSearchMode(boolean animation) {
if (mSearchMode || mSearchBar == null || mSearchBarMover == null || mLeftDrawable == null) {
private void enterSearchMode() {
if (mSearchMode) {
return;
}
mSearchMode = true;
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
mSearchBarMover.returnSearchBarPosition(animation);
mLeftDrawable.setArrow(ANIMATE_TIME);
showSearchBar();
}
private void exitSearchMode(boolean animation) {
if (!mSearchMode || mSearchBar == null || mSearchBarMover == null || mLeftDrawable == null) {
private void exitSearchMode() {
if (!mSearchMode) {
return;
}
mSearchMode = false;
mSearchBar.setState(SearchBar.STATE_NORMAL, animation);
mSearchBarMover.returnSearchBarPosition();
mLeftDrawable.setMenu(ANIMATE_TIME);
showSearchBar();
}
private void onGetFavoritesSuccess(FavoritesParser.Result result, int taskId) {
if (mHelper != null && mSearchBarMover != null &&
mHelper.isCurrentTask(taskId)) {
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
if (mFavCatArray != null) {
System.arraycopy(result.catArray, 0, mFavCatArray, 0, 10);
@@ -864,9 +735,8 @@ public class FavoritesScene extends BaseScene implements
}
updateSearchBar();
assert mUrlBuilder != null;
int pages = 0;
int pages;
if (result.nextPage == null)
pages = mHelper.pgCounter + 1;
else
@@ -895,8 +765,7 @@ public class FavoritesScene extends BaseScene implements
}
private void onGetFavoritesFailure(Exception e, int taskId) {
if (mHelper != null && mSearchBarMover != null &&
mHelper.isCurrentTask(taskId)) {
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
mHelper.onGetException(taskId, e);
}
}
@@ -1076,7 +945,7 @@ public class FavoritesScene extends BaseScene 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
@@ -1189,7 +1058,7 @@ public class FavoritesScene extends BaseScene implements
@Override
void onItemClick(View view, int position) {
FavoritesScene.this.onItemClick(view, position);
FavoritesScene.this.onItemClick(position);
}
@Override
@@ -1223,8 +1092,8 @@ public class FavoritesScene extends BaseScene implements
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);
@@ -1242,7 +1111,6 @@ public class FavoritesScene extends BaseScene 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;
}
@@ -1262,7 +1130,7 @@ public class FavoritesScene extends BaseScene implements
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) {
@@ -1270,10 +1138,13 @@ public class FavoritesScene extends BaseScene implements
SimpleHandler.getInstance().post(() -> onGetFavoritesLocal(keyword, taskId));
} else {
String prev = null, next = null;
if (jumpTo != null) {
if (page == Integer.MAX_VALUE - 1) {
prev = "1-0";
jumpTo = null;
} else if (jumpTo != null) {
next = Integer.toString(minGid);
operation = 0;
} else if (page != 0) {
} else if (page != 0 && mHelper != null) {
if (page >= mHelper.getPageForTop()) {
next = lowerPage;
operation = 1;
@@ -1294,7 +1165,7 @@ public class FavoritesScene extends BaseScene implements
request.setCallback(new GetFavoritesListener(getContext(),
activity.getStageId(), getTag(),
taskId, false, mUrlBuilder.getKeyword()));
request.setArgs(url, Settings.getShowJpnTitle());
request.setArgs(url);
mClient.execute(request);
}
}
@@ -1317,9 +1188,9 @@ public class FavoritesScene extends BaseScene implements
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (mAdapter != null) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@@ -1332,9 +1203,7 @@ public class FavoritesScene extends BaseScene implements
@Override
public void onShowView(View hiddenView, View shownView) {
if (null != mSearchBarMover) {
mSearchBarMover.showSearchBar();
}
showSearchBar();
}
@Override
@@ -1343,11 +1212,9 @@ public class FavoritesScene extends BaseScene implements
}
@Override
protected void onScrollToPosition(int postion) {
if (0 == postion) {
if (null != mSearchBarMover) {
mSearchBarMover.showSearchBar();
}
protected void onScrollToPosition(int position) {
if (0 == position) {
showSearchBar();
}
}
@@ -1364,16 +1231,18 @@ public class FavoritesScene extends BaseScene implements
operation = 0;
}
@NonNull
@Override
protected Parcelable saveInstanceState(Parcelable superState) {
protected Parcelable saveInstanceState(@NonNull Parcelable superState) {
Bundle bundle = (Bundle) super.saveInstanceState(superState);
bundle.putString(KEY_UPPER_PAGE, upperPage);
bundle.putString(KEY_LOWER_PAGE, lowerPage);
return bundle;
}
@NonNull
@Override
protected Parcelable restoreInstanceState(Parcelable state) {
protected Parcelable restoreInstanceState(@NonNull Parcelable state) {
Bundle bundle = (Bundle) state;
upperPage = bundle.getString(KEY_UPPER_PAGE);
lowerPage = bundle.getString(KEY_LOWER_PAGE);
@@ -85,7 +85,7 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
setType(type);
mDownloadManager = EhApplication.getDownloadManager(inflater.getContext());
mDownloadManager = EhApplication.getDownloadManager();
}
private void adjustPaddings() {
@@ -126,7 +126,7 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
break;
}
case GalleryAdapter.TYPE_GRID: {
int columnWidth = mResources.getDimensionPixelOffset(Settings.getThumbSizeResId());
int columnWidth = Settings.getThumbSize();
mLayoutManager.setColumnSize(columnWidth);
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_SUITABLE_SIZE);
if (null != mListDecoration) {
@@ -278,7 +278,7 @@ public final class GalleryCommentsScene extends ToolbarScene
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey, mGalleryDetail.gid, mGalleryDetail.token, id, vote)
.setCallback(new VoteCommentListener(context,
activity.getStageId(), getTag()));
EhApplication.getEhClient(context).execute(request);
EhApplication.getEhClient().execute(request);
}
@SuppressLint("InflateParams")
@@ -408,7 +408,7 @@ public final class GalleryCommentsScene extends ToolbarScene
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
.setArgs(url)
.setCallback(new RefreshCommentListener(activity, activity.getStageId(), getTag()));
EhApplication.getEhClient(activity).execute(request);
EhApplication.getEhClient().execute(request);
}
}
@@ -591,7 +591,7 @@ public final class GalleryCommentsScene extends ToolbarScene
.setArgs(url, comment, mCommentId != 0 ? Long.toString(mCommentId) : null)
.setCallback(new CommentGalleryListener(context,
activity.getStageId(), getTag(), mCommentId));
EhApplication.getEhClient(context).execute(request);
EhApplication.getEhClient().execute(request);
hideSoftInput();
hideEditPanel(true);
}
@@ -709,7 +709,7 @@ public final class GalleryCommentsScene extends ToolbarScene
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
.setArgs(url)
.setCallback(new RefreshCommentListener(activity, activity.getStageId(), getTag()));
EhApplication.getEhClient(activity).execute(request);
EhApplication.getEhClient().execute(request);
}
}
}
@@ -858,8 +858,7 @@ public final class GalleryCommentsScene extends ToolbarScene
}
private CharSequence generateComment(Context context, ObservedTextView textView, GalleryComment comment) {
Spanned sp = Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY, new URLImageGetter(textView,
EhApplication.getConaco(context)), null);
Spanned sp = Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY, new URLImageGetter(textView), null);
SpannableStringBuilder ssb = new SpannableStringBuilder(sp);
@@ -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,8 +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.beerbelly.BeerBelly;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.R;
@@ -91,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;
@@ -116,20 +112,17 @@ import com.hippo.widget.ObservedTextView;
import com.hippo.widget.SimpleGridAutoSpanLayout;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.IntIdGenerator;
import com.hippo.yorozuya.ViewUtils;
import com.hippo.yorozuya.collect.IntList;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
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;
@@ -267,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) {
@@ -299,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) {
@@ -363,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) {
@@ -378,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
@@ -394,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;
@@ -416,6 +371,11 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
}
// Judging by the uploader to exclude the cooldown period
private boolean getDisowned() {
return getUploader().equals("(Disowned)");
}
// -1 for error
private int getCategory() {
if (mGalleryDetail != null) {
@@ -453,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(requireContext(), mGalleryInfo);
galleryProvider.start();
int startPage = galleryProvider.getStartPage();
if (startPage != 0) {
@@ -520,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) {
@@ -551,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) {
@@ -560,7 +520,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (gid != -1) {
Context context = getContext();
AssertUtils.assertNotNull(context);
mDownloadState = EhApplication.getDownloadManager(context).getDownloadState(gid);
mDownloadState = EhApplication.getDownloadManager().getDownloadState(gid);
} else {
mDownloadState = DownloadInfo.STATE_INVALID;
}
@@ -656,19 +616,19 @@ 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(context).addDownloadInfoListener(this);
EhApplication.getDownloadManager().addDownloadInfoListener(this);
return view;
}
@@ -679,7 +639,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
Context context = getContext();
AssertUtils.assertNotNull(context);
EhApplication.getDownloadManager(context).removeDownloadInfoListener(this);
EhApplication.getDownloadManager().removeDownloadInfoListener(this);
mTip = null;
mViewTransition = null;
@@ -742,7 +702,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
// Get from cache
mGalleryDetail = EhApplication.getGalleryDetailCache(context).get(gid);
mGalleryDetail = EhApplication.getGalleryDetailCache().get(gid);
if (mGalleryDetail != null) {
return true;
}
@@ -760,24 +720,24 @@ 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()
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
.setArgs(url)
.setCallback(callback);
EhApplication.getEhClient(context).execute(request);
EhApplication.getEhClient().execute(request);
return true;
}
private void adjustViewVisibility(int state, boolean animation) {
private void adjustViewVisibility(int state) {
if (state == mState) {
return;
}
@@ -785,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);
}
}
@@ -867,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();
@@ -883,7 +838,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
Resources resources = getResources();
mThumb.load(EhCacheKeyFactory.getThumbKey(gd.gid), gd.thumb);
mThumb.load(EhCacheKeyFactory.getThumbKey(gd.gid), gd.thumb, true);
setTitle(EhUtils.getSuitableTitle(gd));
mUploader.setText(gd.uploader);
mUploader.setAlpha(gd.disowned ? .5f : 1f);
@@ -916,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;
}
@@ -928,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) {
@@ -937,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);
@@ -947,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);
@@ -960,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);
@@ -975,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;
}
@@ -1004,7 +956,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
ObservedTextView c = v.findViewById(R.id.comment);
c.setMaxLines(5);
c.setText(Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY,
new URLImageGetter(c, EhApplication.getConaco(context)), null));
new URLImageGetter(c), null));
v.setBackgroundColor(Color.TRANSPARENT);
}
}
@@ -1014,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;
}
@@ -1029,7 +981,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mPreviewText.setText(R.string.more_previews);
}
int columnWidth = resources.getDimensionPixelOffset(Settings.getThumbSizeResId());
int columnWidth = Settings.getThumbSize();
mGridLayout.setColumnSize(columnWidth);
mGridLayout.setStrategy(SimpleGridAutoSpanLayout.STRATEGY_SUITABLE_SIZE);
int size = Math.min(previewNum, previewSet.size());
@@ -1050,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) {
@@ -1098,26 +1040,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (-1L == gid) {
return;
}
File temp = AppConfig.createTempFile();
if (null == temp) {
return;
}
BeerBelly beerBelly = EhApplication.getConaco(context).getBeerBelly();
OutputStream os = null;
try {
os = new FileOutputStream(temp);
if (beerBelly.pullFromDiskCache(EhCacheKeyFactory.getThumbKey(gid), os)) {
ListUrlBuilder lub = new ListUrlBuilder();
lub.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
lub.setImagePath(temp.getPath());
lub.setUseSimilarityScan(true);
GalleryListScene.startScene(this, lub);
}
} catch (FileNotFoundException e) {
// Ignore
} finally {
IOUtils.closeQuietly(os);
var path = Coil.imageLoader(context).getDiskCache().get(EhCacheKeyFactory.getThumbKey(gid)).getData();
ListUrlBuilder lub = new ListUrlBuilder();
lub.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
lub.setImagePath(path.toString());
lub.setUseSimilarityScan(true);
GalleryListScene.startScene(this, lub);
} catch (Throwable e) {
e.printStackTrace();
}
}
@@ -1131,11 +1063,11 @@ 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();
if (TextUtils.isEmpty(uploader)) {
if (TextUtils.isEmpty(uploader) || getDisowned()) {
return;
}
ListUrlBuilder lub = new ListUrlBuilder();
@@ -1144,7 +1076,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
GalleryListScene.startScene(this, lub);
} else if (mCategory == v) {
int category = getCategory();
if (category == -1) {
if (category == EhUtils.NONE || category == EhUtils.PRIVATE || category == EhUtils.UNKNOWN) {
return;
}
ListUrlBuilder lub = new ListUrlBuilder();
@@ -1153,7 +1085,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
} else if (mDownload == v) {
GalleryInfo galleryInfo = getGalleryInfo();
if (galleryInfo != null) {
if (EhApplication.getDownloadManager(context).getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
if (EhApplication.getDownloadManager().getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
CommonOperations.startDownload(activity, galleryInfo, false);
} else {
CheckBoxDialogBuilder builder = new CheckBoxDialogBuilder(context,
@@ -1161,7 +1093,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
getString(R.string.download_remove_dialog_check_text),
Settings.getRemoveImageFiles());
DeleteDialogHelper helper = new DeleteDialogHelper(
EhApplication.getDownloadManager(context), galleryInfo, builder);
EhApplication.getDownloadManager(), galleryInfo, builder);
builder.setTitle(R.string.download_remove_dialog_title)
.setPositiveButton(android.R.string.ok, helper)
.show();
@@ -1175,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) {
@@ -1202,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));
@@ -1221,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);
}
@@ -1309,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);
}
}
@@ -1429,7 +1361,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey, mGalleryDetail.gid, mGalleryDetail.token, tag, vote)
.setCallback(new VoteTagListener(context,
activity.getStageId(), getTag()));
EhApplication.getEhClient(context).execute(request);
EhApplication.getEhClient().execute(request);
}
@Override
@@ -1440,6 +1372,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
if (mUploader == v) {
if (TextUtils.isEmpty(getUploader()) || getDisowned()) {
return false;
}
showFilterUploaderDialog();
} else if (mDownload == v) {
GalleryInfo galleryInfo = getGalleryInfo();
@@ -1448,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);
@@ -1503,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);
}
}
@@ -1532,7 +1455,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return;
}
int downloadState = EhApplication.getDownloadManager(context).getDownloadState(gid);
int downloadState = EhApplication.getDownloadManager().getDownloadState(gid);
if (downloadState == mDownloadState) {
return;
}
@@ -1581,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();
}
@@ -1591,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);
}
}
@@ -1609,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));
}
@@ -1650,7 +1573,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
getApplication().removeGlobalStuff(this);
// Put gallery detail to cache
EhApplication.getGalleryDetailCache(getApplication()).put(result.gid, result);
EhApplication.getGalleryDetailCache().put(result.gid, result);
// Add history
EhDB.putHistoryInfo(result);
@@ -1730,7 +1653,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
scene.onRateGallerySuccess(result);
} else {
// Update rating in cache
GalleryDetail gd = EhApplication.getGalleryDetailCache(getApplication()).get(mGid);
GalleryDetail gd = EhApplication.getGalleryDetailCache().get(mGid);
if (gd != null) {
gd.rating = result.rating;
gd.ratingCount = result.ratingCount;
@@ -1782,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();
}
}
@@ -1790,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();
}
}
@@ -1859,7 +1782,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mRequest = new EhRequest().setMethod(EhClient.METHOD_ARCHIVE_LIST)
.setArgs(url, mGid, mToken)
.setCallback(this);
EhApplication.getEhClient(context).execute(mRequest);
EhApplication.getEhClient().execute(mRequest);
} else {
bind(mArchiveList);
}
@@ -1898,7 +1821,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
request.setMethod(EhClient.METHOD_DOWNLOAD_ARCHIVE);
request.setArgs(mGalleryDetail.gid, mGalleryDetail.token, mArchiveFormParamOr, res);
request.setCallback(new DownloadArchiveListener(context, activity.getStageId(), getTag()));
EhApplication.getEhClient(context).execute(request);
EhApplication.getEhClient().execute(request);
}
if (mDialog != null) {
@@ -1976,7 +1899,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mRequest = new EhRequest().setMethod(EhClient.METHOD_GET_TORRENT_LIST)
.setArgs(url, mGid, mToken)
.setCallback(this);
EhApplication.getEhClient(context).execute(mRequest);
EhApplication.getEhClient().execute(mRequest);
} else {
bind(mTorrentList);
}
@@ -2017,7 +1940,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
FileUtils.sanitizeFilename(name + ".torrent"));
r.allowScanningByMediaScanner();
r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
r.addRequestHeader("Cookie", EhApplication.getEhCookieStore(context).getCookieHeader(HttpUrl.get(url)));
r.addRequestHeader("Cookie", EhApplication.getEhCookieStore().getCookieHeader(HttpUrl.get(url)));
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (dm != null) {
try {
@@ -2112,11 +2035,11 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mGalleryDetail.gid, mGalleryDetail.token, mRatingBar.getRating())
.setCallback(new RateGalleryListener(context,
activity.getStageId(), getTag(), mGalleryDetail.gid));
EhApplication.getEhClient(context).execute(request);
EhApplication.getEhClient().execute(request);
}
}
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;
@@ -32,6 +31,7 @@ import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
@@ -64,7 +64,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.ehviewer.EhApplication;
@@ -88,11 +87,9 @@ 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;
import com.hippo.ehviewer.widget.SearchBar;
import com.hippo.ehviewer.widget.SearchLayout;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
@@ -102,7 +99,6 @@ import com.hippo.view.BringOutTransition;
import com.hippo.view.ViewTransition;
import com.hippo.widget.ContentLayout;
import com.hippo.widget.FabLayout;
import com.hippo.widget.SearchBarMover;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.MathUtils;
@@ -120,12 +116,13 @@ 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")
public final class GalleryListScene extends BaseScene
implements SearchBar.Helper, SearchBar.OnStateChangeListener, FastScroller.OnDragHandlerListener,
SearchLayout.Helper, SearchBarMover.Helper, View.OnClickListener, FabLayout.OnClickFabListener,
public final class GalleryListScene extends SearchBarScene
implements FastScroller.OnDragHandlerListener,
SearchLayout.Helper, FabLayout.OnClickFabListener,
FabLayout.OnExpandListener {
public final static String KEY_ACTION = "action";
@@ -137,14 +134,11 @@ public final class GalleryListScene extends BaseScene
public final static String KEY_LIST_URL_BUILDER = "list_url_builder";
public final static String KEY_HAS_FIRST_REFRESH = "has_first_refresh";
public final static String KEY_STATE = "state";
private static final int BACK_PRESSED_INTERVAL = 2000;
private final static int STATE_NORMAL = 0;
private final static int STATE_SIMPLE_SEARCH = 1;
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
---------------*/
@@ -152,21 +146,14 @@ public final class GalleryListScene extends BaseScene
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
private SearchBar mSearchBar;
@Nullable
private View mSearchFab;
@Nullable
private final Animator.AnimatorListener mSearchFabAnimatorListener = new SimpleAnimatorListener() {
@@ -197,12 +184,6 @@ public final class GalleryListScene extends BaseScene
@Nullable
private GalleryListHelper mHelper;
@Nullable
private DrawerArrowDrawable mLeftDrawable;
@Nullable
private AddDeleteDrawable mRightDrawable;
@Nullable
private SearchBarMover mSearchBarMover;
@Nullable
private AddDeleteDrawable mActionFabDrawable;
@Nullable
private List<QuickSearch> mQuickSearchList;
@@ -258,17 +239,13 @@ public final class GalleryListScene extends BaseScene
} 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) {
@@ -289,6 +266,11 @@ public final class GalleryListScene extends BaseScene
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;
@@ -328,20 +310,19 @@ public final class GalleryListScene extends BaseScene
mHelper.refresh();
}
setState(STATE_NORMAL);
if (null != mSearchBarMover) {
mSearchBarMover.showSearchBar();
}
showSearchBar();
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Context context = getContext();
AssertUtils.assertNotNull(context);
mClient = EhApplication.getEhClient(context);
mDownloadManager = EhApplication.getDownloadManager(context);
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context);
mClient = EhApplication.getEhClient();
mDownloadManager = EhApplication.getDownloadManager();
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter();
mDownloadInfoListener = new DownloadManager.DownloadInfoListener() {
@Override
@@ -441,14 +422,14 @@ public final class GalleryListScene extends BaseScene
mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener);
}
private void setSearchBarHint(SearchBar searchBar) {
searchBar.setEditTextHint(getString(EhUrl.SITE_EX == Settings.getGallerySite() ?
private void setSearchBarHint() {
setEditTextHint(getString(EhUrl.SITE_EX == Settings.getGallerySite() ?
R.string.gallery_list_search_bar_hint_exhentai :
R.string.gallery_list_search_bar_hint_e_hentai));
}
private void setSearchBarSuggestionProvider(SearchBar searchBar) {
searchBar.setSuggestionProvider(text -> {
private void setSearchBarSuggestionProvider() {
setSuggestionProvider(text -> {
GalleryDetailUrlParser.Result result1 = GalleryDetailUrlParser.parse(text, false);
if (result1 != null) {
return Collections.singletonList(new GalleryDetailUrlSuggestion(result1.gid, result1.token));
@@ -493,27 +474,29 @@ public final class GalleryListScene extends BaseScene
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, !isTopList && !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) && null != mSearchBar && !mIsTopList) {
if (builder.getMode() == ListUrlBuilder.MODE_TAG) {
if (!TextUtils.isEmpty(keyword) && !mIsTopList) {
if (mode == ListUrlBuilder.MODE_TAG) {
keyword = wrapTagKeyword(keyword);
}
mSearchBar.setText(keyword);
mSearchBar.cursorToEnd();
setSearchBarText(keyword);
}
// Update title
@@ -521,33 +504,28 @@ public final class GalleryListScene extends BaseScene
if (null == title) {
title = resources.getString(R.string.search);
}
if (null != mSearchBar) {
mSearchBar.setTitle(title);
}
setSearchBarHint(title);
// 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;
}
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_gallery_list, container, false);
Context context = getContext();
@@ -558,18 +536,19 @@ public final class GalleryListScene extends BaseScene
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);
mSearchBar = (SearchBar) ViewUtils.$$(mainLayout, R.id.search_bar);
mFabLayout = (FabLayout) ViewUtils.$$(mainLayout, R.id.fab_layout);
mSearchFab = ViewUtils.$$(mainLayout, R.id.search_fab);
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);
container.addView(mFabLayout);
int paddingTopSB = resources.getDimensionPixelOffset(R.dimen.gallery_padding_top_search_bar);
int paddingBottomFab = resources.getDimensionPixelOffset(R.dimen.gallery_padding_bottom_fab);
@@ -590,14 +569,12 @@ public final class GalleryListScene extends BaseScene
fastScroller.setPadding(fastScroller.getPaddingLeft(), fastScroller.getPaddingTop() + paddingTopSB,
fastScroller.getPaddingRight(), fastScroller.getPaddingBottom());
mLeftDrawable = new DrawerArrowDrawable(context, ResourcesKt.resolveColor(getTheme(), android.R.attr.colorControlNormal));
mRightDrawable = new AddDeleteDrawable(context, ResourcesKt.resolveColor(getTheme(), android.R.attr.colorControlNormal));
mSearchBar.setLeftDrawable(mLeftDrawable);
mSearchBar.setRightDrawable(mRightDrawable);
mSearchBar.setHelper(this);
mSearchBar.setOnStateChangeListener(this);
setSearchBarHint(mSearchBar);
setSearchBarSuggestionProvider(mSearchBar);
setOnApplySearch((query) -> {
onApplySearch(query);
return null;
});
setSearchBarHint();
setSearchBarSuggestionProvider();
mSearchLayout.setHelper(this);
mSearchLayout.setPadding(mSearchLayout.getPaddingLeft(), mSearchLayout.getPaddingTop() + paddingTopSB,
@@ -613,10 +590,12 @@ public final class GalleryListScene extends BaseScene
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
mActionFabDrawable = new AddDeleteDrawable(context, colorID);
mFabLayout.getPrimaryFab().setImageDrawable(mActionFabDrawable);
mSearchFab.setOnClickListener(this);
mSearchBarMover = new SearchBarMover(this, mSearchBar, mRecyclerView, mSearchLayout);
mSearchFab.setOnClickListener((v) -> {
if (STATE_NORMAL != mState) {
onApplySearch();
hideSoftInput();
}
});
// Update list url builder
onUpdateUrlBuilder();
@@ -639,10 +618,6 @@ public final class GalleryListScene extends BaseScene
public void onDestroyView() {
super.onDestroyView();
if (null != mSearchBarMover) {
mSearchBarMover.cancelAnimation();
mSearchBarMover = null;
}
if (null != mHelper) {
mHelper.destroy();
if (1 == mHelper.getShownViewIndex()) {
@@ -654,22 +629,18 @@ public final class GalleryListScene extends BaseScene
mRecyclerView = null;
}
if (null != mFabLayout) {
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
removeAboveSnackView(mFabLayout);
mFabLayout = null;
}
mAdapter = null;
mSearchLayout = null;
mSearchBar = null;
mSearchFab = null;
mViewTransition = null;
mLeftDrawable = null;
mRightDrawable = null;
mActionFabDrawable = null;
}
private void showQuickSearchTipDialog(final QsDrawerAdapter adapter,
final EasyRecyclerView recyclerView, final TextView tip) {
private void showQuickSearchTipDialog() {
Context context = getContext();
if (null == context) {
return;
@@ -773,7 +744,7 @@ public final class GalleryListScene extends BaseScene
if (id == R.id.action_add) {
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
} else if (id == R.id.action_help) {
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
showQuickSearchTipDialog();
}
return true;
});
@@ -793,19 +764,17 @@ public final class GalleryListScene extends BaseScene
if (null != mFabLayout && mFabLayout.isExpanded()) {
return false;
}
switch (mState) {
case STATE_NORMAL:
return getStackIndex() == 0;
case STATE_SIMPLE_SEARCH:
case STATE_SEARCH:
case STATE_SEARCH_SHOW_LIST:
return false;
}
return false;
if (isSearchViewShown())
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;
@@ -828,14 +797,14 @@ public final class GalleryListScene extends BaseScene
}
}
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();
@@ -843,15 +812,6 @@ public final class GalleryListScene extends BaseScene
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, gi);
Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args);
startScene(announcer);
return true;
}
@Override
public void onClick(View v) {
if (STATE_NORMAL != mState && null != mSearchBar) {
mSearchBar.applySearch();
hideSoftInput();
}
}
@Override
@@ -903,7 +863,7 @@ public final class GalleryListScene extends BaseScene
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));
@@ -918,9 +878,7 @@ public final class GalleryListScene extends BaseScene
.setSelection(toDate)
.build();
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
datePicker.addOnPositiveButtonClickListener(v -> {
mHelper.goTo(v);
});
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v));
}
}
@@ -931,17 +889,14 @@ public final class GalleryListScene extends BaseScene
}
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.goToPage(Integer.MAX_VALUE - 1);
}
view.setExpanded(false);
@@ -964,6 +919,18 @@ public final class GalleryListScene extends BaseScene
}
}
@Override
public void onSearchViewExpanded() {
super.onSearchViewExpanded();
selectSearchFab(true);
}
@Override
public void onSearchViewHidden() {
super.onSearchViewHidden();
if (mState == STATE_NORMAL) selectActionFab(true);
}
public boolean onItemLongClick(int position) {
final Context context = getContext();
final MainActivity activity = getMainActivity();
@@ -1006,9 +973,9 @@ public final class GalleryListScene extends BaseScene
.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
@@ -1030,7 +997,7 @@ public final class GalleryListScene extends BaseScene
}
break;
case 3: // Move
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager(context).getLabelList();
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager().getLabelList();
List<String> labelList = new ArrayList<>(labelRawList.size() + 1);
labelList.add(getString(R.string.default_download_label_name));
for (int i = 0, n = labelRawList.size(); i < n; i++) {
@@ -1157,8 +1124,7 @@ public final class GalleryListScene extends BaseScene
}
private void setState(@State int state, boolean animation) {
if (null == mSearchBar || null == mSearchBarMover ||
null == mViewTransition || null == mSearchLayout) {
if (null == mViewTransition || null == mSearchLayout) {
return;
}
@@ -1166,71 +1132,40 @@ public final class GalleryListScene extends BaseScene
int oldState = mState;
mState = state;
showSearchBar();
onStateChange(state);
switch (oldState) {
case STATE_NORMAL:
if (state == STATE_SIMPLE_SEARCH) {
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
mSearchBarMover.returnSearchBarPosition();
selectSearchFab(animation);
} else if (state == STATE_SEARCH) {
mViewTransition.showView(1, animation);
mSearchLayout.scrollSearchContainerToTop();
mSearchBar.setState(SearchBar.STATE_SEARCH, animation);
mSearchBarMover.returnSearchBarPosition();
selectSearchFab(animation);
} else if (state == STATE_SEARCH_SHOW_LIST) {
mViewTransition.showView(1, animation);
mSearchLayout.scrollSearchContainerToTop();
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
mSearchBarMover.returnSearchBarPosition();
selectSearchFab(animation);
}
break;
case STATE_SIMPLE_SEARCH:
if (state == STATE_NORMAL) {
mSearchBar.setState(SearchBar.STATE_NORMAL, animation);
mSearchBarMover.returnSearchBarPosition();
selectActionFab(animation);
} else if (state == STATE_SEARCH) {
mViewTransition.showView(1, animation);
mSearchLayout.scrollSearchContainerToTop();
mSearchBar.setState(SearchBar.STATE_SEARCH, animation);
mSearchBarMover.returnSearchBarPosition();
} else if (state == STATE_SEARCH_SHOW_LIST) {
mViewTransition.showView(1, animation);
mSearchLayout.scrollSearchContainerToTop();
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
mSearchBarMover.returnSearchBarPosition();
}
break;
case STATE_SEARCH:
if (state == STATE_NORMAL) {
mViewTransition.showView(0, animation);
mSearchBar.setState(SearchBar.STATE_NORMAL, animation);
mSearchBarMover.returnSearchBarPosition();
selectActionFab(animation);
} else if (state == STATE_SIMPLE_SEARCH) {
mViewTransition.showView(0, animation);
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
mSearchBarMover.returnSearchBarPosition();
} else if (state == STATE_SEARCH_SHOW_LIST) {
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
mSearchBarMover.returnSearchBarPosition();
}
break;
case STATE_SEARCH_SHOW_LIST:
if (state == STATE_NORMAL) {
mViewTransition.showView(0, animation);
mSearchBar.setState(SearchBar.STATE_NORMAL, animation);
mSearchBarMover.returnSearchBarPosition();
selectActionFab(animation);
} else if (state == STATE_SIMPLE_SEARCH) {
mViewTransition.showView(0, animation);
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
mSearchBarMover.returnSearchBarPosition();
} else if (state == STATE_SEARCH) {
mSearchBar.setState(SearchBar.STATE_SEARCH, animation);
mSearchBarMover.returnSearchBarPosition();
}
break;
}
@@ -1239,51 +1174,15 @@ public final class GalleryListScene extends BaseScene
}
@Override
public void onClickTitle() {
public boolean onMenuItemClick(@NonNull MenuItem item) {
if (mState == STATE_NORMAL) {
setState(STATE_SIMPLE_SEARCH);
}
}
@Override
public void onClickLeftIcon() {
if (null == mSearchBar) {
return;
}
if (mSearchBar.getState() == SearchBar.STATE_NORMAL) {
toggleDrawer(Gravity.LEFT);
setState(STATE_SEARCH);
} else {
setState(STATE_NORMAL);
}
return true;
}
@Override
public void onClickRightIcon() {
if (null == mSearchBar) {
return;
}
if (mSearchBar.getState() == SearchBar.STATE_NORMAL) {
setState(STATE_SEARCH);
} else {
if (mSearchBar.getEditText().length() == 0) {
setState(STATE_NORMAL);
} else {
// Clear
mSearchBar.setText("");
}
}
}
@Override
public void onSearchEditTextClick() {
if (mState == STATE_SEARCH) {
setState(STATE_SEARCH_SHOW_LIST);
}
}
@Override
public void onApplySearch(String query) {
if (null == mUrlBuilder || null == mHelper || null == mSearchLayout) {
return;
@@ -1311,21 +1210,6 @@ public final class GalleryListScene extends BaseScene
setState(STATE_NORMAL);
}
@Override
public void onSearchEditTextBackPressed() {
onBackPressed();
}
@Override
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
@@ -1336,38 +1220,10 @@ public final class GalleryListScene extends BaseScene
public void onEndDragHandler() {
// Restore right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
if (null != mSearchBarMover) {
mSearchBarMover.returnSearchBarPosition();
}
showSearchBar();
}
@Override
public void onStateChange(SearchBar searchBar, int newState, int oldState, boolean animation) {
if (null == mLeftDrawable || null == mRightDrawable) {
return;
}
switch (oldState) {
default:
case SearchBar.STATE_NORMAL:
mLeftDrawable.setArrow(animation ? ANIMATE_TIME : 0);
mRightDrawable.setDelete(animation ? ANIMATE_TIME : 0);
break;
case SearchBar.STATE_SEARCH:
if (newState == SearchBar.STATE_NORMAL) {
mLeftDrawable.setMenu(animation ? ANIMATE_TIME : 0);
mRightDrawable.setAdd(animation ? ANIMATE_TIME : 0);
}
break;
case SearchBar.STATE_SEARCH_LIST:
if (newState == STATE_NORMAL) {
mLeftDrawable.setMenu(animation ? ANIMATE_TIME : 0);
mRightDrawable.setAdd(animation ? ANIMATE_TIME : 0);
}
break;
}
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);
@@ -1379,9 +1235,7 @@ public final class GalleryListScene extends BaseScene
@Override
public void onChangeSearchMode() {
if (null != mSearchBarMover) {
mSearchBarMover.showSearchBar();
}
showSearchBar();
}
@Override
@@ -1396,38 +1250,15 @@ public final class GalleryListScene extends BaseScene
}
}
// SearchBarMover.Helper
@Override
public boolean isValidView(RecyclerView recyclerView) {
return (mState == STATE_NORMAL && recyclerView == mRecyclerView) ||
(mState == STATE_SEARCH && recyclerView == mSearchLayout);
}
// SearchBarMover.Helper
@Override
public RecyclerView getValidRecyclerView() {
if (mState == STATE_NORMAL || mState == STATE_SIMPLE_SEARCH) {
return mRecyclerView;
} else {
return mSearchLayout;
}
}
// SearchBarMover.Helper
@Override
public boolean forceShowSearchBar() {
return mState == STATE_SIMPLE_SEARCH || mState == STATE_SEARCH_SHOW_LIST;
}
private void onGetGalleryListSuccess(GalleryListParser.Result result, int taskId) {
if (mHelper != null && mSearchBarMover != null &&
mHelper.isCurrentTask(taskId)) {
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
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;
int pages;
if (mIsTopList)
pages = 200;
else if (result.nextGid == 0)
@@ -1440,8 +1271,7 @@ public final class GalleryListScene extends BaseScene
}
private void onGetGalleryListFailure(Exception e, int taskId) {
if (mHelper != null && mSearchBarMover != null &&
mHelper.isCurrentTask(taskId)) {
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
mHelper.onGetException(taskId, e);
}
}
@@ -1571,7 +1401,7 @@ public final class GalleryListScene extends BaseScene
mRecyclerView.outOfCustomChoiceMode();
}
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
DownloadManager downloadManager = EhApplication.getDownloadManager();
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
if (downloadInfo == null) {
return;
@@ -1651,7 +1481,7 @@ public final class GalleryListScene extends BaseScene
}
}
private abstract class UrlSuggestion extends SearchBar.Suggestion {
private abstract class UrlSuggestion extends Suggestion {
@Override
public CharSequence getText(TextView textView) {
if (textView.getId() == android.R.id.text1) {
@@ -1739,7 +1569,7 @@ public final class GalleryListScene extends BaseScene
@Override
void onItemClick(View view, int position) {
GalleryListScene.this.onItemClick(view, position);
GalleryListScene.this.onItemClick(position);
}
@Override
@@ -1773,6 +1603,9 @@ public final class GalleryListScene extends BaseScene
} else {
nextGid = page;
}
} else if (page == Integer.MAX_VALUE - 1) {
prevGid = 1;
jumpTo = null;
} else if (jumpTo != null) {
nextGid = minGid;
} else if (page != 0) {
@@ -1812,6 +1645,7 @@ public final class GalleryListScene extends BaseScene
return GalleryListScene.this.getContext();
}
@SuppressLint("NotifyDataSetChanged")
@Override
protected void notifyDataSetChanged() {
if (null != mAdapter) {
@@ -1820,9 +1654,9 @@ public final class GalleryListScene extends BaseScene
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (null != mAdapter) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@@ -1835,9 +1669,7 @@ public final class GalleryListScene extends BaseScene
@Override
public void onShowView(View hiddenView, View shownView) {
if (null != mSearchBarMover) {
mSearchBarMover.showSearchBar();
}
showSearchBar();
showActionFab();
}
@@ -1847,11 +1679,9 @@ public final class GalleryListScene extends BaseScene
}
@Override
protected void onScrollToPosition(int postion) {
if (0 == postion) {
if (null != mSearchBarMover) {
mSearchBarMover.showSearchBar();
}
protected void onScrollToPosition(int position) {
if (0 == position) {
showSearchBar();
showActionFab();
}
}
@@ -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
@@ -96,7 +91,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
Context context = getContext();
AssertUtils.assertNotNull(context);
mClient = EhApplication.getEhClient(context);
mClient = EhApplication.getEhClient();
if (savedInstanceState == null) {
onInit();
} else {
@@ -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,11 +139,10 @@ public class GalleryPreviewsScene extends ToolbarScene {
Context context = getContext();
AssertUtils.assertNotNull(context);
Resources resources = context.getResources();
mAdapter = new GalleryPreviewAdapter();
mRecyclerView.setAdapter(mAdapter);
int columnWidth = resources.getDimensionPixelOffset(Settings.getThumbSizeResId());
int columnWidth = Settings.getThumbSize();
AutoGridLayoutManager layoutManager = new AutoGridLayoutManager(context, columnWidth, LayoutUtils.dp2pix(context, 16));
layoutManager.setStrategy(AutoGridLayoutManager.STRATEGY_SUITABLE_SIZE);
mRecyclerView.setLayoutManager(layoutManager);
@@ -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);
}
}
@@ -368,6 +364,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return GalleryPreviewsScene.this.getContext();
}
@SuppressLint("NotifyDataSetChanged")
@Override
protected void notifyDataSetChanged() {
if (mAdapter != null) {
@@ -376,9 +373,9 @@ public class GalleryPreviewsScene extends ToolbarScene {
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (mAdapter != null) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@@ -43,10 +43,8 @@ import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.dao.HistoryInfo
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.GalleryActivity
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
import com.hippo.ehviewer.widget.SimpleRatingView
import com.hippo.scene.Announcer
@@ -55,10 +53,12 @@ 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
@SuppressLint("NotifyDataSetChanged")
class HistoryScene : ToolbarScene() {
private var mRecyclerView: RecyclerView? = null
private val mAdapter: HistoryAdapter by lazy {
@@ -72,9 +72,7 @@ class HistoryScene : ToolbarScene() {
}
})
}
private val mDownloadManager: DownloadManager by lazy {
EhApplication.getDownloadManager(requireContext())
}
private val mDownloadManager = EhApplication.downloadManager
private val mDownloadInfoListener: DownloadInfoListener by lazy {
object : DownloadInfoListener {
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
@@ -99,9 +97,7 @@ class HistoryScene : ToolbarScene() {
override fun onUpdateLabels() {}
}
}
private val mFavouriteStatusRouter: FavouriteStatusRouter by lazy {
EhApplication.getFavouriteStatusRouter(requireContext())
}
private val mFavouriteStatusRouter = EhApplication.favouriteStatusRouter
private val mFavouriteStatusRouterListener: FavouriteStatusRouter.Listener by lazy {
FavouriteStatusRouter.Listener { _: Long, _: Int ->
mAdapter.notifyDataSetChanged()
@@ -135,7 +131,7 @@ class HistoryScene : ToolbarScene() {
setLiftOnScrollTargetView(recyclerView)
val mFastScroller = ViewUtils.`$$`(content, R.id.fast_scroller) as FastScroller
val mTip = ViewUtils.`$$`(view, R.id.tip) as TextView
ViewTransition(content, mTip)
val mViewTransition = ViewTransition(content, mTip)
val resources = requireContext().resources
val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.big_history)
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
@@ -168,6 +164,15 @@ class HistoryScene : ToolbarScene() {
)
}
}
viewLifecycleOwner.lifecycleScope.launch {
mAdapter.onPagesUpdatedFlow.collectLatest {
if (mAdapter.itemCount == 0) {
mViewTransition.showView(1, true)
} else {
mViewTransition.showView(0, true)
}
}
}
return view
}
@@ -262,9 +267,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)
}
@@ -302,7 +307,7 @@ class HistoryScene : ToolbarScene() {
}
3 -> {
val labelRawList = EhApplication.getDownloadManager(context).labelList
val labelRawList = EhApplication.downloadManager.labelList
val labelList: MutableList<String> = ArrayList(labelRawList.size + 1)
labelList.add(getString(R.string.default_download_label_name))
var i = 0
@@ -366,6 +371,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(
@@ -373,7 +379,7 @@ class HistoryScene : ToolbarScene() {
private val mGi: GalleryInfo
) : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
val downloadManager = EhApplication.getDownloadManager(requireContext())
val downloadManager = EhApplication.downloadManager
val downloadInfo = downloadManager.getDownloadInfo(mGi.gid) ?: return
val label = if (which == 0) null else mLabels[which]
downloadManager.changeLabel(listOf(downloadInfo), label)
@@ -428,6 +434,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) }
}
@@ -99,7 +99,7 @@ public final class ProgressScene extends BaseScene implements View.OnClickListen
.setArgs(mGid, mPToken, mPage)
.setCallback(new GetGalleryTokenListener(context,
activity.getStageId(), getTag()));
EhApplication.getEhClient(context).execute(request);
EhApplication.getEhClient().execute(request);
return true;
}
return false;
@@ -0,0 +1,334 @@
package com.hippo.ehviewer.ui.scene
import android.annotation.SuppressLint
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.text.TextUtils
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.CallSuper
import androidx.core.widget.addTextChangedListener
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.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() {
private var mSearchView: SearchView? = null
private var mRecyclerView: RecyclerView? = null
private var mSuggestionList: List<Suggestion>? = null
private var mSuggestionAdapter: SuggestionAdapter? = null
private var mSuggestionProvider: SuggestionProvider? = null
private var mAllowEmptySearch = true
private val mSearchDatabase by lazy { SearchDatabase.getInstance(context) }
private var onApplySearch: (String) -> Unit = {}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View? {
val view =
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)
mSearchView = view.findViewById(R.id.searchview)
mRecyclerView = view.findViewById(R.id.search_bar_list)
mSearchView?.editText?.addTextChangedListener {
updateSuggestions()
}
mSearchView?.editText?.setOnEditorActionListener { _, _, _ ->
(mToolbar as SearchBar).text = mSearchView!!.text
mSearchView?.hide()
onApplySearch()
true
}
mSuggestionList = ArrayList()
mSuggestionAdapter = SuggestionAdapter(LayoutInflater.from(context))
mRecyclerView?.adapter = mSuggestionAdapter
val layoutManager = LinearLayoutManager(context)
mRecyclerView?.layoutManager = layoutManager
mSearchView?.addTransitionListener { _, _, newState ->
if (newState == SearchView.TransitionState.SHOWING)
onSearchViewExpanded()
else if (newState == SearchView.TransitionState.HIDING)
onSearchViewHidden()
}
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
return view.apply { addView(contentView, 0) }
}
@CallSuper
open fun onSearchViewExpanded() {
(requireActivity() as StageActivity).updateBackPressCallBackStatus()
updateSuggestions()
}
@CallSuper
open fun onSearchViewHidden() {
(requireActivity() as StageActivity).updateBackPressCallBackStatus()
}
fun setSearchBarHint(hint: String?) {
(mToolbar as? SearchBar)?.hint = hint
}
fun setSearchBarText(text: String?) {
(mToolbar as? SearchBar)?.text = text
}
fun setEditTextHint(hint: String?) {
mSearchView?.editText?.hint = hint
}
fun setEditTextText(text: String?) {
mSearchView?.setText(text)
}
fun setNavigationDrawable(drawable: Drawable?) {
(mToolbar as? SearchBar)?.navigationIcon = drawable
}
override fun onNavigationClick() {
toggleDrawer(Gravity.START)
}
fun setOnApplySearch(lambda: (String) -> Unit) {
onApplySearch = lambda
}
fun onApplySearch() {
val query = mSearchView?.text.toString().trim()
if (!mAllowEmptySearch && query.isEmpty()) {
return
}
mSearchDatabase.addQuery(query)
onApplySearch(query)
}
fun setAllowEmptySearch(allowEmptySearch: Boolean) {
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 <= ' ' }
val index1 = keyword.indexOf(':')
if (index1 == -1 || index1 >= keyword.length - 1) {
// Can't find :, or : is the last char
return keyword
}
if (keyword[index1 + 1] == '"') {
// The char after : is ", the word must be quoted
return keyword
}
val index2 = keyword.indexOf(' ')
return if (index2 <= index1) {
// Can't find space, or space is before :
keyword
} else keyword.substring(0, index1 + 1) + "\"" + keyword.substring(index1 + 1) + "$\""
}
interface SuggestionProvider {
fun providerSuggestions(text: String): List<Suggestion>?
}
abstract class Suggestion {
abstract fun getText(textView: TextView): CharSequence?
abstract fun onClick()
open fun onLongClick(): Boolean {
return false
}
}
private class SuggestionHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val text1 = itemView.findViewById(android.R.id.text1) as TextView
val text2 = itemView.findViewById(android.R.id.text2) as TextView
}
private inner class SuggestionAdapter constructor(private val mInflater: LayoutInflater) :
RecyclerView.Adapter<SuggestionHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuggestionHolder {
return SuggestionHolder(mInflater.inflate(R.layout.item_simple_list_2, parent, false))
}
override fun onBindViewHolder(holder: SuggestionHolder, position: Int) {
val suggestion = mSuggestionList?.get(position)
val text1 = suggestion?.getText(holder.text1)
val text2 = suggestion?.getText(holder.text2)
holder.text1.text = text1
if (text2 == null) {
holder.text2.visibility = View.GONE
holder.text2.text = ""
} else {
holder.text2.visibility = View.VISIBLE
holder.text2.text = text2
}
holder.itemView.setOnClickListener {
mSuggestionList?.run {
if (position < size) {
this[position].onClick()
}
}
}
holder.itemView.setOnLongClickListener {
mSuggestionList?.run {
if (position < size) {
return@setOnLongClickListener this[position].onLongClick()
}
}
return@setOnLongClickListener false
}
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemCount(): Int {
return mSuggestionList?.size ?: 0
}
}
inner class TagSuggestion constructor(
private var mHint: String?,
private var mKeyword: String
) :
Suggestion() {
override fun getText(textView: TextView): CharSequence? {
return if (textView.id == android.R.id.text1) {
mKeyword
} else {
mHint
}
}
override fun onClick() {
val edittext = mSearchView?.editText
edittext?.let {
val text = it.text.toString()
var temp = wrapTagKeyword(mKeyword) + " "
if (text.contains(" ")) {
temp = text.substring(0, text.lastIndexOf(" ")) + " " + temp
}
it.setText(temp)
it.setSelection(text.length)
}
}
}
inner class KeywordSuggestion constructor(private val mKeyword: String) : Suggestion() {
override fun getText(textView: TextView): CharSequence? {
return if (textView.id == android.R.id.text1) {
mKeyword
} else {
null
}
}
override fun onClick() {
mSearchView?.editText?.run {
setText(mKeyword)
setSelection(length())
}
}
override fun onLongClick(): Boolean {
BaseDialogBuilder(requireContext())
.setMessage(requireContext().getString(R.string.delete_search_history, mKeyword))
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.delete) { _, _ ->
mSearchDatabase.deleteQuery(mKeyword)
updateSuggestions(false)
}
.show()
return true
}
}
@SuppressLint("NotifyDataSetChanged")
private fun updateSuggestions(scrollToTop: Boolean = true) {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
val suggestions = mutableListOf<Suggestion>()
mergedSuggestionFlow().collect {
suggestions.add(it)
}
withContext(Dispatchers.Main) {
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
}
fun showSearchBar() {
mAppBarLayout?.setExpanded(true)
}
}
@@ -50,7 +50,7 @@ public class SelectSiteScene extends SolidScene implements View.OnClickListener
View view = inflater.inflate(R.layout.scene_select_site, container, false);
mButtonGroup = (MaterialButtonToggleGroup) ViewUtils.$$(view, R.id.button_group);
if (EhApplication.getEhCookieStore(requireContext()).hasSignedIn()) {
if (EhApplication.getEhCookieStore().hasSignedIn()) {
((MaterialButton) view.findViewById(R.id.site_ex)).setChecked(true);
} else {
((MaterialButton) view.findViewById(R.id.site_e)).setChecked(true);
@@ -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) {
@@ -131,7 +131,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
mSignIn = ViewUtils.$$(loginForm, R.id.sign_in);
mSignInViaWebView = (TextView) ViewUtils.$$(loginForm, R.id.sign_in_via_webview);
mSignInViaCookies = (TextView) ViewUtils.$$(loginForm, R.id.sign_in_via_cookies);
mSkipSigningIn = (TextView) ViewUtils.$$(loginForm, R.id.skip_signing_in);
mSkipSigningIn = (TextView) ViewUtils.$$(loginForm, R.id.tourist_mode);
mSignInViaWebView.setPaintFlags(mSignInViaWebView.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mSignInViaCookies.setPaintFlags(mSignInViaCookies.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
@@ -280,16 +280,16 @@ 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()
.setMethod(EhClient.METHOD_SIGN_IN)
.setArgs(username, password)
.setCallback(callback);
EhApplication.getEhClient(context).execute(request);
EhApplication.getEhClient().execute(request);
mSigningIn = true;
}
@@ -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(context).execute(request);
EhApplication.getEhClient().execute(request);
EhApplication.getEhClient().execute(new EhRequest()
.setMethod(EhClient.METHOD_GET_UCONFIG));
}
private void redirectTo() {
@@ -341,7 +344,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
return;
}
if (EhApplication.getEhCookieStore(context).hasSignedIn()) {
if (EhApplication.getEhCookieStore().hasSignedIn()) {
getProfile();
} else {
mSigningIn = false;
@@ -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,8 +117,8 @@ public class WebViewSignInScene extends SolidScene {
return cookies != null ? cookies : Collections.emptyList();
}
private void addCookie(Context context, String domain, Cookie cookie) {
EhApplication.getEhCookieStore(context).addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
private void addCookie(String domain, Cookie cookie) {
EhApplication.getEhCookieStore().addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
}
@Override
@@ -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);
}
}
@@ -51,11 +51,11 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
info.favoriteSlot = slot
}
}
EhApplication.getFavouriteStatusRouter().addListener(listener)
EhApplication.favouriteStatusRouter.addListener(listener)
}
fun destroy() {
EhApplication.getFavouriteStatusRouter().removeListener(listener)
EhApplication.favouriteStatusRouter.removeListener(listener)
}
override fun onAddData(data: GalleryInfo?) {
@@ -100,7 +100,7 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
val bundle = super.saveInstanceState(superState) as Bundle
// TODO It's a bad design
val router = EhApplication.getFavouriteStatusRouter()
val router = EhApplication.favouriteStatusRouter
val id = router.saveDataMap(map)
bundle.putInt(KEY_DATA_MAP, id)
bundle.putInt(KEY_MIN_GID, minGid)
@@ -112,7 +112,7 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
val bundle = state as Bundle
val id = bundle.getInt(KEY_DATA_MAP, IntIdGenerator.INVALID_ID)
if (id != IntIdGenerator.INVALID_ID) {
val router = EhApplication.getFavouriteStatusRouter()
val router = EhApplication.favouriteStatusRouter
val map = router.restoreDataMap(id)
if (map != null) {
this.map = map
@@ -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;
}
}
Loaded 100 of 360 files, more files were not shown because too many files have changed in this diff. Show more